chesspal 0.2.4 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -12
- package/dist/cli/cli.js +83 -3
- package/dist/cli/command-line.js +26 -9
- package/dist/cli/icon.d.ts +25 -0
- package/dist/cli/icon.js +80 -0
- package/dist/commands/agents.d.ts +19 -2
- package/dist/commands/agents.js +39 -13
- package/dist/commands/ai-say.js +3 -4
- package/dist/commands/analyse-game.d.ts +4 -0
- package/dist/commands/analyse-game.js +46 -13
- package/dist/commands/annotate-game.d.ts +2 -0
- package/dist/commands/annotate-game.js +40 -18
- package/dist/commands/answer-lines.d.ts +2 -0
- package/dist/commands/answer-lines.js +13 -3
- package/dist/commands/coach.js +4 -2
- package/dist/commands/context.d.ts +11 -1
- package/dist/commands/context.js +5 -0
- package/dist/commands/delete-review.js +9 -5
- package/dist/commands/game-summary.d.ts +2 -0
- package/dist/commands/game-summary.js +4 -0
- package/dist/commands/index.d.ts +3 -2
- package/dist/commands/index.js +7 -4
- package/dist/commands/init.js +2 -4
- package/dist/commands/instructions.d.ts +9 -0
- package/dist/commands/instructions.js +30 -0
- package/dist/commands/lead-input.d.ts +1 -1
- package/dist/commands/lead-input.js +7 -4
- package/dist/commands/page-link.js +6 -5
- package/dist/commands/past-games.d.ts +1 -1
- package/dist/commands/past-games.js +12 -5
- package/dist/commands/player-move.js +2 -3
- package/dist/commands/profiles.d.ts +23 -9
- package/dist/commands/profiles.js +76 -35
- package/dist/commands/ratings.d.ts +8 -0
- package/dist/commands/ratings.js +45 -0
- package/dist/commands/registry.d.ts +1 -1
- package/dist/commands/registry.js +3 -2
- package/dist/commands/say.d.ts +1 -1
- package/dist/commands/say.js +1 -1
- package/dist/commands/status.d.ts +3 -1
- package/dist/commands/status.js +46 -27
- package/dist/commands/study.js +8 -5
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/review.d.ts +2 -0
- package/dist/core/review.js +5 -1
- package/dist/games/ai-model.d.ts +4 -2
- package/dist/games/ai-model.js +10 -1
- package/dist/games/analysis.d.ts +26 -3
- package/dist/games/analysis.js +41 -6
- package/dist/games/archive.d.ts +6 -3
- package/dist/games/archive.js +13 -5
- package/dist/games/current-game.d.ts +10 -1
- package/dist/games/current-game.js +49 -15
- package/dist/games/files.d.ts +5 -0
- package/dist/games/files.js +42 -1
- package/dist/games/index.d.ts +3 -3
- package/dist/games/index.js +3 -3
- package/dist/home/data-folder.d.ts +2 -0
- package/dist/home/data-folder.js +5 -1
- package/dist/home/index.d.ts +1 -1
- package/dist/home/index.js +1 -1
- package/dist/host-files/agents.d.ts +2 -2
- package/dist/host-files/agents.js +12 -6
- package/dist/host-files/host-files.d.ts +5 -3
- package/dist/host-files/host-files.js +15 -8
- package/dist/host-files/index.d.ts +2 -2
- package/dist/host-files/index.js +2 -2
- package/dist/page/app/app.js +49 -20
- package/dist/page/app/title.js +17 -0
- package/dist/page/avatars/torretta.js +4 -4
- package/dist/page/dialogs/analyses.js +4 -4
- package/dist/page/dialogs/dialog.js +4 -1
- package/dist/page/dialogs/dialogs.css +84 -10
- package/dist/page/dialogs/profiles.js +96 -43
- package/dist/page/game/column.js +5 -1
- package/dist/page/game/plies.js +7 -4
- package/dist/page/guide/blocks.js +67 -0
- package/dist/page/guide/en.md +66 -0
- package/dist/page/guide/foot.js +18 -0
- package/dist/page/guide/guide.css +200 -0
- package/dist/page/guide/guide.js +89 -0
- package/dist/page/guide/it.md +66 -0
- package/dist/page/guide/markdown.js +67 -0
- package/dist/page/helps/helps.css +7 -10
- package/dist/page/layout/favicon.svg +13 -0
- package/dist/page/layout/header.js +3 -3
- package/dist/page/layout/icon.icns +0 -0
- package/dist/page/layout/icon.ico +0 -0
- package/dist/page/layout/icon.png +0 -0
- package/dist/page/layout/layout.css +85 -4
- package/dist/page/layout/window.js +45 -0
- package/dist/page/main.js +4 -1
- package/dist/page/players/card.js +1 -1
- package/dist/page/profile/profile.js +14 -9
- package/dist/page/profile/settings.js +9 -0
- package/dist/page/style.css +2 -0
- package/dist/page/table/table.css +123 -0
- package/dist/page/table/window.js +130 -0
- package/dist/page/talk/chat.css +23 -5
- package/dist/page/talk/talk.css +38 -26
- package/dist/page/talk/talk.js +6 -4
- package/dist/page/talk/voice.js +35 -9
- package/dist/page/text.js +131 -17
- package/dist/page/theme.css +7 -0
- package/dist/profiles/index.d.ts +2 -1
- package/dist/profiles/index.js +2 -1
- package/dist/profiles/profiles.d.ts +10 -15
- package/dist/profiles/profiles.js +30 -15
- package/dist/profiles/ratings.d.ts +35 -0
- package/dist/profiles/ratings.js +61 -0
- package/dist/server/bell.d.ts +24 -3
- package/dist/server/bell.js +84 -10
- package/dist/server/caller.d.ts +2 -1
- package/dist/server/caller.js +3 -2
- package/dist/server/mcp.d.ts +7 -3
- package/dist/server/mcp.js +100 -52
- package/dist/server/page/analyses.d.ts +2 -0
- package/dist/server/page/analyses.js +20 -5
- package/dist/server/page/attach.d.ts +4 -0
- package/dist/server/page/attach.js +112 -0
- package/dist/server/page/chat.d.ts +65 -0
- package/dist/server/page/chat.js +87 -0
- package/dist/server/page/events.d.ts +7 -0
- package/dist/server/page/events.js +20 -0
- package/dist/server/page/files.js +10 -1
- package/dist/server/page/game.js +43 -23
- package/dist/server/page/page.d.ts +51 -7
- package/dist/server/page/page.js +74 -48
- package/dist/server/page/profiles.d.ts +5 -0
- package/dist/server/page/profiles.js +26 -10
- package/dist/server/page/table.d.ts +58 -0
- package/dist/server/page/table.js +210 -0
- package/dist/server/table/browser.d.ts +6 -0
- package/dist/server/table/browser.js +48 -0
- package/dist/server/table/hosts/chats.d.ts +7 -0
- package/dist/server/table/hosts/chats.js +34 -0
- package/dist/server/table/hosts/find.d.ts +2 -0
- package/dist/server/table/hosts/find.js +20 -0
- package/dist/server/table/hosts/hosts.d.ts +34 -0
- package/dist/server/table/hosts/hosts.js +49 -0
- package/dist/server/table/hosts/index.d.ts +3 -0
- package/dist/server/table/hosts/index.js +4 -0
- package/dist/server/table/hosts/play.d.ts +29 -0
- package/dist/server/table/hosts/play.js +180 -0
- package/dist/server/table/hosts/seat.d.ts +33 -0
- package/dist/server/table/hosts/seat.js +80 -0
- package/dist/server/table/hosts/terminal.d.ts +6 -0
- package/dist/server/table/hosts/terminal.js +63 -0
- package/dist/server/table/icon/index.d.ts +32 -0
- package/dist/server/table/icon/index.js +83 -0
- package/dist/server/table/icon/linux.d.ts +5 -0
- package/dist/server/table/icon/linux.js +47 -0
- package/dist/server/table/icon/macos.d.ts +3 -0
- package/dist/server/table/icon/macos.js +38 -0
- package/dist/server/table/icon/windows.d.ts +3 -0
- package/dist/server/table/icon/windows.js +48 -0
- package/dist/server/table/index.d.ts +6 -0
- package/dist/server/table/index.js +7 -0
- package/dist/server/table/launch.d.ts +19 -0
- package/dist/server/table/launch.js +78 -0
- package/dist/server/table/link.d.ts +10 -0
- package/dist/server/table/link.js +231 -0
- package/dist/server/table/table.d.ts +35 -0
- package/dist/server/table/table.js +94 -0
- package/dist/sites/chesscom.d.ts +4 -0
- package/dist/sites/chesscom.js +25 -0
- package/dist/sites/choice.d.ts +17 -0
- package/dist/sites/choice.js +13 -0
- package/dist/sites/index.d.ts +2 -0
- package/dist/sites/index.js +4 -0
- package/dist/sites/lichess.d.ts +4 -0
- package/dist/sites/lichess.js +32 -0
- package/dist/sites/request.d.ts +11 -0
- package/dist/sites/request.js +15 -0
- package/dist/sites/sites.d.ts +6 -0
- package/dist/sites/sites.js +33 -0
- package/instructions/analysis.md +1 -1
- package/instructions/analyst-agent.md +1 -1
- package/instructions/coach.md +2 -1
- package/instructions/opponent.md +6 -6
- package/instructions/ponder.md +1 -0
- package/instructions/start.md +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// The table's window the first time it opens (D-104): Chrome and Edge open an application window about 1000 px wide, where the page has two columns. The
|
|
2
|
+
// page asks once for 16:9, 1600×900 or what the screen has, centred; after that the browser keeps the size the player gives it (Zoe).
|
|
3
|
+
// The mark the table puts on the address of the window it opens (server/table/browser.ts).
|
|
4
|
+
const mark = "opened";
|
|
5
|
+
const sizedKey = "chesspal-window-sized";
|
|
6
|
+
const wanted = { width: 1600, height: 900 };
|
|
7
|
+
/** Sizes the window the table opened, once in this browser, and takes the mark off the address. */
|
|
8
|
+
export function fitOpenedWindow() {
|
|
9
|
+
const address = new URL(location.href);
|
|
10
|
+
if (!address.searchParams.has(mark)) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
address.searchParams.delete(mark);
|
|
14
|
+
history.replaceState(history.state, "", address.pathname + address.search + address.hash);
|
|
15
|
+
if (sizedBefore()) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const place = windowPlace(screen.availWidth, screen.availHeight);
|
|
19
|
+
window.resizeTo(place.width, place.height);
|
|
20
|
+
window.moveTo(place.left, place.top);
|
|
21
|
+
noteSized();
|
|
22
|
+
}
|
|
23
|
+
/** Where the window goes on a screen with this much room: 1600×900, or less on a smaller screen, centred. */
|
|
24
|
+
export function windowPlace(roomWidth, roomHeight) {
|
|
25
|
+
const width = Math.min(wanted.width, roomWidth);
|
|
26
|
+
const height = Math.min(wanted.height, roomHeight);
|
|
27
|
+
return { width, height, left: Math.round((roomWidth - width) / 2), top: Math.round((roomHeight - height) / 2) };
|
|
28
|
+
}
|
|
29
|
+
function sizedBefore() {
|
|
30
|
+
try {
|
|
31
|
+
return localStorage.getItem(sizedKey) !== null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// A browser that keeps nothing for the page: sizing it at every opening is the lesser harm.
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function noteSized() {
|
|
39
|
+
try {
|
|
40
|
+
localStorage.setItem(sizedKey, "1");
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Nothing kept: the next opening sizes the window again.
|
|
44
|
+
}
|
|
45
|
+
}
|
package/dist/page/main.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx } from "preact/jsx-runtime";
|
|
2
|
-
// The page's start: the settings this browser remembers, shown before the first frame, then the app
|
|
2
|
+
// The page's start: the window the table opened sized the first time, the settings this browser remembers, shown before the first frame, then the app
|
|
3
|
+
// drawn into its place.
|
|
3
4
|
import { render } from "preact";
|
|
4
5
|
import { App } from "./app/app.js";
|
|
6
|
+
import { fitOpenedWindow } from "./layout/window.js";
|
|
5
7
|
import { loadSettings, showSettings } from "./profile/settings.js";
|
|
8
|
+
fitOpenedWindow();
|
|
6
9
|
const settings = loadSettings();
|
|
7
10
|
showSettings(settings);
|
|
8
11
|
render(_jsx(App, { start: settings }), document.getElementById("app"));
|
|
@@ -8,7 +8,7 @@ export function PlayerCard({ place, picture, name, signed, side, level, taken, a
|
|
|
8
8
|
const opponent = side === "white" ? "black" : "white";
|
|
9
9
|
return (_jsx("section", { class: `card player ${place}`, children: _jsxs("div", { class: "player-line", children: [picture, _jsxs("div", { class: "who", children: [_jsx("div", { class: "name", title: typeof name === "string" ? name : undefined, children: name }), signed === undefined ? null : _jsx("div", { class: "signed", title: signed, children: signed }), side === undefined ? null : (_jsxs("div", { class: "note", children: [text.sides[side], level === undefined ? null : (_jsxs(_Fragment, { children: [" ", "\u00B7 ", _jsx(LevelTip, { level: level })] })), turn
|
|
10
10
|
? (_jsxs(_Fragment, { children: [" ", "\u00B7 ", _jsx("b", { class: "turn", children: text.yourTurn })] }))
|
|
11
|
-
: null] })), _jsxs("div", { class: "taken", role: "img", "aria-label": text.taken(taken.map((piece) => text.piece(piece, opponent)), ahead), children: [groupsOf(taken).map((group) => (_jsx("span", { class: group[0]?.toUpperCase() === "P" ? "group pawns" : "group", children: group.map((piece, index) => _jsx(Piece, { letter: opponent === "white" ? piece : piece.toLowerCase(), small: true }, index)) }, group[0]))), ahead > 0 ? _jsxs("span", { class: "balance", children: ["+", ahead] }) : null] }), badges] })
|
|
11
|
+
: null] }))] }), clock, side === undefined && taken.length === 0 && badges === undefined ? null : (_jsxs("div", { class: "haul", children: [_jsxs("div", { class: "taken", role: "img", "aria-label": text.taken(taken.map((piece) => text.piece(piece, opponent)), ahead), children: [groupsOf(taken).map((group) => (_jsx("span", { class: group[0]?.toUpperCase() === "P" ? "group pawns" : "group", children: group.map((piece, index) => _jsx(Piece, { letter: opponent === "white" ? piece : piece.toLowerCase(), small: true }, index)) }, group[0]))), ahead > 0 ? _jsxs("span", { class: "balance", children: ["+", ahead] }) : null] }), badges] }))] }) }));
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* A side's time on the game's clock, as the server keeps it: it runs while the side is to move and the game goes on, unless `held` (a move on its way);
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
// The player's profile on the page (D-23, D-45): as the server sends it, the choices the wizard offers, and the requests that change it.
|
|
2
2
|
/** How the player says they play, as the server keeps it. */
|
|
3
3
|
export const levels = ["learning", "casual", "regular", "tournament"];
|
|
4
|
-
/**
|
|
5
|
-
export const ratingSites = ["lichess", "chess.com"
|
|
6
|
-
/** The
|
|
7
|
-
export const
|
|
4
|
+
/** The sites a rating is read from, by the player's names there (D-105). */
|
|
5
|
+
export const ratingSites = ["lichess", "chess.com"];
|
|
6
|
+
/** The speeds whose rating counts, in the order they count (D-106). */
|
|
7
|
+
export const speeds = ["rapid", "classical", "blitz"];
|
|
8
8
|
/** The longest name the server keeps. */
|
|
9
9
|
export const nameLimit = 40;
|
|
10
|
+
/** The FIDE ratings the server takes, as whole numbers (D-106). */
|
|
11
|
+
export const fideLowest = 1400;
|
|
12
|
+
export const fideHighest = 3000;
|
|
10
13
|
/** The drawn avatars, by the ids the server keeps (D-45). */
|
|
11
14
|
export const avatarIds = ["short-hair", "bun", "glasses", "beanie", "beard", "curls", "moustache", "silver-bob", "cat", "owl"];
|
|
12
15
|
/** The address of a profile's own image: it changes with the image. */
|
|
@@ -14,12 +17,14 @@ export function imageAddress(profile) {
|
|
|
14
17
|
return profile.image === undefined ? undefined : `/api/profile/image?profile=${encodeURIComponent(profile.id)}&v=${encodeURIComponent(profile.image)}`;
|
|
15
18
|
}
|
|
16
19
|
/** Makes a profile, in use unless `use` is false, with the photo after it, made small first so that a photo that cannot be read makes no profile: the refusal, or undefined. */
|
|
17
|
-
export async function makeProfile({ name, level,
|
|
20
|
+
export async function makeProfile({ name, level, lichess, chesscom, fide, avatar, photo, use }) {
|
|
18
21
|
const image = photo === undefined ? undefined : await shrunk(photo).catch(() => undefined);
|
|
19
22
|
if (photo !== undefined && image === undefined) {
|
|
20
23
|
return badImage;
|
|
21
24
|
}
|
|
22
|
-
const
|
|
25
|
+
const names = { ...(lichess?.trim() ? { lichess: lichess.trim() } : {}), ...(chesscom?.trim() ? { chesscom: chesscom.trim() } : {}) };
|
|
26
|
+
const chosen = { ...(fide === undefined ? {} : { fide }), ...(avatar === undefined ? {} : { avatar }), ...(use === false ? { use } : {}) };
|
|
27
|
+
const answer = await post("/api/profile/new", jsonOf({ name: name.trim(), level, ...names, ...chosen }));
|
|
23
28
|
if ("refused" in answer) {
|
|
24
29
|
return answer.refused;
|
|
25
30
|
}
|
|
@@ -33,9 +38,9 @@ export async function useProfile(id) {
|
|
|
33
38
|
export async function chooseAvatar(profile, avatar) {
|
|
34
39
|
return refusalOf(await post(forProfile("/api/profile/avatar", profile), jsonOf({ avatar })));
|
|
35
40
|
}
|
|
36
|
-
/**
|
|
37
|
-
export async function
|
|
38
|
-
return refusalOf(await post(forProfile("/api/profile/
|
|
41
|
+
/** What the player changes in the profile the page shows: an empty level or site name removed. The server's refusal, or undefined. */
|
|
42
|
+
export async function editProfile(profile, changes) {
|
|
43
|
+
return refusalOf(await post(forProfile("/api/profile/edit", profile), jsonOf(changes)));
|
|
39
44
|
}
|
|
40
45
|
/** Opens the data folder in the system's file manager, where the page runs: the server's refusal, or undefined. */
|
|
41
46
|
export async function openDataFolder() {
|
|
@@ -30,6 +30,11 @@ export function settingsFrom(value, defaults = { theme: "system", sound: true })
|
|
|
30
30
|
sound: typeof stored.sound === "boolean" ? stored.sound : defaults.sound,
|
|
31
31
|
voice: typeof stored.voice === "boolean" ? stored.voice : false,
|
|
32
32
|
language: oneOf(stored.language, ["auto", "it", "en"], "auto"),
|
|
33
|
+
// Checked against what the host offers when the chat opens, which this page does not know.
|
|
34
|
+
claudeModel: word(stored.claudeModel),
|
|
35
|
+
claudeEffort: word(stored.claudeEffort),
|
|
36
|
+
codexModel: word(stored.codexModel),
|
|
37
|
+
codexEffort: word(stored.codexEffort),
|
|
33
38
|
};
|
|
34
39
|
}
|
|
35
40
|
/** Remembers the settings in this browser. */
|
|
@@ -75,3 +80,7 @@ function parsed(saved) {
|
|
|
75
80
|
function oneOf(value, choices, fallback) {
|
|
76
81
|
return choices.find((choice) => choice === value) ?? fallback;
|
|
77
82
|
}
|
|
83
|
+
// A host's model or effort, a short word; empty when it is none.
|
|
84
|
+
function word(value) {
|
|
85
|
+
return typeof value === "string" && /^[\w.-]{1,40}$/.test(value) ? value : "";
|
|
86
|
+
}
|
package/dist/page/style.css
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
@import "board/board.css";
|
|
6
6
|
@import "talk/talk.css";
|
|
7
7
|
@import "talk/chat.css";
|
|
8
|
+
@import "table/table.css";
|
|
8
9
|
@import "game/game.css";
|
|
9
10
|
@import "helps/helps.css";
|
|
10
11
|
@import "legend/legend.css";
|
|
11
12
|
@import "dialogs/dialogs.css";
|
|
13
|
+
@import "guide/guide.css";
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/* The chat's window (D-107): narrow, a row for each host, its button with the tick of the one at the table and its model and effort under their titles,
|
|
2
|
+
their columns shared through the subgrid; the command to type instead */
|
|
3
|
+
.dialog:has(> .chat-hosts) {
|
|
4
|
+
width: min(460px, calc(100vw - 32px));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.chat-hosts {
|
|
8
|
+
display: grid;
|
|
9
|
+
grid-template-columns: minmax(0, 1fr) auto auto;
|
|
10
|
+
gap: 6px;
|
|
11
|
+
margin: 8px 0 12px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.chat-head,
|
|
15
|
+
.chat-host {
|
|
16
|
+
display: grid;
|
|
17
|
+
grid-column: 1 / -1;
|
|
18
|
+
grid-template-columns: subgrid;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.chat-head {
|
|
22
|
+
color: var(--soft);
|
|
23
|
+
font-size: 12px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.chat-host .choice {
|
|
27
|
+
min-width: 0;
|
|
28
|
+
font-size: 15px;
|
|
29
|
+
text-align: left;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.chat-host .choice span {
|
|
33
|
+
flex: 1;
|
|
34
|
+
min-width: 0;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
text-overflow: ellipsis;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.chat-host .in-use {
|
|
41
|
+
flex: none;
|
|
42
|
+
width: 18px;
|
|
43
|
+
height: 18px;
|
|
44
|
+
color: var(--accent);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.chat-host .choice[aria-disabled="true"] {
|
|
48
|
+
border-color: var(--line);
|
|
49
|
+
opacity: 0.6;
|
|
50
|
+
cursor: default;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.chat-host select {
|
|
54
|
+
padding: 4px;
|
|
55
|
+
border: 1px solid var(--line);
|
|
56
|
+
border-radius: 6px;
|
|
57
|
+
background: var(--card);
|
|
58
|
+
color: var(--ink);
|
|
59
|
+
font: inherit;
|
|
60
|
+
font-size: 13px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Empty, the state takes no room */
|
|
64
|
+
.chat-state:empty {
|
|
65
|
+
display: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.chat-command {
|
|
69
|
+
color: var(--soft);
|
|
70
|
+
font-size: 12.5px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Each command whole on its line; two families, so the browser does not shrink the monospace */
|
|
74
|
+
.chat-command code {
|
|
75
|
+
font-family: ui-monospace, monospace;
|
|
76
|
+
white-space: nowrap;
|
|
77
|
+
user-select: all;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* The note on a start again that waits, with its ↻ and ×; in a game with no chat at the table, a line that opens the chat's window */
|
|
81
|
+
.table-note {
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
gap: 6px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.table-note > span {
|
|
88
|
+
flex: 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.table-note .icon-button {
|
|
92
|
+
flex: none;
|
|
93
|
+
width: 30px;
|
|
94
|
+
height: 30px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.over-board > .table-note button {
|
|
98
|
+
pointer-events: auto;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.no-chat-line {
|
|
102
|
+
display: inline-flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
padding: 0;
|
|
105
|
+
border: 0;
|
|
106
|
+
background: none;
|
|
107
|
+
color: inherit;
|
|
108
|
+
font: inherit;
|
|
109
|
+
text-align: left;
|
|
110
|
+
text-decoration: underline;
|
|
111
|
+
text-underline-offset: 3px;
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* No chat at the table: a hollow ring before the AI's name, and before the line that says so */
|
|
116
|
+
.no-chat {
|
|
117
|
+
flex: none;
|
|
118
|
+
width: 8px;
|
|
119
|
+
height: 8px;
|
|
120
|
+
margin-right: 5px;
|
|
121
|
+
border: 1.5px solid var(--soft);
|
|
122
|
+
border-radius: 50%;
|
|
123
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "preact/hooks";
|
|
3
|
+
import { Dialog } from "../dialogs/dialog.js";
|
|
4
|
+
import { checkIcon } from "../dialogs/profiles.js";
|
|
5
|
+
/** The chat's window: closed once a chat opens or starts again, the page then saying where it stands. */
|
|
6
|
+
export function ChatDialog({ open, table, settings, onChange, name, onClose, text }) {
|
|
7
|
+
const [sending, setSending] = useState(false);
|
|
8
|
+
const [failed, setFailed] = useState(false);
|
|
9
|
+
useEffect(() => setFailed(false), [open]);
|
|
10
|
+
const words = text.table;
|
|
11
|
+
const installed = table.hosts.filter((host) => host.installed && host.name in choiceKeys);
|
|
12
|
+
// A chat opened by hand is the player's: Chesspal cannot start it again. Its rows stay readable, as the ones waiting.
|
|
13
|
+
const byHand = table.chat && !table.held;
|
|
14
|
+
const busy = sending || table.opening !== undefined || table.waiting === true;
|
|
15
|
+
const choose = async (host) => {
|
|
16
|
+
setSending(true);
|
|
17
|
+
setFailed(false);
|
|
18
|
+
const done = table.chat ? await restartChat(host) : await openChat(host);
|
|
19
|
+
setSending(false);
|
|
20
|
+
if (done) {
|
|
21
|
+
onClose();
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
setFailed(true);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
// While a chat opens or waits to start again, the rows wait too, and the note says why.
|
|
28
|
+
const note = installed.length === 0
|
|
29
|
+
? words.install(name)
|
|
30
|
+
: busy
|
|
31
|
+
? chatState(table, name, text) ?? words.choose
|
|
32
|
+
: byHand
|
|
33
|
+
? words.byHand(name)
|
|
34
|
+
: table.chat
|
|
35
|
+
? words.restartHint
|
|
36
|
+
: words.choose;
|
|
37
|
+
const offersChoices = installed.some((host) => host.models.length > 0 || host.efforts.length > 0);
|
|
38
|
+
// The chat at the table started with other choices than the ones kept now: its row starts it again with these, else it only closes the window.
|
|
39
|
+
const started = table.held ? table.started : undefined;
|
|
40
|
+
const changed = (host) => started !== undefined && (chosen(host, "model", settings) !== (started.model ?? "") || chosen(host, "effort", settings) !== (started.effort ?? ""));
|
|
41
|
+
return (_jsxs(Dialog, { open: open, title: words.title, onClose: onClose, text: text, children: [_jsx("p", { class: "note", children: note }), _jsxs("div", { class: "chat-hosts", role: "group", "aria-label": words.title, children: [offersChoices
|
|
42
|
+
? (_jsxs("div", { class: "chat-head", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", { children: words.model }), _jsx("span", { children: words.effort })] }))
|
|
43
|
+
: null, installed.map((host) => {
|
|
44
|
+
const atTable = table.chat && table.host === host.name;
|
|
45
|
+
const off = busy || byHand;
|
|
46
|
+
const again = !atTable || changed(host);
|
|
47
|
+
return (_jsxs("div", { class: "chat-host", children: [_jsxs("button", { class: "choice", type: "button", title: atTable && again ? words.restartChanged : undefined, "aria-current": atTable ? "true" : undefined, "aria-disabled": off, onClick: off ? undefined : again ? () => void choose(host.name) : onClose, children: [_jsx("span", { children: hostTitles[host.name] ?? host.name }), atTable ? (again ? restartIcon : checkIcon) : null] }), _jsx(Choice, { host: host, kind: "model", settings: settings, onChange: onChange, text: text }), _jsx(Choice, { host: host, kind: "effort", settings: settings, onChange: onChange, text: text })] }, host.name));
|
|
48
|
+
})] }), _jsx("p", { class: "chat-state", role: "status", children: failed ? (table.chat ? words.notRestarted : words.failed) : null }), table.chat || installed.length === 0 ? null : (_jsxs("p", { class: "chat-command", children: [words.typeIt, " ", installed.map((host, index) => (_jsxs("span", { children: [index === 0 ? "" : " · ", _jsx("code", { children: host.command })] }, host.name)))] }))] }));
|
|
49
|
+
}
|
|
50
|
+
/** The AI's name on its card, which opens the chat's window: with no chat at the table, a hollow ring before it says so. */
|
|
51
|
+
export function AiNameButton({ name, table, onOpen, text }) {
|
|
52
|
+
const title = table.chat ? text.table.change : text.table.noChat;
|
|
53
|
+
return (_jsxs("button", { class: "profile-name ai-name", type: "button", title: title, "aria-label": `${name}: ${title}`, "aria-haspopup": "dialog", onClick: onOpen, children: [table.chat ? null : _jsx("span", { class: "no-chat", "aria-hidden": "true" }), _jsx("span", { children: name }), _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M7 10L12 15L17 10" }) })] }));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The note on the chat at the table where the asleep note stands: one opening or starting again; one waiting to, with a ↻ that starts it at once and a ×
|
|
57
|
+
* that lets the request go; in a game with no chat at the table, a line that opens the chat's window. None otherwise.
|
|
58
|
+
*/
|
|
59
|
+
export function tableNote({ table, name, inGame, onOpen, text }) {
|
|
60
|
+
const words = text.table;
|
|
61
|
+
const state = chatState(table, name, text);
|
|
62
|
+
if (table.waiting === true) {
|
|
63
|
+
return (_jsxs("p", { class: "asleep table-note", role: "status", children: [_jsx("span", { children: state }), _jsx("button", { class: "icon-button", type: "button", title: words.restartNow, "aria-label": words.restartNow, onClick: () => void endWaiting(true), children: restartIcon }), _jsx("button", { class: "icon-button", type: "button", title: words.cancel, "aria-label": words.cancel, onClick: () => void endWaiting(false), children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M6 6L18 18M18 6L6 18" }) }) })] }));
|
|
64
|
+
}
|
|
65
|
+
if (state !== undefined) {
|
|
66
|
+
return _jsx("p", { class: "asleep", role: "status", children: state });
|
|
67
|
+
}
|
|
68
|
+
if (!table.chat && inGame) {
|
|
69
|
+
return (_jsx("p", { class: "asleep table-note", role: "status", children: _jsxs("button", { class: "no-chat-line", type: "button", "aria-haspopup": "dialog", onClick: onOpen, children: [_jsx("span", { class: "no-chat", "aria-hidden": "true" }), words.noChat] }) }));
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
/** What the page says of the chat at the table: one opening, starting again, or waiting to; nothing else. */
|
|
74
|
+
export function chatState(table, name, text) {
|
|
75
|
+
const words = text.table;
|
|
76
|
+
if (table.waiting === true) {
|
|
77
|
+
return words.waiting(name);
|
|
78
|
+
}
|
|
79
|
+
return table.opening === "chat" ? words.opening : table.opening === "again" ? words.again(name) : table.opening === "restart" ? words.restart : undefined;
|
|
80
|
+
}
|
|
81
|
+
const hostTitles = { claude: "Claude Code", codex: "Codex" };
|
|
82
|
+
// The settings that keep each host's choice.
|
|
83
|
+
const choiceKeys = {
|
|
84
|
+
claude: { model: "claudeModel", effort: "claudeEffort" },
|
|
85
|
+
codex: { model: "codexModel", effort: "codexEffort" },
|
|
86
|
+
};
|
|
87
|
+
/** The ↻ of a chat that starts again. */
|
|
88
|
+
const restartIcon = (_jsx("svg", { class: "icon in-use", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M19.5 12a7.5 7.5 0 1 1-2.2-5.3M19.5 4.5v4h-4" }) }));
|
|
89
|
+
/** A host's model or effort as the settings keep it: "" for the host's own, and for a choice it no longer offers. */
|
|
90
|
+
function chosen(host, kind, settings) {
|
|
91
|
+
const key = choiceKeys[host.name][kind];
|
|
92
|
+
const offered = kind === "model" ? host.models : host.efforts;
|
|
93
|
+
return offered.includes(settings[key]) ? settings[key] : "";
|
|
94
|
+
}
|
|
95
|
+
/** A host's model or effort: a dash keeps the host's own; a choice it no longer offers shows as the dash. Its title says what it stands at, the dash too. */
|
|
96
|
+
function Choice({ host, kind, settings, onChange, text }) {
|
|
97
|
+
const key = choiceKeys[host.name][kind];
|
|
98
|
+
const offered = kind === "model" ? host.models : host.efforts;
|
|
99
|
+
const value = chosen(host, kind, settings);
|
|
100
|
+
const title = hostTitles[host.name] ?? host.name;
|
|
101
|
+
if (offered.length === 0) {
|
|
102
|
+
return _jsx("span", {});
|
|
103
|
+
}
|
|
104
|
+
return (_jsxs("select", { "aria-label": `${text.table[kind]}, ${title}`, title: `${text.table[kind]}: ${value === "" ? text.table.asHost(title) : value}`, value: value, onChange: (event) => onChange({ ...settings, [key]: event.currentTarget.value }), children: [_jsx("option", { value: "", children: "\u2014" }), offered.map((one) => _jsx("option", { value: one, children: shortName(one) }, one))] }));
|
|
105
|
+
}
|
|
106
|
+
// Codex's models without the "gpt-" they all start with, so the choice stays narrow; the title keeps the whole name.
|
|
107
|
+
function shortName(value) {
|
|
108
|
+
return value.replace(/^gpt-/, "");
|
|
109
|
+
}
|
|
110
|
+
/** Asks the table to open a terminal with a chat in the host: whether it did. */
|
|
111
|
+
function openChat(host) {
|
|
112
|
+
return asked("/api/table/chat", { host }, (answer) => answer.opened === true);
|
|
113
|
+
}
|
|
114
|
+
/** Asks the table to start at once the chat that waits to start again, cutting its answer, or to let the request go. */
|
|
115
|
+
function endWaiting(now) {
|
|
116
|
+
return asked("/api/table/waiting", { now }, () => true);
|
|
117
|
+
}
|
|
118
|
+
/** Asks the table to start its chat again with the host, at once or once it has ended its answer: whether it took the request. */
|
|
119
|
+
function restartChat(host) {
|
|
120
|
+
return asked("/api/table/restart", { host }, (answer) => answer.restarting === true || answer.waiting === true);
|
|
121
|
+
}
|
|
122
|
+
async function asked(path, body, done) {
|
|
123
|
+
try {
|
|
124
|
+
const response = await fetch(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
|
|
125
|
+
return response.ok && done((await response.json()));
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
package/dist/page/talk/chat.css
CHANGED
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/* The thread reads in time's order whichever way the board is turned: the newest words just over the box (Zoe) */
|
|
24
|
+
.flipped .talk.studied,
|
|
25
|
+
.flipped .talk.studied > section {
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
.flipped .talk.studied .sent {
|
|
25
30
|
justify-content: flex-end;
|
|
26
31
|
margin: 0 0 8px;
|
|
@@ -50,10 +55,21 @@
|
|
|
50
55
|
}
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
/* The AI's answers on the left, in its bubbles,
|
|
58
|
+
/* The AI's answers on the left, in its bubbles, each with a small Torretta at its foot instead of the tail that points at the big one: it is not by them (Stefano) */
|
|
54
59
|
.sent li.from-ai {
|
|
55
60
|
align-self: flex-start;
|
|
56
|
-
|
|
61
|
+
flex-direction: row;
|
|
62
|
+
align-items: flex-end;
|
|
63
|
+
gap: 6px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.sent li.from-ai > .torretta {
|
|
67
|
+
flex: none;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* The bubble's foot level with the Torretta's, spaced as the player's lines are */
|
|
71
|
+
.sent li.from-ai > .say {
|
|
72
|
+
margin: 0;
|
|
57
73
|
}
|
|
58
74
|
|
|
59
75
|
.talk.studied .sent .say::before,
|
|
@@ -71,9 +87,11 @@
|
|
|
71
87
|
display: flex;
|
|
72
88
|
align-items: center;
|
|
73
89
|
gap: 1px;
|
|
74
|
-
|
|
75
|
-
|
|
90
|
+
min-height: 24px;
|
|
91
|
+
margin: -2px 0 2px -6px;
|
|
92
|
+
padding: 2px 6px;
|
|
76
93
|
border: 0;
|
|
94
|
+
border-radius: 6px;
|
|
77
95
|
background: none;
|
|
78
96
|
color: inherit;
|
|
79
97
|
font: inherit;
|
|
@@ -86,5 +104,5 @@
|
|
|
86
104
|
.shown-move:hover,
|
|
87
105
|
.shown-move:focus-visible {
|
|
88
106
|
opacity: 1;
|
|
89
|
-
|
|
107
|
+
background: color-mix(in oklab, currentColor 15%, transparent);
|
|
90
108
|
}
|
package/dist/page/talk/talk.css
CHANGED
|
@@ -1,23 +1,42 @@
|
|
|
1
1
|
/* The talk's column: the players' cards with their pictures and names, the faces, the words said and the box to write in, the card of a page offline,
|
|
2
2
|
and the notes laid over the board between games */
|
|
3
3
|
|
|
4
|
-
/* The picture
|
|
4
|
+
/* The picture at the card's top left, the clock at its top right (Stefano); the pieces taken and the badges under the name, run to the card's edge, under
|
|
5
|
+
the clock too; the button that starts the clocks, or the next game, takes the card's width below */
|
|
5
6
|
.player-line {
|
|
6
|
-
display:
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
display: grid;
|
|
8
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
9
|
+
grid-template-rows: auto 1fr;
|
|
10
|
+
column-gap: 12px;
|
|
11
|
+
align-items: start;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.player-line > :first-child {
|
|
15
|
+
grid-column: 1;
|
|
16
|
+
grid-row: 1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.player-line:has(> .haul) > :first-child {
|
|
20
|
+
grid-row: 1 / span 2;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.player-line > .who {
|
|
24
|
+
grid-column: 2;
|
|
25
|
+
grid-row: 1;
|
|
9
26
|
}
|
|
10
27
|
|
|
11
28
|
.player-line > .clock {
|
|
12
|
-
|
|
29
|
+
grid-column: 3;
|
|
30
|
+
grid-row: 1;
|
|
13
31
|
}
|
|
14
32
|
|
|
15
|
-
.player-line >
|
|
16
|
-
|
|
33
|
+
.player-line > .haul {
|
|
34
|
+
grid-column: 2 / -1;
|
|
35
|
+
grid-row: 2;
|
|
36
|
+
min-width: 0;
|
|
17
37
|
}
|
|
18
38
|
|
|
19
39
|
.who {
|
|
20
|
-
flex: 1;
|
|
21
40
|
min-width: 0;
|
|
22
41
|
line-height: 1.25;
|
|
23
42
|
}
|
|
@@ -76,14 +95,11 @@
|
|
|
76
95
|
|
|
77
96
|
/* The clocks wait for the player (Zoe): the button that starts them takes a line of the player's card, under the clock it starts, off the board's squares;
|
|
78
97
|
between games the new game takes the same line, in the clock's place (D-77) */
|
|
79
|
-
.player-line:has(> .start-clocks, > .next-game) {
|
|
80
|
-
flex-wrap: wrap;
|
|
81
|
-
row-gap: 10px;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
98
|
.start-clocks,
|
|
85
99
|
.next-game {
|
|
86
|
-
|
|
100
|
+
grid-column: 1 / -1;
|
|
101
|
+
grid-row: 3;
|
|
102
|
+
margin-top: 10px;
|
|
87
103
|
justify-content: center;
|
|
88
104
|
padding: 8px 14px;
|
|
89
105
|
font-size: 15px;
|
|
@@ -107,7 +123,8 @@
|
|
|
107
123
|
align-self: center;
|
|
108
124
|
}
|
|
109
125
|
|
|
110
|
-
|
|
126
|
+
/* In analysis, a side that took nothing has its badges right under its name */
|
|
127
|
+
.haul:has(> .badge-counts) > .taken:empty {
|
|
111
128
|
display: none;
|
|
112
129
|
}
|
|
113
130
|
|
|
@@ -430,6 +447,12 @@
|
|
|
430
447
|
display: none;
|
|
431
448
|
}
|
|
432
449
|
|
|
450
|
+
/* A bubble keeps its height: one that does not fit is hidden whole, the newest scrolls in itself (talk.tsx) */
|
|
451
|
+
.says > li,
|
|
452
|
+
.sent > li {
|
|
453
|
+
flex: none;
|
|
454
|
+
}
|
|
455
|
+
|
|
433
456
|
.say {
|
|
434
457
|
position: relative;
|
|
435
458
|
background: var(--say);
|
|
@@ -646,11 +669,6 @@
|
|
|
646
669
|
margin: 8px 0 0;
|
|
647
670
|
}
|
|
648
671
|
|
|
649
|
-
/* In analysis nothing in the talk gives way: on a short screen the talk scrolls whole */
|
|
650
|
-
.talk.studied {
|
|
651
|
-
overflow-y: auto;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
672
|
.talk.studied > section,
|
|
655
673
|
.talk.studied > section > * {
|
|
656
674
|
flex: none;
|
|
@@ -703,12 +721,6 @@
|
|
|
703
721
|
margin-top: 4px;
|
|
704
722
|
}
|
|
705
723
|
|
|
706
|
-
/* In analysis the board turned round changes which side's card is on top, never the order of the talk */
|
|
707
|
-
.flipped .talk.studied,
|
|
708
|
-
.flipped .talk.studied > section {
|
|
709
|
-
flex-direction: column;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
724
|
.move-note .head {
|
|
713
725
|
display: flex;
|
|
714
726
|
align-items: center;
|
package/dist/page/talk/talk.js
CHANGED
|
@@ -60,7 +60,7 @@ export function Legend({ review, words }) {
|
|
|
60
60
|
/**
|
|
61
61
|
* The player's words of the game, the newest by the box they write in with how far it got, and the box: Enter or the button sends the words alone, the next
|
|
62
62
|
* move takes them with it. 200 characters from one move of the AI's to the next: once they are written, the box goes until its move (D-54). In analysis the
|
|
63
|
-
* AI's answers are in the same thread, and each line of the player's names the move it was written on (D-101).
|
|
63
|
+
* AI's answers are in the same thread, each by a small Torretta, and each line of the player's names the move it was written on (D-101).
|
|
64
64
|
*/
|
|
65
65
|
export function Compose({ lines, newestFirst, spent, draft, onDraft, onSend, canMove, name, problem, about, text }) {
|
|
66
66
|
const list = useRef(null);
|
|
@@ -76,7 +76,7 @@ export function Compose({ lines, newestFirst, spent, draft, onDraft, onSend, can
|
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
78
|
return (_jsxs(_Fragment, { children: [_jsx("ol", { ref: list, class: "sent", "aria-live": "polite", children: inOrder(lines.map((line, index) => (_jsxs("li", { class: line.who === "ai" ? "from-ai" : undefined, style: about === undefined ? aged(lines.length - 1 - index) : reread(index, lastAnswer), children: [line.who === "ai"
|
|
79
|
-
? (_jsx("p", { class: "say", children: _jsx(Marked, { text: line.text }) }))
|
|
79
|
+
? (_jsxs(_Fragment, { children: [_jsx(Torretta, { size: 24 }), _jsx("p", { class: "say", children: _jsx(Marked, { text: line.text }) })] }))
|
|
80
80
|
: (_jsxs("p", { class: "said", children: [about === undefined || line.shown?.move === undefined ? null : _jsx(ShownMove, { move: line.shown.move, onShow: () => about.onShow(line.shown.at), text: text }), line.text] })), index === lastWritten ? _jsx(Status, { heard: line.heard, name: name, text: text }) : null] }, index))), newestFirst) }), left <= 0 ? null : (_jsxs("form", { class: about === undefined ? "compose" : "compose asking", onSubmit: (event) => {
|
|
81
81
|
event.preventDefault();
|
|
82
82
|
submit();
|
|
@@ -164,13 +164,15 @@ function useOnlyWhole(list, lines, newestFirst = false) {
|
|
|
164
164
|
}
|
|
165
165
|
const fit = () => {
|
|
166
166
|
const box = element.getBoundingClientRect();
|
|
167
|
+
const padding = getComputedStyle(element);
|
|
168
|
+
const room = element.clientHeight - parseFloat(padding.paddingTop) - parseFloat(padding.paddingBottom);
|
|
167
169
|
const newest = newestFirst ? element.firstElementChild : element.lastElementChild;
|
|
168
170
|
for (const item of element.children) {
|
|
169
171
|
const place = item.getBoundingClientRect();
|
|
170
172
|
const style = item.style;
|
|
171
173
|
// The newest never hides: one longer than the column, as a note with the helps on (D-78), scrolls in itself.
|
|
172
|
-
style.maxHeight = item === newest ?
|
|
173
|
-
style.overflowY = item === newest && item.scrollHeight >
|
|
174
|
+
style.maxHeight = item === newest ? room + "px" : "";
|
|
175
|
+
style.overflowY = item === newest && item.scrollHeight > room ? "auto" : "";
|
|
174
176
|
style.visibility = item === newest || (place.top >= box.top - 1 && place.bottom <= box.bottom + 1) ? "" : "hidden";
|
|
175
177
|
}
|
|
176
178
|
};
|