Skip to content

Commit

Permalink
Merge branch 'master' into fully-show-notation-on-refresh
Browse files Browse the repository at this point in the history
  • Loading branch information
johndoknjas authored Jan 21, 2025
2 parents 885bc05 + 84e2dd9 commit 95dbe52
Show file tree
Hide file tree
Showing 14 changed files with 126 additions and 40 deletions.
2 changes: 1 addition & 1 deletion app/controllers/Auth.scala
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ final class Auth(
lila.mon.user.register.confirmEmailResult(true).increment()
env.user.repo.email(user.id).flatMap {
_.so: email =>
authLog(user.username, email.some, s"Confirmed email ${email.value}")
authLog(user.username, email.some, s"Confirmed email")
welcome(user, email, sendWelcomeEmail = false)
} >> redirectNewUser(user)
}
Expand Down
31 changes: 16 additions & 15 deletions app/controllers/Study.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,24 @@ final class Study(

def search(text: String, page: Int) = OpenOrScopedBody(parse.anyContent)(_.Study.Read, _.Web.Mobile):
Reasonable(page):
if text.trim.isEmpty then
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
else
env
.studySearch(ctx.me)(text.take(100), page)
.flatMap: pag =>
negotiate(
Ok.page(views.study.list.search(pag, text)),
text.trim.some.filter(_.nonEmpty).filter(_.sizeIs > 2).filter(_.sizeIs < 200) 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) =>
env
.studySearch(ctx.me)(clean.take(100), page)
.flatMap: pag =>
negotiate(
Ok.page(views.study.list.search(pag, text)),
apiStudies(pag)
)

def homeLang = LangPage(routes.Study.allDefault())(allResults(Order.hot, 1))

Expand Down
2 changes: 1 addition & 1 deletion app/views/mod/inquiry.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ object inquiry:
)

