Merge branch 'master' into image-hosting-errwhere

This commit is contained in:
Thibault Duplessis
2025-10-21 10:32:42 +02:00
committed by GitHub
51 changed files with 309 additions and 160 deletions
+6 -6
View File
@@ -59,13 +59,13 @@ Feel free to use the [Lichess API](https://lichess.org/api) in your applications
| Name | Version | Notes |
| ----------------- | ------- | ------------------------------------------------- |
| Chromium / Chrome | last 10 | Full support |
| Firefox | 75+ | Full support (fastest local analysis since FF 79) |
| Edge | 91+ | Full support (reasonable support for 79+) |
| Opera | 66+ | Reasonable support |
| Safari | 11.1+ | Reasonable support |
| Firefox | 115+ | Full support (recommended) |
| Chromium / Chrome | 112+ | Full support |
| Edge | 109+ | Full support |
| Opera | 91+ | Reasonable support |
| Safari | 13.1+ | Reasonable support |
Older browsers (including any version of Internet Explorer) will not work.
Older browsers will not work.
For your own sake, please upgrade. Security and performance, think about it!
## License
+28 -27
View File
@@ -30,33 +30,34 @@ final class Study(
apiC: => Api
) extends LilaController(env):
def search(text: String, page: Int) = OpenOrScopedBody(parse.anyContent)(_.Study.Read, _.Web.Mobile):
Reasonable(page):
WithProxy: proxy ?=>
val maxLen =
if proxy.isFloodish then 50
else if HTTPRequest.isCrawler(req).yes then 80
else if ctx.isAnon then 100
else 200
text.trim.some.filter(_.nonEmpty).filter(_.sizeIs > 2).filter(_.sizeIs < maxLen) match
case None =>
for
pag <- env.study.pager.all(Orders.default, page)
_ <- preloadMembers(pag)
res <- negotiate(
Ok.page(views.study.list.all(pag, Orders.default)),
apiStudies(pag)
)
yield res
case Some(clean) =>
limit.enumeration.search(rateLimited):
env
.studySearch(clean.take(100), page)
.flatMap: pag =>
negotiate(
Ok.page(views.study.list.search(pag, text)),
apiStudies(pag)
)
def search(text: String, page: Int, order: Option[Order]) =
OpenOrScopedBody(parse.anyContent)(_.Study.Read, _.Web.Mobile):
Reasonable(page):
WithProxy: proxy ?=>
val maxLen =
if proxy.isFloodish then 50
else if HTTPRequest.isCrawler(req).yes then 80
else if ctx.isAnon then 100
else 200
text.trim.some.filter(_.nonEmpty).filter(_.sizeIs > 2).filter(_.sizeIs < maxLen) match
case None =>
for
pag <- env.study.pager.all(Orders.default, page)
_ <- preloadMembers(pag)
res <- negotiate(
Ok.page(views.study.list.all(pag, Orders.default)),
apiStudies(pag)
)
yield res
case Some(clean) =>
limit.enumeration.search(rateLimited):
env
.studySearch(clean.take(100), order | Order.relevant, page)
.flatMap: pag =>
negotiate(
Ok.page(views.study.list.search(pag, order | Order.relevant, text)),
apiStudies(pag)
)
def homeLang = LangPage(routes.Study.allDefault())(allResults(Order.hot, 1))
+2 -2
View File
@@ -62,7 +62,7 @@ let currentCategory = '';
const names: string[] = [];
const wgets: string[] = [];
$('div').each((i, el) => {
$('div').each((_, el) => {
const category = $(el)
.text()
.split('\n')
@@ -78,7 +78,7 @@ $('div').each((i, el) => {
$(el)
.find('a')
.each((i, el) => {
.each((_, el) => {
let name = $(el).attr('href');
const url = $(el).attr('data-src');
+1 -1
View File
@@ -238,7 +238,7 @@ GET /study/likes/:order controllers.Study.mineLikes(order: StudyO
GET /study/by/:username controllers.Study.byOwnerDefault(username: UserStr, page: Int ?= 1)
GET /study/by/:username/export.pgn controllers.Study.exportPgn(username: UserStr)
GET /study/by/:username/:order controllers.Study.byOwner(username: UserStr, order: StudyOrder, page: Int ?= 1)
GET /study/search controllers.Study.search(q ?= "", page: Int ?= 1)
GET /study/search controllers.Study.search(q ?= "", page: Int ?= 1, order: Option[StudyOrder] ?= None)
GET /study/$id<\w{8}> controllers.Study.show(id: StudyId)
POST /study controllers.Study.create
POST /study/as controllers.Study.createAs
+6 -1
View File
@@ -37,4 +37,9 @@ case class StartStudy(studyId: StudyId)
case class RemoveStudy(studyId: StudyId)
enum Order:
case hot, newest, oldest, updated, popular, alphabetical, mine
case hot, newest, oldest, updated, popular, alphabetical, mine, relevant
def key = toString
object Order:
def all: List[Order] = values.toList
val byKey = values.mapBy(_.key)
+2
View File
@@ -2060,6 +2060,7 @@ object I18nKey:
val `youCanAlsoScrollOverTheBoardToMoveInTheGame`: I18nKey = "youCanAlsoScrollOverTheBoardToMoveInTheGame"
val `scrollOverComputerVariationsToPreviewThem`: I18nKey = "scrollOverComputerVariationsToPreviewThem"
val `analysisShapesHowTo`: I18nKey = "analysisShapesHowTo"
val `primaryColorArrowsHowTo`: I18nKey = "primaryColorArrowsHowTo"
val `letOtherPlayersMessageYou`: I18nKey = "letOtherPlayersMessageYou"
val `receiveForumNotifications`: I18nKey = "receiveForumNotifications"
val `shareYourInsightsData`: I18nKey = "shareYourInsightsData"
@@ -2456,6 +2457,7 @@ object I18nKey:
val `recentlyUpdated`: I18nKey = "study:recentlyUpdated"
val `mostPopular`: I18nKey = "study:mostPopular"
val `alphabetical`: I18nKey = "study:alphabetical"
val `relevant`: I18nKey = "study:relevant"
val `addNewChapter`: I18nKey = "study:addNewChapter"
val `addMembers`: I18nKey = "study:addMembers"
val `inviteToTheStudy`: I18nKey = "study:inviteToTheStudy"
@@ -9,7 +9,7 @@ import java.time.LocalDate
import lila.common.Form.*
import lila.core.i18n.Translate
import lila.search.spec.{ DateRange, IntRange, Query, Sorting as SpecSorting }
import lila.search.spec.{ DateRange, IntRange, Query, GameSorting as SpecSorting }
final private[gameSearch] class GameSearchForm:
@@ -1,4 +1,5 @@
package lila.gameSearch
import lila.core.i18n.{ I18nKey as trans, Translate }
case class Sorting(f: String, order: String)
+5 -1
View File
@@ -110,6 +110,8 @@ final class StudyPager(
case Order.alphabetical => $sort.asc("name")
// mine filter for topic view
case Order.mine => $sort.desc("rank")
// relevant not used here
case Order.relevant => $sort.desc("rank")
,
hint = hint
).mapFutureList(withChaptersAndLiking())
@@ -146,9 +148,10 @@ final class StudyPager(
object Orders:
import lila.core.study.Order
val default = Order.hot
val list = Order.values.toList
val list = Order.all.filter(_ != Order.relevant)
val withoutMine = list.filterNot(_ == Order.mine)
val withoutSelector = withoutMine.filter(o => o != Order.oldest && o != Order.alphabetical)
val search = List(Order.hot, Order.newest, Order.popular, Order.alphabetical, Order.relevant)
private val byKey = list.mapBy(_.toString)
def apply(key: String): Order = byKey.getOrElse(key, default)
val name: Order => I18nKey =
@@ -159,3 +162,4 @@ object Orders:
case Order.popular => I18nKey.study.mostPopular
case Order.alphabetical => I18nKey.study.alphabetical
case Order.mine => I18nKey.study.myStudies
case Order.relevant => I18nKey.study.relevant
+1 -1
View File
@@ -37,7 +37,7 @@ object StudyPgnTags:
def validate(name: String, value: String): Option[Tag] = for
tpe <- Tag.tagTypesByLowercase.get(name.toLowerCase).filter(relevantTypeSet)
cleaned = lila.common.String.fullCleanUp(value)
if cleaned.nonEmpty && cleaned.length <= 140
if cleaned.length <= 140
yield Tag(tpe, cleaned)
def validateTagTypes(tags: Tags): Either[String, Tags] =
+7 -6
View File
@@ -96,7 +96,7 @@ final class ListUi(helpers: Helpers, bits: StudyBits):
url = routes.Study.minePrivate(_)
)
def search(pag: Paginator[WithChaptersAndLiked], text: String)(using Context) =
def search(pag: Paginator[WithChaptersAndLiked], order: Order, text: String)(using Context) =
Page(text)
.css("analyse.study.index")
.js(infiniteScrollEsmInit):
@@ -104,10 +104,11 @@ final class ListUi(helpers: Helpers, bits: StudyBits):
menu("search", Orders.default),
main(cls := "page-menu__content study-index box")(
div(cls := "box__top")(
searchForm(trans.search.search.txt(), text),
searchForm(trans.search.search.txt(), text, order),
bits.orderSelect(order, "search", url = o => routes.Study.search(text, 1, o.some)),
bits.newForm()
),
paginate(pag, routes.Study.search(text))
paginate(pag, routes.Study.search(text, pag.currentPage, order.some))
)
)
@@ -127,7 +128,7 @@ final class ListUi(helpers: Helpers, bits: StudyBits):
menu(active, order, topics.so(_.value)),
main(cls := "page-menu__content study-index box")(
div(cls := "box__top")(
searchForm(title, s"$searchFilter${searchFilter.nonEmpty.so(" ")}"),
searchForm(title, s"$searchFilter${searchFilter.nonEmpty.so(" ")}", order),
bits.orderSelect(order, active, url),
bits.newForm()
),
@@ -171,8 +172,8 @@ final class ListUi(helpers: Helpers, bits: StudyBits):
)(trs.whatAreStudies())
)
def searchForm(placeholder: String, value: String) =
form(cls := "search", action := routes.Study.search(), method := "get")(
def searchForm(placeholder: String, value: String, order: Order) =
form(cls := "search", action := routes.Study.search(order = order.some), method := "get")(
input(name := "q", st.placeholder := placeholder, st.value := value, enterkeyhint := "search"),
submitButton(cls := "button", dataIcon := Icon.Search)
)
+2 -1
View File
@@ -12,7 +12,8 @@ final class StudyBits(helpers: Helpers):
def orderSelect(order: Order, active: String, url: Order => Call)(using Context) =
val orders =
if active == "all" then Orders.withoutSelector
if active == "search" then Orders.search
else if active == "all" then Orders.withoutSelector
else if active.startsWith("topic") then Orders.list
else Orders.withoutMine
lila.ui.bits.mselect(
+22 -3
View File
@@ -5,8 +5,10 @@ import scalalib.paginator.*
import lila.search.*
import lila.search.client.SearchClient
import lila.search.spec.Query
import lila.search.spec.{ Query, StudySorting }
import lila.study.Study
import lila.search.spec.{ Order as SpecOrder, StudySortField }
import lila.core.study.Order
final class Env(
studyRepo: lila.study.StudyRepo,
@@ -16,13 +18,30 @@ final class Env(
val api: StudySearchApi = wire[StudySearchApi]
def apply(text: String, page: Int)(using me: Option[Me]) =
def apply(text: String, order: Order, page: Int)(using me: Option[Me]) =
Paginator[Study.WithChaptersAndLiked](
adapter = new AdapterLike[Study]:
def query = Query.study(text.take(100), me.map(_.userId.value))
def query =
Query.study(
text.take(100),
order.toSpec,
me.map(_.userId.value)
)
def nbResults = api.count(query).dmap(_.toInt)
def slice(offset: Int, length: Int) = api.search(query, From(offset), Size(length))
.mapFutureList(pager.withChaptersAndLiking()),
currentPage = page,
maxPerPage = pager.maxPerPage
)
extension (x: Order)
def toSpec: Option[StudySorting] =
x.match
case Order.alphabetical => StudySorting(StudySortField.Name, SpecOrder.Asc).some
case Order.hot => StudySorting(StudySortField.Hot, SpecOrder.Desc).some
case Order.newest => StudySorting(StudySortField.CreatedAt, SpecOrder.Desc).some
case Order.oldest => StudySorting(StudySortField.CreatedAt, SpecOrder.Asc).some
case Order.popular => StudySorting(StudySortField.Likes, SpecOrder.Desc).some
case Order.updated => StudySorting(StudySortField.UpdatedAt, SpecOrder.Desc).some
case Order.mine => StudySorting(StudySortField.Likes, SpecOrder.Asc).some
case Order.relevant => none
+10 -3
View File
@@ -38,9 +38,16 @@ object LilaRouter:
given PathBindable[Color] =
strPath[Color](Color.fromName, "Invalid chess color, should be white or black", _.name)
given PathBindable[Uci] = strPath[Uci](Uci.apply, "Invalid UCI move", _.uci)
given PathBindable[StudyOrder] = strPath[StudyOrder](
s => scala.util.Try(StudyOrder.valueOf(s).some).getOrElse(None),
"Invalid study order"
given PathBindable[StudyOrder] = strPath(
StudyOrder.byKey.get,
s"Invalid study order ${StudyOrder.all.mkString(", ")}"
)
given QueryStringBindable[StudyOrder] = strQueryString(
StudyOrder.byKey.get,
s"Invalid study order ${StudyOrder.all.mkString(", ")}",
_.key
)
private def urlEncode(str: String) = java.net.URLEncoder.encode(str, "utf-8")
+6 -1
View File
@@ -137,7 +137,12 @@ object help:
ul(
li(trans.site.youCanAlsoScrollOverTheBoardToMoveInTheGame()),
li(trans.site.scrollOverComputerVariationsToPreviewThem()),
li(trans.site.analysisShapesHowTo())
li(
trans.site.analysisShapesHowTo(),
ul(
li(trans.site.primaryColorArrowsHowTo())
)
)
)
)
)
+2 -2
View File
@@ -28,13 +28,13 @@
]
},
"dependencies": {
"@lichess-org/chessground": "9.8.3",
"@lichess-org/chessground": "9.8.4",
"@lichess-org/pgn-viewer": "2.5.2",
"@types/lichess": "workspace:*",
"@typescript-eslint/eslint-plugin": "^8.46.1",
"@typescript-eslint/parser": "^8.46.1",
"ab": "github:lichess-org/ab-stub",
"chessops": "^0.15.0",
"chessops": "^0.15",
"eslint": "^9.38.0",
"eslint-plugin-compat": "^6.0.2",
"lint-staged": "^16.2.4",
+65 -51
View File
@@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@lichess-org/chessground':
specifier: 9.8.3
version: 9.8.3
specifier: 9.8.4
version: 9.8.4
'@lichess-org/pgn-viewer':
specifier: 2.5.2
version: 2.5.2
@@ -27,7 +27,7 @@ importers:
specifier: github:lichess-org/ab-stub
version: https://codeload.github.com/lichess-org/ab-stub/tar.gz/94236bf34dbc9c05daf50f4c9842d859b9142be0
chessops:
specifier: ^0.15.0
specifier: ^0.15
version: 0.15.0
eslint:
specifier: ^9.38.0
@@ -155,7 +155,7 @@ importers:
specifier: ^1.9.3
version: 1.9.3
cropperjs:
specifier: ^1.6.2
specifier: 1.6.2
version: 1.6.2
debounce-promise:
specifier: ^3.1.2
@@ -761,6 +761,12 @@ packages:
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
'@eslint-community/eslint-utils@4.9.0':
resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
'@eslint-community/regexpp@4.12.1':
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
@@ -830,8 +836,8 @@ packages:
'@kurkle/color@0.3.4':
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
'@lichess-org/chessground@9.8.3':
resolution: {integrity: sha512-W0dTDeuKLsVJqYRF34OBcRrVqX4EC6/vK3zH11iT1dTR2i3yh79VkTckix8BWrXX8K0PLqWsK7A+/7CoTr+arA==}
'@lichess-org/chessground@9.8.4':
resolution: {integrity: sha512-bVPXvR1EWXAAYsuvT7LBmAuwMgIW18RwfFCgwLFT6+g6Ou0mXIJNhq9eczQKTxKnGHTKDbj55AyzmVpIxogYXg==}
'@lichess-org/pgn-viewer@2.5.2':
resolution: {integrity: sha512-RPbq5xOa80/p1B7DkZQe4HDJN18gf7SANn8nXfDOmCi9tmqbVAPEtGs7W+qw62B5EZpU98gXxfomZ3Cr/rOkpA==}
@@ -1033,8 +1039,8 @@ packages:
'@types/qrcode@1.5.5':
resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==}
'@types/react@19.2.2':
resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==}
'@types/react@19.1.9':
resolution: {integrity: sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==}
'@types/sortablejs@1.15.8':
resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==}
@@ -1681,8 +1687,8 @@ packages:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
is-fullwidth-code-point@5.1.0:
resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==}
is-fullwidth-code-point@5.0.0:
resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==}
engines: {node: '>=18'}
is-glob@4.0.3:
@@ -1742,8 +1748,8 @@ packages:
engines: {node: '>=20.17'}
hasBin: true
listr2@9.0.5:
resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==}
listr2@9.0.4:
resolution: {integrity: sha512-1wd/kpAdKRLwv7/3OKC8zZ5U8e/fajCfWMxacUvB79S5nLrYGPtUI/8chMQhn3LQjsRVErTb9i1ECAwW0ZIHnQ==}
engines: {node: '>=20.0.0'}
locate-path@5.0.0:
@@ -2097,8 +2103,8 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
slice-ansi@7.1.2:
resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==}
slice-ansi@7.1.0:
resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==}
engines: {node: '>=18'}
snabbdom@3.5.1:
@@ -2153,8 +2159,8 @@ packages:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-ansi@7.1.2:
resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
strip-ansi@7.1.0:
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
engines: {node: '>=12'}
strip-json-comments@3.1.1:
@@ -2483,7 +2489,7 @@ snapshots:
lru-cache: 10.4.3
optional: true
'@babel/runtime@7.28.4': {}
'@babel/runtime@7.28.2': {}
'@badrap/result@0.3.1': {}
@@ -2491,7 +2497,7 @@ snapshots:
'@blakeembrey/template@1.2.0': {}
'@csstools/color-helpers@5.1.0':
'@csstools/color-helpers@5.0.2':
optional: true
'@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
@@ -2594,6 +2600,11 @@ snapshots:
'@esbuild/win32-x64@0.25.11':
optional: true
'@eslint-community/eslint-utils@4.7.0(eslint@9.38.0)':
dependencies:
eslint: 9.38.0
eslint-visitor-keys: 3.4.3
'@eslint-community/eslint-utils@4.9.0(eslint@9.38.0)':
dependencies:
eslint: 9.38.0
@@ -2604,7 +2615,7 @@ snapshots:
'@eslint/config-array@0.21.1':
dependencies:
'@eslint/object-schema': 2.1.7
debug: 4.4.3
debug: 4.4.1
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -2670,12 +2681,12 @@ snapshots:
'@kurkle/color@0.3.4': {}
'@lichess-org/chessground@9.8.3': {}
'@lichess-org/chessground@9.8.4': {}
'@lichess-org/pgn-viewer@2.5.2':
dependencies:
'@lichess-org/chessground': 9.8.3
'@types/node': 24.8.1
'@lichess-org/chessground': 9.8.4
'@types/node': 24.7.2
chessops: 0.15.0
snabbdom: 3.5.1
@@ -2884,7 +2895,7 @@ snapshots:
'@typescript-eslint/types': 8.46.1
'@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.46.1
debug: 4.4.3
debug: 4.4.1
eslint: 9.38.0
typescript: 5.9.3
transitivePeerDependencies:
@@ -2894,7 +2905,7 @@ snapshots:
dependencies:
'@typescript-eslint/tsconfig-utils': 8.46.1(typescript@5.9.3)
'@typescript-eslint/types': 8.46.1
debug: 4.4.3
debug: 4.4.1
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -2913,7 +2924,7 @@ snapshots:
'@typescript-eslint/types': 8.46.1
'@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3)
'@typescript-eslint/utils': 8.46.1(eslint@9.38.0)(typescript@5.9.3)
debug: 4.4.3
debug: 4.4.1
eslint: 9.38.0
ts-api-utils: 2.1.0(typescript@5.9.3)
typescript: 5.9.3
@@ -2928,11 +2939,11 @@ snapshots:
'@typescript-eslint/tsconfig-utils': 8.46.1(typescript@5.9.3)
'@typescript-eslint/types': 8.46.1
'@typescript-eslint/visitor-keys': 8.46.1
debug: 4.4.3
debug: 4.4.1
fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 9.0.5
semver: 7.7.3
semver: 7.7.2
ts-api-utils: 2.1.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
@@ -2940,7 +2951,7 @@ snapshots:
'@typescript-eslint/utils@8.46.1(eslint@9.38.0)(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0)
'@eslint-community/eslint-utils': 4.7.0(eslint@9.38.0)
'@typescript-eslint/scope-manager': 8.46.1
'@typescript-eslint/types': 8.46.1
'@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3)
@@ -2962,13 +2973,13 @@ snapshots:
chai: 5.3.3
tinyrainbow: 2.0.0
'@vitest/mocker@3.2.4(vite@7.1.11(@types/node@24.8.1)(yaml@2.8.1))':
'@vitest/mocker@3.2.4(vite@7.0.6(@types/node@24.7.2)(yaml@2.8.1))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.19
optionalDependencies:
vite: 7.1.11(@types/node@24.8.1)(yaml@2.8.1)
vite: 7.0.6(@types/node@24.7.2)(yaml@2.8.1)
'@vitest/pretty-format@3.2.4':
dependencies:
@@ -3183,7 +3194,7 @@ snapshots:
cli-truncate@5.1.0:
dependencies:
slice-ansi: 7.1.2
slice-ansi: 7.1.0
string-width: 8.1.0
cliui@6.0.0:
@@ -3343,8 +3354,8 @@ snapshots:
dependencies:
'@mdn/browser-compat-data': 5.7.6
ast-metadata-inferer: 0.8.1
browserslist: 4.26.3
caniuse-lite: 1.0.30001751
browserslist: 4.25.1
caniuse-lite: 1.0.30001731
eslint: 9.38.0
find-up: 5.0.0
globals: 15.15.0
@@ -3370,7 +3381,7 @@ snapshots:
'@eslint/eslintrc': 3.3.1
'@eslint/js': 9.38.0
'@eslint/plugin-kit': 0.4.0
'@humanfs/node': 0.16.7
'@humanfs/node': 0.16.6
'@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.4.3
'@types/estree': 1.0.8
@@ -3487,7 +3498,7 @@ snapshots:
get-caller-file@2.0.5: {}
get-east-asian-width@1.4.0: {}
get-east-asian-width@1.3.0: {}
glob-parent@5.1.2:
dependencies:
@@ -3560,7 +3571,7 @@ snapshots:
is-fullwidth-code-point@3.0.0: {}
is-fullwidth-code-point@5.1.0:
is-fullwidth-code-point@5.0.0:
dependencies:
get-east-asian-width: 1.4.0
@@ -3631,14 +3642,14 @@ snapshots:
lint-staged@16.2.4:
dependencies:
commander: 14.0.1
listr2: 9.0.5
listr2: 9.0.4
micromatch: 4.0.8
nano-spawn: 2.0.0
pidtree: 0.6.0
string-argv: 0.3.2
yaml: 2.8.1
listr2@9.0.5:
listr2@9.0.4:
dependencies:
cli-truncate: 5.1.0
colorette: 2.0.20
@@ -3682,8 +3693,6 @@ snapshots:
marked@16.4.1: {}
memory-pager@1.5.0: {}
merge2@1.4.1: {}
micromatch@4.0.8:
@@ -3730,7 +3739,7 @@ snapshots:
dependencies:
boolbase: 1.0.0
nwsapi@2.2.22:
nwsapi@2.2.21:
optional: true
object-assign@4.1.1: {}
@@ -3979,7 +3988,7 @@ snapshots:
signal-exit@4.1.0: {}
slice-ansi@7.1.2:
slice-ansi@7.1.0:
dependencies:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
@@ -4025,13 +4034,18 @@ snapshots:
get-east-asian-width: 1.4.0
strip-ansi: 7.1.2
string-width@8.1.0:
dependencies:
get-east-asian-width: 1.3.0
strip-ansi: 7.1.0
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
strip-ansi@7.1.2:
dependencies:
ansi-regex: 6.2.2
ansi-regex: 6.1.0
strip-json-comments@3.1.1: {}
@@ -4155,13 +4169,13 @@ snapshots:
uuid@11.1.0: {}
vite-node@3.2.4(@types/node@24.8.1)(yaml@2.8.1):
vite-node@3.2.4(@types/node@24.7.2)(yaml@2.8.1):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 7.1.11(@types/node@24.8.1)(yaml@2.8.1)
vite: 7.0.6(@types/node@24.7.2)(yaml@2.8.1)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -4176,7 +4190,7 @@ snapshots:
- tsx
- yaml
vite@7.1.11(@types/node@24.8.1)(yaml@2.8.1):
vite@7.0.6(@types/node@24.7.2)(yaml@2.8.1):
dependencies:
esbuild: 0.25.11
fdir: 6.5.0(picomatch@4.0.3)
@@ -4189,11 +4203,11 @@ snapshots:
fsevents: 2.3.3
yaml: 2.8.1
vitest@3.2.4(@types/node@24.8.1)(jsdom@26.1.0)(yaml@2.8.1):
vitest@3.2.4(@types/node@24.7.2)(jsdom@26.1.0)(yaml@2.8.1):
dependencies:
'@types/chai': 5.2.2
'@vitest/expect': 3.2.4
'@vitest/mocker': 3.2.4(vite@7.1.11(@types/node@24.8.1)(yaml@2.8.1))
'@vitest/mocker': 3.2.4(vite@7.0.6(@types/node@24.7.2)(yaml@2.8.1))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
@@ -4211,8 +4225,8 @@ snapshots:
tinyglobby: 0.2.15
tinypool: 1.1.1
tinyrainbow: 2.0.0
vite: 7.1.11(@types/node@24.8.1)(yaml@2.8.1)
vite-node: 3.2.4(@types/node@24.8.1)(yaml@2.8.1)
vite: 7.0.6(@types/node@24.7.2)(yaml@2.8.1)
vite-node: 3.2.4(@types/node@24.7.2)(yaml@2.8.1)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 24.8.1
@@ -4314,4 +4328,4 @@ snapshots:
yocto-queue@0.1.0: {}
zxcvbn@4.4.2: {}
zxcvbn@4.4.2: {}
+1 -1
View File
@@ -28,7 +28,7 @@ object Dependencies {
val lettuce = "io.lettuce" % "lettuce-core" % "6.8.1.RELEASE"
val nettyTransport =
("io.netty" % s"netty-transport-native-$notifier" % "4.2.7.Final").classifier(s"$os-$arch")
val lilaSearch = "com.github.lichess-org.lila-search" %% "client" % "3.2.4"
val lilaSearch = "com.github.lichess-org.lila-search" %% "client" % "3.3.0"
val munit = "org.scalameta" %% "munit" % "1.2.1" % Test
val uaparser = "org.uaparser" %% "uap-scala" % "0.20.0"
val apacheText = "org.apache.commons" % "commons-text" % "1.14.0"
+4
View File
@@ -5,6 +5,9 @@
<plurals name="nbBroadcasts">
<item quantity="other">%s 個直播</item>
</plurals>
<plurals name="nbViewers">
<item quantity="other">%s 位觀眾</item>
</plurals>
<string name="liveBroadcasts">錦標賽直播</string>
<string name="broadcastCalendar">直播時程表</string>
<string name="newBroadcast">新的現場直播</string>
@@ -75,4 +78,5 @@
<string name="allBroadcastsByMonth">以月份顯示所有直播</string>
<string name="backToLiveMove">返回及時走法</string>
<string name="sinceHideResults">由於您選擇隱藏結果,所有預覽棋盤都是空的,以避免劇透。</string>
<string name="liveboard">實時棋局</string>
</resources>
+11 -11
View File
@@ -18,26 +18,26 @@
<string name="removedStudents">Уклоњени</string>
<string name="inviteTheStudentBack">Позови ученика назад</string>
<string name="sendAMessage">Пошаљи поруку свим ученицима.</string>
<string name="aLinkToTheClassWillBeAdded">Линк за час ће бити аутоматски додат на крају поруке, нема потребе да га сами укључите.</string>
<string name="aLinkToTheClassWillBeAdded">Линк до часа ће бити аутоматски додат на крају поруке, тако да не мораш сам/а да га убациш.</string>
<string name="className">Назив часа</string>
<string name="classDescription">Опис часа</string>
<string name="visibleByBothStudentsAndTeachers">Видљиво и учитељима и ученицима овога часа</string>
<string name="teachersOfTheClass">Учитељи</string>
<string name="addLichessUsernames">Додајте Lichess корисничка имена да бисте их позвали да буду учитељи. Свако име у новом реду.</string>
<string name="resetPassword">Обнови лозинку</string>
<string name="visibleByBothStudentsAndTeachers">Видљиво и учитељима и ученицима часа</string>
<string name="teachersOfTheClass">Учитељи часа</string>
<string name="addLichessUsernames">Додај Lichess корисничка имена да их позовеш као учитеље. Једно по линији.</string>
<string name="resetPassword">Промени лозинку</string>
<string name="makeSureToCopy">Копирајте или запиђите лозинку. Нећете моћи да је поново видите!</string>
<string name="passwordX">Лозинка: %s</string>
<string name="generateANewPassword">Направите нову лозинку за ученика</string>
<string name="invitedToXByY">Позван на %1$s од стране %2$s</string>
<string name="generateANewPassword">Генериши нову лозинку за ученика</string>
<string name="invitedToXByY">Позван/а на %1$s од стране %2$s</string>
<string name="realName">Право име</string>
<string name="privateWillNeverBeShown">Приватно. Никада неће бити приказано изван учионице. Помаже да се присетите ко је ученик.</string>
<string name="addStudent">Додајте ученика</string>
<string name="lichessProfileXCreatedForY">Личес налог %1$s направљен за %2$s.</string>
<string name="addStudent">Додај ученика</string>
<string name="lichessProfileXCreatedForY">Lichess профил %1$s направљен за %2$s.</string>
<string name="studentCredentials">Ученик: %1$s
Корисничко име: %2$s
Лозинка: %3$s</string>
<string name="inviteALichessAccount">Позовите Личес налог</string>
<string name="inviteDesc1">Ако ученик већ има Lichess налог, можете их позвати на час.</string>
<string name="inviteALichessAccount">Позови Lichess налог</string>
<string name="inviteDesc1">Ако ученик већ има Lichess налог, можеш га/је позвати у час.</string>
<string name="inviteDesc2">Примиће поруку на Lichess са везом да приступе часу.</string>
<string name="inviteDesc3">Важно: позовите само ученике које познајете и који активно желе да се придруже часу.</string>
<string name="inviteDesc4">Никада не шаљите позивнице насумичним играчима.</string>
+1
View File
@@ -115,4 +115,5 @@
<string name="xIsAKidAccountWarning">%1$s 是未成年帳戶,無法接收您的消息。您必須手動給他發送邀請鏈接: %2$s</string>
<string name="moveToClass">移動到 %s</string>
<string name="moveToAnotherClass">移動到另一個課程</string>
<string name="allowMessagingBetweenStudents">允許學生之間的消息</string>
</resources>
+1 -1
View File
@@ -2,7 +2,7 @@
<resources>
<string name="emailConfirm_subject">%s,请确认您的 lichess.org 账户。</string>
<string name="emailConfirm_click" comment="emailConfirm_click&#10;&#10;Part of the confirmation email upon registering for a Lichess account.&#10;&#10;The unique reset link is presented just after this string.">单击下列链接以激活您的 Lichess 帐户:</string>
<string name="emailConfirm_justIgnore" comment="This text is included in the registration email for new users.">如果您没有注册 Lichess,您可以安全地忽略此消息。 未确认的账户和您邮箱地址的所有信息将在 48 小时后从我们的系统中删除。</string>
<string name="emailConfirm_justIgnore" comment="This text is included in the registration email for new users.">如果您并未在 Lichess 注册,可放心忽略此邮件。未确认的账号及所有相关邮箱记录将在 48 小时后从系统中删除。</string>
<string name="passwordReset_subject">%s,请重置您的 lichess.org 密码。</string>
<string name="passwordReset_intro">我们收到了重置您账户密码的请求。</string>
<string name="passwordReset_clickOrIgnore">如果是您发出了这个请求,请点击下方的链接,否则请忽略本邮件。</string>
+1 -1
View File
@@ -4,7 +4,7 @@
<string name="xHasNoChessInsights">%s nie ma jeszcze wygenerowanych statystyk!</string>
<string name="insightsAreProtected">Statystyki użytkownika %s są ukryte</string>
<string name="cantSeeInsights">Nie masz dostępu do statystyk użytkownika %s.</string>
<string name="generateInsights">Wygeneruj statystyki szachowe dla użytkownika %s.</string>
<string name="generateInsights" comment="%s is a player's username.&#10;&#10;Clicking this will scan data on Lichess to create insights for player %s.&#10;&#10;Example:&#10;&#10;Generate DrNykterstein's chess insights.">Wygeneruj statystyki szachowe dla użytkownika %s.</string>
<string name="crunchingData">Teraz przetwarzamy dane tylko dla ciebie!</string>
<string name="maybeAskThemToChangeTheir" comment="The parameter is the translation for 'insights settings'">Może poprosić ich o zmianę %s?</string>
<string name="insightsSettings" comment="In the context of 'maybe ask them to change their insight settings'">ustawienia dostępności statystyk</string>
+1 -1
View File
@@ -44,7 +44,7 @@
<string name="whitePawn">biały pion</string>
<string name="blackPawn">czarny pion</string>
<string name="sanSymbols" comment="Keep this order: king queen rook bishop knight takes">K Q R B N x</string>
<string name="sanTakes" comment="Indicates a piece captured another piece as part of a longer sentence.&#10;Examples:&#10;&quot;knight takes f 3&quot;&#10;&quot;e takes f 3&quot;&#10;The exact format can vary depending on the user's preferences. See the screenshot for the options as of 2025-08-23.&#10;&#10;Used only in blind mode when the screen reader verbally announces a move.&#10;&#10;'Takes' corresponds to &quot;x&quot; in Standard Algebraic Notation, for pieces being captured.">zbija</string>
<string name="sanTakes" comment="Indicates a piece captured another piece as part of a longer sentence.&#10;Examples:&#10;&quot;knight takes f 3&quot;&#10;&quot;e takes f 3&quot;&#10;The exact format can vary depending on the user's preferences.&#10;&#10;Used only in blind mode when the screen reader verbally announces a move.&#10;&#10;&quot;Takes&quot; corresponds to &quot;x&quot; in Standard Algebraic Notation, for pieces being captured.">zbija</string>
<string name="sanCheck" comment="+ in standard notation">szach</string>
<string name="sanCheckmate" comment="# in standard notation">mat</string>
<string name="sanPromotesTo" comment="Indicates a piece is promoted, as part of a longer sentence.&#10;Examples:&#10;&quot;e promotes to queen&quot;&#10;&#10;Used only in blind mode when the screen reader verbally announces a move.&#10;&#10;'Takes' corresponds to &quot;=&quot; in Standard Algebraic Notation, for pieces being promoted.">promuje do</string>
+11 -11
View File
@@ -3,16 +3,16 @@
<string name="logInAsUsername">%s 已登录</string>
<string name="welcome">欢迎!</string>
<string name="welcomeToLichess">欢迎来到 Lichess</string>
<string name="thisIsYourProfilePage">这是的个人资料页</string>
<string name="enabledKidModeSuggestion">如果此账户是为儿童创建的,那可能会想启用 %s。</string>
<string name="whatNowSuggestions">接下来做什么?以下是一些建议:</string>
<string name="learnChessRules">学习国际象棋的规则</string>
<string name="improveWithChessTacticsPuzzles">练习谜题以提高战术技巧</string>
<string name="playTheArtificialIntelligence"> AI 对</string>
<string name="playOpponentsFromAroundTheWorld">世界各地的棋手对</string>
<string name="followYourFriendsOnLichess">在 Lichess 上关注的朋友</string>
<string name="playInTournaments">锦标赛</string>
<string name="thisIsYourProfilePage">这是的个人资料页。</string>
<string name="enabledKidModeSuggestion">如果此账户是为儿童创建的,那可能需要启用 %s。</string>
<string name="whatNowSuggestions">现在做什么?这里有一些建议:</string>
<string name="learnChessRules">学习国规则</string>
<string name="improveWithChessTacticsPuzzles">通过战术谜题提升棋艺</string>
<string name="playTheArtificialIntelligence"> AI 对</string>
<string name="playOpponentsFromAroundTheWorld">与来自世界各地的棋手对</string>
<string name="followYourFriendsOnLichess">在 Lichess 上关注的朋友</string>
<string name="playInTournaments">锦标赛</string>
<string name="learnFromXAndY">从 %1$s 和 %2$s 中学习</string>
<string name="configureLichess">根据您的好配置 Lichess</string>
<string name="exploreTheSiteAndHaveFun">探索站,愉快下棋 :)</string>
<string name="configureLichess">根据您的好配置 Lichess</string>
<string name="exploreTheSiteAndHaveFun">探索站,祝您玩得开心 :)</string>
</resources>
+5
View File
@@ -2,6 +2,7 @@
<resources>
<string name="gameSetup">게임 설정</string>
<string name="challengeAFriend" comment="be mindful how 'friend' translates in gendered languages. look for a more neutral term if there's an issue, or base your translation on a different phrase like 'invite to a game'. keep it short and convey the button's meaning without offense.">친구에게 도전</string>
<string name="playAgainstComputer">컴퓨터와 대국하기</string>
<string name="gameMode">게임 모드</string>
<string name="createLobbyGame" comment="One of three buttons on the home page. Opens the modal to create a game that goes in the lobby.">로비 게임 만들기</string>
<string name="youPlayAs">당신은 재생</string>
@@ -837,6 +838,7 @@
<string name="playVariationToCreateConditionalPremoves">기물을 움직여 조건적인 수를 만들기</string>
<string name="noConditionalPremoves">조건적인 수가 없습니다</string>
<string name="playX">%s 를 둠</string>
<string name="challengeX">%s에게 도전하기</string>
<plurals name="andSaveNbPremoveLines">
<item quantity="other">%s 종류의 조건 수를 설정</item>
</plurals>
@@ -966,4 +968,7 @@ FEN 포지션을 생성하기 위해 %s를 사용할 수 있습니다.
<string name="accessibility">접근성</string>
<string name="enableBlindMode">시각장애 모드 활성화하기</string>
<string name="disableBlindMode">시각장애 모드 비활성화하기</string>
<string name="copyToClipboard">클립보드에 복사</string>
<string name="online" comment="status of a user">온라인</string>
<string name="offline" comment="status of a user">오프라인</string>
</resources>
+3
View File
@@ -4,6 +4,7 @@
<string name="challengeAFriend" comment="be mindful how 'friend' translates in gendered languages. look for a more neutral term if there's an issue, or base your translation on a different phrase like 'invite to a game'. keep it short and convey the button's meaning without offense.">Daag een vriend uit</string>
<string name="playAgainstComputer">Speel tegen de computer</string>
<string name="gameMode">Spelmodus</string>
<string name="createLobbyGame" comment="One of three buttons on the home page. Opens the modal to create a game that goes in the lobby.">Maak een lobby spel aan</string>
<string name="youPlayAs">Je speelt als</string>
<string name="toInviteSomeoneToPlayGiveThisUrl">Deel deze link als u iemand wil uitnodigen om met u te spelen</string>
<string name="gameOver">Partij afgelopen</string>
@@ -344,6 +345,7 @@
<item quantity="other">%s bezig</item>
</plurals>
<string name="exportGames">Partijen exporteren</string>
<string name="ratingFilter">Filter van rating</string>
<plurals name="giveNbSeconds">
<item quantity="one">Geef %s seconde</item>
<item quantity="other">Geef %s seconden</item>
@@ -828,6 +830,7 @@
<string name="notifications">Meldingen</string>
<string name="notificationsX">Meldingen: %1$s</string>
<string name="perfRatingX">Rating: %s</string>
<string name="yourRatingIsX">Uw rating is %s</string>
<plurals name="nbSecondsToPlayTheFirstMove">
<item quantity="one">%s seconde om de eerste zet te spelen</item>
<item quantity="other">%s seconden om de eerste zet te spelen</item>
+22
View File
@@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="gameSetup">棋局設置</string>
<string name="challengeAFriend" comment="be mindful how 'friend' translates in gendered languages. look for a more neutral term if there's an issue, or base your translation on a different phrase like 'invite to a game'. keep it short and convey the button's meaning without offense.">挑戰一位朋友</string>
<string name="playAgainstComputer">與電腦對局</string>
<string name="gameMode">遊戲模式</string>
<string name="toInviteSomeoneToPlayGiveThisUrl">請分享此網址以邀人下棋</string>
<string name="gameOver">遊戲結束</string>
<string name="waitingForOpponent">正在等待對手</string>
@@ -20,6 +24,7 @@
<string name="asBlack">作為黑方</string>
<string name="randomColor">隨機選色</string>
<string name="createAGame">開始對局</string>
<string name="createTheGame">創建對局</string>
<string name="whiteIsVictorious">白方勝</string>
<string name="blackIsVictorious">黑方勝</string>
<string name="youPlayTheWhitePieces">您執白棋</string>
@@ -154,6 +159,7 @@
<string name="drawByMutualAgreement">雙方同意和局</string>
<string name="fiftyMovesWithoutProgress">50步規則和局</string>
<string name="currentGames">當前對局</string>
<string name="joinedX">於 %s 加入</string>
<plurals name="nbGames">
<item quantity="other">查看所有%s盤棋</item>
</plurals>
@@ -319,7 +325,15 @@
<string name="openingExplorerAndTablebase">開局瀏覽器 &amp; 總局資料庫</string>
<string name="takeback">悔棋</string>
<string name="proposeATakeback">請求悔棋</string>
<string name="whiteProposesTakeback">白方提議悔棋</string>
<string name="blackProposesTakeback">黑方提議悔棋</string>
<string name="takebackPropositionSent">悔棋請求已發送</string>
<string name="whiteDeclinesTakeback">白方拒絕悔棋</string>
<string name="blackDeclinesTakeback">黑方拒絕悔棋</string>
<string name="whiteAcceptsTakeback">白方同意悔棋</string>
<string name="blackAcceptsTakeback">黑方同意悔棋</string>
<string name="whiteCancelsTakeback">白方取消悔棋</string>
<string name="blackCancelsTakeback">黑方取消悔棋</string>
<string name="yourOpponentProposesATakeback">對手請求悔棋</string>
<string name="bookmarkThisGame">收藏該棋局</string>
<string name="tournament">錦標賽</string>
@@ -698,6 +712,7 @@
<string name="downloadAnnotated">下載含有棋子走動方向的棋局</string>
<string name="downloadRaw">下載純文字</string>
<string name="downloadImported">下載導入的棋局</string>
<string name="downloadAllGames">下載所有棋局</string>
<string name="crosstable">歷程表</string>
<string name="youCanAlsoScrollOverTheBoardToMoveInTheGame">您也可以捲動棋盤以移動。</string>
<string name="scrollOverComputerVariationsToPreviewThem">將鼠標移到電腦分析變種上進行預覽</string>
@@ -733,6 +748,7 @@
<string name="anonymous">匿名用户</string>
<string name="yourScore">您的分數:%s</string>
<string name="language">語言</string>
<string name="allLanguages">所有語言</string>
<string name="background">背景</string>
<string name="light"></string>
<string name="dark"></string>
@@ -937,4 +953,10 @@
一切營運成本、開發和內容皆來自用戶之捐贈。</string>
<string name="nothingToSeeHere">目前這裡沒有什麼好看的。</string>
<string name="stats">統計</string>
<string name="copyToClipboard">複製到剪貼簿</string>
<string name="online" comment="status of a user">在線</string>
<string name="offline" comment="status of a user">離線</string>
<string name="search">搜索</string>
<string name="clearSearch">清除搜索</string>
<string name="tags">標籤</string>
</resources>
+1
View File
@@ -58,6 +58,7 @@
<string name="studyPgn">研究 PGN</string>
<string name="chapterPgn">章節PGN</string>
<string name="copyChapterPgn">複製PGN</string>
<string name="copyRawChapterPgn">複製純 PGN</string>
<string name="downloadGame">下載棋局</string>
<string name="studyUrl">研究連結</string>
<string name="currentChapterUrl">目前章節連結</string>
+2 -2
View File
@@ -42,10 +42,10 @@
<item quantity="other">%s 年前</item>
</plurals>
<plurals name="nbMinutesRemaining">
<item quantity="other">剩 %s 分钟</item>
<item quantity="other"> %s 分钟</item>
</plurals>
<plurals name="nbHoursRemaining">
<item quantity="other">剩 %s 小时</item>
<item quantity="other"> %s 小时</item>
</plurals>
<string name="completed">已完成</string>
</resources>
+3
View File
@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="community">社群</string>
<string name="myFriends">我的朋友</string>
<string name="myLikes" comment="A page which lists blog posts that you have pressed ❤️ Like on: https://lichess.org/blog/liked">我的點讚</string>
<string name="myBlog">我的部落格</string>
<string name="likedBlogs">已喜歡的部落格</string>
<plurals name="blogPosts">
+2 -1
View File
@@ -768,7 +768,8 @@
<string name="crosstable">Crosstable</string>
<string name="youCanAlsoScrollOverTheBoardToMoveInTheGame">Scroll over the board to move in the game.</string>
<string name="scrollOverComputerVariationsToPreviewThem">Scroll over computer variations to preview them.</string>
<string name="analysisShapesHowTo">Press shift+click or right-click to draw circles and arrows on the board.</string>
<string name="analysisShapesHowTo">Press right-click (or shift+click) to draw circles and arrows on the board. For other colours, combine the following with right-click:</string>
<string name="primaryColorArrowsHowTo">Ctrl or shift = red; command, alt, or meta = blue; a key from each = yellow.</string>
<string name="letOtherPlayersMessageYou">Let other players message you</string>
<string name="receiveForumNotifications">Receive notifications when mentioned in the forum</string>
<string name="shareYourInsightsData">Share your chess insights data</string>
+1
View File
@@ -16,6 +16,7 @@
<string name="recentlyUpdated">Recently updated</string>
<string name="mostPopular">Most popular</string>
<string name="alphabetical">Alphabetical</string>
<string name="relevant">Relevant</string>
<string name="addNewChapter">Add a new chapter</string>
<plurals name="nbChapters">
<item quantity="one">%s Chapter</item>
+1 -1
View File
@@ -120,7 +120,7 @@ async function splitConfig(cfgPath: string): Promise<SplitConfig[]> {
co.noImplicitReturns = false;
co.noUnusedParameters = false;
}
co.lib = co.lib?.map((lib: string) => /lib\.(.+)\.d\.ts/.exec(lib)?.[1] ?? lib);
co.lib = co.lib?.map((lib: string) => lib.match(/lib\.(.+)\.d\.ts/)?.[1] ?? lib);
co.pathsBasePath = undefined;
co.incremental = true;
config.compilerOptions = co;
+5 -1
View File
@@ -2895,7 +2895,7 @@ interface I18n {
analysis: string;
/** Analysis options */
analysisOptions: string;
/** Press shift+click or right-click to draw circles and arrows on the board. */
/** Press right-click (or shift+click) to draw circles and arrows on the board. For other colours, combine the following with right-click: */
analysisShapesHowTo: string;
/** and save %s premove lines */
andSaveNbPremoveLines: I18nPlural;
@@ -3917,6 +3917,8 @@ interface I18n {
practiceWithComputer: string;
/** Previously on Lichess TV */
previouslyOnLichessTV: string;
/** Ctrl or shift = red; command, alt, or meta = blue; a key from each = yellow. */
primaryColorArrowsHowTo: string;
/** Privacy */
privacy: string;
/** Privacy policy */
@@ -5059,6 +5061,8 @@ interface I18n {
readMoreAboutEmbedding: string;
/** Recently updated */
recentlyUpdated: string;
/** Relevant */
relevant: string;
/** Right under the board */
rightUnderTheBoard: string;
/** Save */
+9
View File
@@ -126,5 +126,14 @@ $col2-panel-height: 240px;
rgba(255, 255, 255, 0) 100%
);
overflow: hidden;
position: relative;
cursor: pointer;
}
.game-duration {
position: absolute;
bottom: 0;
right: 1.5em;
pointer-events: none;
font-family: Roboto, sans-serif;
}
+2 -1
View File
@@ -107,7 +107,8 @@
content: $licon-X;
}
[data-mode='ceval']::after {
content: $licon-Cpu;
content: $licon-Cogs;
color: $c-font-dimmer;
padding-inline-end: 10px;
}
[data-mode='ceval']:has(eval)::after {
+4
View File
@@ -648,6 +648,10 @@ export default class AnalyseCtrl implements CevalHandler {
this.redraw();
}
allowedEval(node: Tree.Node = this.node): Tree.ClientEval | Tree.ServerEval | false | undefined {
return (this.cevalEnabled() && node.ceval) || (this.showFishnetAnalysis() && node.eval);
}
outcome(node?: Tree.Node): Outcome | undefined {
return this.position(node || this.node).unwrap(
pos => pos.outcome(),
+2 -2
View File
@@ -162,14 +162,14 @@ export class InlineView {
?.map(g => this.glyphs[g.id - 1])
.filter(Boolean)
.forEach(cls => (classes[cls] = true));
return hl('move', { attrs: { p: path }, class: classes }, [
parentDisclose && this.disclosureBtn(parentNode, parentPath),
withIndex && renderIndex(node.ply, true),
renderMoveNodes(
node,
ctrl.showFishnetAnalysis() && isMainline && !this.inline,
isMainline && !this.inline,
(!!ctrl.study && !ctrl.study.relay) || ctrl.showFishnetAnalysis(),
ctrl.allowedEval(node) || false,
),
]);
}
+14 -3
View File
@@ -298,9 +298,20 @@ export const renderIndexAndMove = (node: Tree.Node, withEval: boolean, withGlyph
export const renderIndex = (ply: Ply, withDots: boolean): VNode =>
hl(`index.sbhint${ply}`, plyToTurn(ply) + (withDots ? (ply % 2 === 1 ? '.' : '...') : ''));
export function renderMoveNodes(node: Tree.Node, withEval: boolean, withGlyphs: boolean): LooseVNodes {
const ev = node.ceval ?? node.eval;
const evalText = ev?.cp !== undefined ? normalizeEval(ev.cp) : ev?.mate !== undefined ? `#${ev.mate}` : '';
export function renderMoveNodes(
node: Tree.Node,
withEval: boolean,
withGlyphs: boolean,
ev?: Tree.ClientEval | Tree.ServerEval | false,
): LooseVNodes {
ev ??= node.ceval ?? node.eval; // ev = false will override withEval
const evalText = !ev
? ''
: ev?.cp !== undefined
? normalizeEval(ev.cp)
: ev?.mate !== undefined
? `#${ev.mate}`
: '';
return [
hl('san', fixCrazySan(node.san!)),
withGlyphs && node.glyphs?.map(g => hl('glyph', { attrs: { title: g.name } }, g.symbol)),
+1 -1
View File
@@ -99,7 +99,7 @@ function renderPracticeTab(ctrl: AnalyseCtrl): LooseVNode {
function renderMobileCevalTab(ctrl: AnalyseCtrl): LooseVNode {
const engineMode = ctrl.activeControlMode() || 'ceval',
ev = ctrl.node.ceval ?? (ctrl.showFishnetAnalysis() ? ctrl.node.eval : undefined),
ev = ctrl.allowedEval() || undefined,
evalstr = ev?.cp !== undefined ? renderEval(ev.cp) : ev?.mate ? '#' + ev.mate : '',
active = ctrl.activeControlMode() && !ctrl.activeControlBarTool(),
latent = ctrl.activeControlMode() && !!ctrl.activeControlBarTool();
+1 -1
View File
@@ -28,7 +28,7 @@
"@types/zxcvbn": "^4.4.5",
"@yaireo/tagify": "4.17.9",
"canvas-confetti": "^1.9.3",
"cropperjs": "^1.6.2",
"cropperjs": "1.6.2",
"debounce-promise": "^3.1.2",
"emoji-mart": "^5.6.0",
"flatpickr": "^4.6.13",
+4
View File
@@ -137,7 +137,11 @@ type Composition = { boards: number; squares: Map<number, Map<Color, Map<co.Role
async function makeCover(polyglotBook: OpeningBook, boardSize: number, numMoves?: number): Promise<Blob> {
const squareSize = boardSize / 8;
if (typeof OffscreenCanvas !== 'function') throw 'no OffscreenCanvas support';
// eslint-disable-next-line compat/compat
const canvas = new OffscreenCanvas(boardSize, boardSize);
const ctx = canvas.getContext('2d')!;
const composition: Composition = { boards: 1, squares: new Map() };
+1 -1
View File
@@ -4,5 +4,5 @@
"skipLibCheck": true,
"noImplicitReturns": false
},
"references": [{ "path": "../lib/tsconfig.json" }, { "path": "../voice/tsconfig.json" }]
"references": [{ "path": "../lib/tsconfig.json" }]
}
+11
View File
@@ -206,6 +206,9 @@ export default async function (
},
},
};
if (moveCentis) addGameDuration(el, moveCentis);
const movetimeChart = new Chart(el, config) as PlyChart;
movetimeChart.selectPly = selectPly.bind(movetimeChart);
pubsub.on('ply', movetimeChart.selectPly);
@@ -213,6 +216,14 @@ export default async function (
return movetimeChart;
}
const addGameDuration = (el: HTMLCanvasElement, moveCentis: number[]) => {
const chart = $(el);
let label = chart.next('.game-duration');
if (!label.length) label = $('<div class="game-duration">').insertAfter(chart);
const duration = moveCentis.reduce((s, v) => s + v, 0);
label.text(i18n.site.duration + ' ' + formatClock(duration));
};
const toBlurArray = (player: Player) =>
player.blurs && player.blurs.bits ? player.blurs.bits.split('') : [];
+3
View File
@@ -22,6 +22,9 @@ export const highlightSearchTerm = (search: string, selector: string): void => {
}
if (ranges.length === 0) return;
// create a CSS highlight that can be styled with the ::highlight(search) pseudo-element
if (typeof Highlight !== 'function') throw 'no Highlight support';
// eslint-disable-next-line compat/compat
const highlight = new Highlight(...ranges);
CSS.highlights.set(highlightName, highlight);
};
+9 -8
View File
@@ -18,14 +18,15 @@ function primer() {
}
function acquire() {
navigator.wakeLock
?.request('screen')
.then(sentinel => {
wakeLock = sentinel;
primerEvents.forEach(e => window.removeEventListener(e, primer, { capture: true }));
primerEvents = [];
})
.catch(() => (wakeLock = null));
if ('wakeLock' in navigator)
navigator.wakeLock
?.request('screen')
.then(sentinel => {
wakeLock = sentinel;
primerEvents.forEach(e => window.removeEventListener(e, primer, { capture: true }));
primerEvents = [];
})
.catch(() => (wakeLock = null));
}
// safari will only grant wakeLock on a user interaction, and without that initial grant
+1 -1
View File
@@ -25,7 +25,7 @@
}
&::before {
@extend %data-icon;
font-size: 1.7em;
font-size: 32px;
color: $c-font-dimmer;
}
&--hook.button::before {
+1 -1
View File
@@ -4,7 +4,7 @@
"noEmit": true,
"module": "esnext",
"target": "es2020",
"lib": ["es2020", "webworker"],
"lib": ["es2020", "WebWorker"],
"types": []
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": { "noEmit": true, "isolatedDeclarations": false },
"references": [{ "path": "../lib/tsconfig.json" }, { "path": "../bits/tsconfig.json" }]
"references": [{ "path": "../lib/tsconfig.json" }]
}
+2 -2
View File
@@ -19,8 +19,8 @@
"libReplacement": false,
"moduleResolution": "Node",
"module": "ESNext",
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"target": "ES2021",
"lib": ["ES2021", "DOM", "DOM.Iterable"],
"types": ["lichess"],
"paths": {
"@/*": ["${configDir}/src/*"]