def apply(in: lila.mod.Inquiry)(using ctx: Context) =
div(id := "inquiry")(
div(id := "inquiry", data("username") := in.user.user.username)(
i(title := "Costello the Inquiry Octopus", cls := "costello"),
div(cls := "meat")(
userLink(in.user.user, withPerfRating = in.user.perfs.some, params = "?mod"),
Expand Down
2 changes: 1 addition & 1 deletion modules/common/src/main/HTTPRequest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ object HTTPRequest:

private val crawlerMatcher = UaMatcher:
// spiders/crawlers
"""Googlebot|AdsBot|Google-Read-Aloud|bingbot|BingPreview|facebookexternalhit|meta-externalagent|SemrushBot|AhrefsBot|PetalBot|Applebot|YandexBot|YandexAdNet|YandexImages|Twitterbot|Baiduspider|Amazonbot|Bytespider|yacybot|ImagesiftBot|ChatGLM-Spider|YisouSpider""" +
"""Googlebot|GoogleOther|AdsBot|Google-Read-Aloud|bingbot|BingPreview|facebookexternalhit|meta-externalagent|SemrushBot|AhrefsBot|PetalBot|Applebot|YandexBot|YandexAdNet|YandexImages|Twitterbot|Baiduspider|Amazonbot|Bytespider|yacybot|ImagesiftBot|ChatGLM-Spider|YisouSpider|Yeti/""" +
// http libs
"""|HeadlessChrome|okhttp|axios|wget|curl|python-requests|aiohttp|commons-httpclient|python-urllib|python-httpx|Nessus"""

Expand Down
20 changes: 10 additions & 10 deletions ui/.build/src/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export async function hash(): Promise<void> {
pkg.hash.map(async hash =>
(await globArray(hash.glob, { cwd: env.outDir })).map(path => ({
path,
replace: hash.replace,
update: hash.update,
root: pkg.root,
})),
),
Expand All @@ -40,22 +40,22 @@ export async function hash(): Promise<void> {
for (const key of Object.keys(env.manifest.hashed)) {
if (!hashed.some(x => x.path.endsWith(key))) delete env.manifest.hashed[key];
}
// TODO find a better home for all of this
const replaceMany: Map<string, { root: string; mapping: Record<string, string> }> = new Map();
for (const { root, path, replace } of hashed) {
if (!replace) continue;
const replaceInOne = replaceMany.get(replace) ?? { root, mapping: {} };

const updates: Map<string, { root: string; mapping: Record<string, string> }> = new Map();
for (const { root, path, update } of hashed) {
if (!update) continue;
const updateFile = updates.get(update) ?? { root, mapping: {} };
const from = path.slice(env.outDir.length + 1);
replaceInOne.mapping[from] = asHashed(from, env.manifest.hashed[from].hash!);
replaceMany.set(replace, replaceInOne);
updateFile.mapping[from] = asHashed(from, env.manifest.hashed[from].hash!);
updates.set(update, updateFile);
}
for await (const { name, hash } of [...replaceMany].map(([n, r]) => replaceAllIn(n, r.root, r.mapping))) {
for await (const { name, hash } of [...updates].map(([n, r]) => update(n, r.root, r.mapping))) {
env.manifest.hashed[name] = { hash };
}
updateManifest({ dirty: true });
}

async function replaceAllIn(name: string, root: string, files: Record<string, string>) {
async function update(name: string, root: string, files: Record<string, string>) {
const result = Object.entries(files).reduce(
(data, [from, to]) => data.replaceAll(from, to),
await fs.promises.readFile(path.join(root, name), 'utf8'),
Expand Down
2 changes: 1 addition & 1 deletion ui/.build/src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface Package {
name: string; // dirname of package root
pkg: any; // the entire package.json object
bundle: { module?: string; inline?: string }[]; // TODO doc
hash: { glob: string; replace?: string }[]; // TODO doc
hash: { glob: string; update?: string }[]; // TODO doc
sync: Sync[]; // pre-bundle filesystem copies from package json
}

Expand Down
19 changes: 12 additions & 7 deletions ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,26 +127,31 @@ Web asset distribution involves the caching of URLs, and hashes provide a repeat

ui/build calculates and writes all hashes used to determine asset URLs to a manifest.\*.json file. This file is used by the lila server to tell browsers what they need. Javascript and css assets built from lichess sources are hashed automatically, but the "build" / "hash" section within package.json describes assets that must be hashed separately. These include images, fonts, and packages of js & css from the npmjs repository that we don't compile but must be exposed through our content distribution strategy.

Because these unmanaged assets originate in or are copied to the /public folder during the build process, all paths within the "hash" property resolve relative to /public.
"build" / "hash" may contain a single entry or an array of entries. an entry may be a bare string glob or filename relative to /public. It may also be an object with a "glob" property (relative to /public) and an optional "update" filename property relative to /ui/\<package>. More on this below.

ui/build computes a sha256 checksum of each matched asset's content, using a portion of that checksum to make a hash, then creates a symlink with that hash in the name pointing back to the original file. All links are created within /public/hashed. When a source file's content changes on the filesystem, its corresponding symlink will get a new name. This changes the object's URL and forces our CDN to create a fresh cache entry that will propagate through their edge server caches in distribution. Once again, lila is kept informed by ui/build through entries it writes to manifest.\*.json.
ui/build computes a sha256 checksum of each matched asset's content and creates a symlink named with a hash from that checksum within /public/hashed. The symlink points back to the original file (somewhere in /public). When an asset's content changes on the filesystem, its corresponding symlink will get a new name. This changes the object's URL and forces our CDN to create a fresh cache entry that will propagate through their edge server caches in distribution. Once again, lila is kept informed by ui/build through entries it writes to manifest.\*.json.

```json
"hash": [
"font/lichess.woff",
"font/lichess.woff2",
"lifat/background/montage*.webp",
"npm/*",
"javascripts/**",
"piece-css/*"
]
```

Above we see the hash property in [/ui/site/package.json](./site/package.json) where we match the listed web fonts as well as files in lifat/background that start with montage and end with .webp. The npm/\* glob requests hashes for all top level files in public/npm, and at this point it's helpful to note that matched assets are hashed during manifest creation AFTER "sync" operations have completed for all packages.
Note that matched assets are hashed during manifest creation AFTER "sync" operations have completed for all packages. In the ceval/package.json example we saw where stockfish wasms are synced to /public/npm. Here in site/package.json, those synced stockfish wasms are subsequently matched by the npm/* glob and symlinked in /public/hashed for efficient distribution.

In the ceval/package.json example we saw where stockfish wasms are synced to /public/npm. Here in site/package.json, those synced stockfish wasms are subsequently matched by globs at their copy destinations and symlinked in /public/hashed for efficient distribution. [/ui/site/package.json](./site/package.json) is where we hash unmanaged assets that don't really fit anywhere else.
"build" / "hash" entires that provide a filename for the "update" property will first process the "glob" property, creating symlinks in /public/hashed (same as before). Afterwards, the "update" file is transformed with all occurrence of globbed sources replaced by their hashed symlinks. The updated file is then itself content-hashed, written to /public/hashed, and remapped from its original path in manifest.json. This is useful when an asset references other files by name and those references must be updated to reflect the hashed URLs. For example: [/ui/common/css/theme/font-face.css](./common/css/theme/font-face.css) is transformed via this hash entry from [/ui/common/package.json](./common/package.json):

The double asterisk in the javascripts/\*\* glob match everything inside its folder hierarchy.
```json
"hash": [
{
"glob": "font/*.woff2",
"update": "css/theme/font-face.css"
}
]
```

And that's about it for package.json. The nodejs sources for ui/build script are in the [/ui/.build](./.build) folder. Have a glance if something goes wrong or you have questions beyond the scope of this readme.

Expand Down
2 changes: 1 addition & 1 deletion ui/bits/css/relay/_card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
&__title {
@include line-clamp(2);
margin: 0.2em 0 0.3em 0;
font-weight: bold;
font-weight: normal;
color: $c-font-clearer;
font-size: 1.3em;
.relay-cards--tier-best & {
Expand Down
2 changes: 1 addition & 1 deletion ui/common/css/component/_mini-game.scss
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
}

&__result {
font-weight: normal;
font-weight: bold;
margin: 0 1ch;
}
}
2 changes: 1 addition & 1 deletion ui/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"build": {
"hash": {
"glob": "font/*.woff2",
"replace": "css/theme/font-face.css"
"update": "css/theme/font-face.css"
}
}
}
68 changes: 68 additions & 0 deletions ui/common/src/highlight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* Ported to typescript from https://github.com/marmelab/highlight-search-term/blob/main/src/index.js */

export const highlightSearchTerm = (search: string, selector: string): void => {
const highlightName = 'lichess-highlight';

console.log(search, CSS.highlights, highlightName);

if (!CSS.highlights) return; // disable feature on Firefox as it does not support CSS Custom Highlight API

// remove previous highlight
CSS.highlights.delete(highlightName);
if (!search) {
// nothing to highlight
return;
}
// find all text nodes containing the search term
const ranges: AbstractRange[] = [];
try {
const elements = document.querySelectorAll<HTMLElement>(selector);
Array.from(elements).map(element => {
getTextNodesInElementContainingText(element, search).forEach(node => {
node.parentElement && ranges.push(...getRangesForSearchTermInElement(node.parentElement, search));
});
});
} catch (error) {
console.error(error);
}
if (ranges.length === 0) return;
// create a CSS highlight that can be styled with the ::highlight(search) pseudo-element
const highlight = new Highlight(...ranges);
CSS.highlights.set(highlightName, highlight);
};

const getTextNodesInElementContainingText = (element: HTMLElement, text: string) => {
const lowerCaseText = text.toLowerCase();
const nodes = [];
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
let node;
while ((node = walker.nextNode())) {
if (node.textContent?.toLowerCase().includes(lowerCaseText)) {
nodes.push(node);
}
}
return nodes;
};

const getRangesForSearchTermInElement = (element: HTMLElement, search: string) => {
const ranges: AbstractRange[] = [];
const lowerCaseSearch = search.toLowerCase();
if (element.childNodes.length === 0) return ranges;
// In some frameworks like React, when combining static text with dynamic text, the element may have multiple Text child nodes.
// To avoid errors, we must find the child node that actually contains the search term.
const childWithSearchTerm = Array.from(element.childNodes).find(node =>
node.textContent?.toLowerCase().includes(lowerCaseSearch),
);
if (!childWithSearchTerm) return ranges;
const text = childWithSearchTerm.textContent?.toLowerCase() || '';
let start = 0;
let index;
while ((index = text.indexOf(lowerCaseSearch, start)) >= 0) {
const range = new Range();
range.setStart(childWithSearchTerm, index);
range.setEnd(childWithSearchTerm, index + search.length);
ranges.push(range);
start = index + search.length;
}
return ranges;
};
1 change: 1 addition & 0 deletions ui/lobby/css/_timeline.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

a {
@extend %base-font, %page-font;
font-weight: normal;
}

&:hover a {
Expand Down
5 changes: 5 additions & 0 deletions ui/mod/css/_inquiry.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ body.no-inquiry {
}
}

*::highlight(lichess-highlight) {
background-color: yellow;
color: black;
}

#inquiry {
height: 48px;
background: #484541;
Expand Down
8 changes: 7 additions & 1 deletion ui/mod/src/mod.inquiry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { formToXhr } from 'common/xhr';
import { expandMentions } from 'common/richText';
import { storage } from 'common/storage';
import { alert } from 'common/dialog';
import { highlightSearchTerm } from 'common/highlight';
import { pubsub } from 'common/pubsub';

site.load.then(() => {
const noteStore = storage.make('inquiry-note');
const usernameNoteStore = storage.make('inquiry-note-user');
const username = $('#inquiry .meat > .user-link').text().split(' ')[0];
const username = $('#inquiry').data('username');
if (username !== usernameNoteStore.get()) noteStore.remove();
usernameNoteStore.set(username);
const noteTextArea = $('#inquiry .notes').find('textarea')[0] as HTMLTextAreaElement;
Expand Down Expand Up @@ -114,4 +116,8 @@ site.load.then(() => {
const username = $(this).parents('tr').find('td:first-child .user-link').text().split(' ')[0];
addToNote(`Alt: @${username}`);
});

const highlightUsername = () => highlightSearchTerm(username, '#main-wrap .user-link');
setTimeout(highlightUsername, 300);
pubsub.on('content-loaded', highlightUsername);
});

0 comments on commit 95dbe52

Please sign in to comment.