@wildwinter/app-shell 0.14.0 → 0.15.0
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/dist/anchored.css +54 -0
- package/dist/anchored.css.map +1 -1
- package/dist/comments.css +25 -0
- package/dist/comments.css.map +1 -1
- package/dist/index.cjs +109 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -3
- package/dist/index.d.ts +61 -3
- package/dist/index.js +107 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -269,7 +269,7 @@ interface AnchoredPanelOptions {
|
|
|
269
269
|
* inspector); `"below"` suits one hung off a button in a document's header
|
|
270
270
|
* (Storyletter's topline). Either way it flips when the room is not there.
|
|
271
271
|
*/
|
|
272
|
-
prefer?: "left" | "below";
|
|
272
|
+
prefer?: "left" | "below" | "centre";
|
|
273
273
|
/** Selectors for chrome the panel must not cover - a problems bar, a review
|
|
274
274
|
* bar - so those stay usable while it is open. */
|
|
275
275
|
keepClear?: string[];
|
|
@@ -284,7 +284,44 @@ declare function openAnchoredPanel(opts: AnchoredPanelOptions): AnchoredPanel |
|
|
|
284
284
|
* an element that no longer exists would hang in the air. */
|
|
285
285
|
declare function closeAnchoredPanel(): void;
|
|
286
286
|
/** Patterpad's placement, with the side as a parameter. */
|
|
287
|
-
declare function placeAnchored(panel: HTMLElement, anchor: HTMLElement, width: number, prefer: "left" | "below", keepClear: string[]): void;
|
|
287
|
+
declare function placeAnchored(panel: HTMLElement, anchor: HTMLElement, width: number, prefer: "left" | "below" | "centre", keepClear: string[]): void;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Slugify a name into hyphen form: lower case, apostrophes dropped, runs of
|
|
291
|
+
* anything else to a single hyphen, no leading or trailing hyphen.
|
|
292
|
+
*
|
|
293
|
+
* May return "", when there was nothing usable in the input. Callers treat that
|
|
294
|
+
* as "no pinned address" rather than as an address.
|
|
295
|
+
*/
|
|
296
|
+
declare function gameIdify(text: string): string;
|
|
297
|
+
/** Is this a legal address? Lower case, digits and hyphens, starting and ending
|
|
298
|
+
* with a letter or a digit. Note "" is NOT legal: an empty pinned address means
|
|
299
|
+
* "derive one", which is a different thing from an address. */
|
|
300
|
+
declare function isValidGameId(gameId: string): boolean;
|
|
301
|
+
|
|
302
|
+
interface GameIdEditorOptions {
|
|
303
|
+
anchor: HTMLElement;
|
|
304
|
+
/** The pinned address, or "" when it is being derived. */
|
|
305
|
+
value: string;
|
|
306
|
+
/** What it derives to, shown as the placeholder. */
|
|
307
|
+
derived: string;
|
|
308
|
+
/** Persist. "" means "stop pinning, go back to deriving". */
|
|
309
|
+
onCommit: (gameId: string) => void;
|
|
310
|
+
/** The panel's title. Both apps say "Game ID"; a third may not. */
|
|
311
|
+
title?: string;
|
|
312
|
+
/** Override the rules. Defaults are the shell's, which is what a new app
|
|
313
|
+
* should use; an app whose domain layer owns them can pass its own. */
|
|
314
|
+
slugify?: (text: string) => string;
|
|
315
|
+
validate?: (gameId: string) => boolean;
|
|
316
|
+
/** Where the panel goes, for hosts that anchor it somewhere tight. */
|
|
317
|
+
prefer?: "left" | "below" | "centre";
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Open it. Returns false when that click TOGGLED the panel shut, which is the
|
|
321
|
+
* anchored panel's contract and worth passing on: a caller that repaints on open
|
|
322
|
+
* needs to know it did not.
|
|
323
|
+
*/
|
|
324
|
+
declare function openGameIdEditor(opts: GameIdEditorOptions): boolean;
|
|
288
325
|
|
|
289
326
|
/** One shard's state as the renderer sees it. The app's own DTO will be this
|
|
290
327
|
* shape (it crosses its own bridge); this is what the view needs. */
|
|
@@ -365,6 +402,11 @@ interface CommentMessage {
|
|
|
365
402
|
author: string;
|
|
366
403
|
ts: string;
|
|
367
404
|
body: string;
|
|
405
|
+
/** A TOMBSTONE: the words are gone, the turn in the conversation is not.
|
|
406
|
+
* Deleting a reply out of a thread would renumber the argument around it, so
|
|
407
|
+
* what is left says who spoke and when and that they withdrew it. The body is
|
|
408
|
+
* emptied on the way out, so "deleted" means deleted from the file too. */
|
|
409
|
+
deleted?: boolean;
|
|
368
410
|
}
|
|
369
411
|
/** A thread, anchored to the id of whatever it is about. */
|
|
370
412
|
interface Comment {
|
|
@@ -385,6 +427,22 @@ interface CommentsOptions {
|
|
|
385
427
|
newThreadId: () => string;
|
|
386
428
|
post: (threadId: string, body: string) => void;
|
|
387
429
|
setResolved: (threadId: string, resolved: boolean) => void;
|
|
430
|
+
/**
|
|
431
|
+
* Withdraw one message. Absent = the host does not offer deletion.
|
|
432
|
+
*
|
|
433
|
+
* By INDEX, because a message has no id of its own and giving every message
|
|
434
|
+
* one would be a format change to every stored thread for the sake of one
|
|
435
|
+
* verb. The index is read off the list the reader is looking at, and these
|
|
436
|
+
* files are edited by one person at a time with a VCS underneath.
|
|
437
|
+
*
|
|
438
|
+
* The host decides what deleting MEANS: the agreed rule is that the message
|
|
439
|
+
* becomes a tombstone, and the whole thread goes when nothing readable would
|
|
440
|
+
* be left (which covers the common case of withdrawing a comment nobody had
|
|
441
|
+
* replied to).
|
|
442
|
+
*/
|
|
443
|
+
deleteMessage?: (threadId: string, index: number) => void;
|
|
444
|
+
/** Where the panel sits: "centre" for a point on a canvas (see anchored.ts). */
|
|
445
|
+
prefer?: "left" | "below" | "centre";
|
|
388
446
|
}
|
|
389
447
|
declare function openComments(opts: CommentsOptions): void;
|
|
390
448
|
|
|
@@ -503,4 +561,4 @@ interface SaveController {
|
|
|
503
561
|
}
|
|
504
562
|
declare function createSaveController(opts: SaveControllerOptions): SaveController;
|
|
505
563
|
|
|
506
|
-
export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type DocLine, type DupGuard, type ElProps, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobProgressOptions, type JobProgressView, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButtonOptions, type SaveController, type SaveControllerOptions, type SaveStatus, type SettingsDialog, type SettingsDialogOptions, type SettingsSection, type SettingsSectionHandle, type ShardVc, type TipRect, type TooltipOptions, type VcMap, askIdentity, closeAnchoredPanel, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, hideTip, icon, iconBtn, iconSvg, initTooltips, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openNotesEditor, paintVcBadges, pinButton, placeAnchored, showAbout, tagChips, tipAt, tipBold, vcBadgeFor };
|
|
564
|
+
export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type DocLine, type DupGuard, type ElProps, type GameIdEditorOptions, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobProgressOptions, type JobProgressView, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButtonOptions, type SaveController, type SaveControllerOptions, type SaveStatus, type SettingsDialog, type SettingsDialogOptions, type SettingsSection, type SettingsSectionHandle, type ShardVc, type TipRect, type TooltipOptions, type VcMap, askIdentity, closeAnchoredPanel, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, gameIdify, hideTip, icon, iconBtn, iconSvg, initTooltips, isValidGameId, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, showAbout, tagChips, tipAt, tipBold, vcBadgeFor };
|
package/dist/index.d.ts
CHANGED
|
@@ -269,7 +269,7 @@ interface AnchoredPanelOptions {
|
|
|
269
269
|
* inspector); `"below"` suits one hung off a button in a document's header
|
|
270
270
|
* (Storyletter's topline). Either way it flips when the room is not there.
|
|
271
271
|
*/
|
|
272
|
-
prefer?: "left" | "below";
|
|
272
|
+
prefer?: "left" | "below" | "centre";
|
|
273
273
|
/** Selectors for chrome the panel must not cover - a problems bar, a review
|
|
274
274
|
* bar - so those stay usable while it is open. */
|
|
275
275
|
keepClear?: string[];
|
|
@@ -284,7 +284,44 @@ declare function openAnchoredPanel(opts: AnchoredPanelOptions): AnchoredPanel |
|
|
|
284
284
|
* an element that no longer exists would hang in the air. */
|
|
285
285
|
declare function closeAnchoredPanel(): void;
|
|
286
286
|
/** Patterpad's placement, with the side as a parameter. */
|
|
287
|
-
declare function placeAnchored(panel: HTMLElement, anchor: HTMLElement, width: number, prefer: "left" | "below", keepClear: string[]): void;
|
|
287
|
+
declare function placeAnchored(panel: HTMLElement, anchor: HTMLElement, width: number, prefer: "left" | "below" | "centre", keepClear: string[]): void;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Slugify a name into hyphen form: lower case, apostrophes dropped, runs of
|
|
291
|
+
* anything else to a single hyphen, no leading or trailing hyphen.
|
|
292
|
+
*
|
|
293
|
+
* May return "", when there was nothing usable in the input. Callers treat that
|
|
294
|
+
* as "no pinned address" rather than as an address.
|
|
295
|
+
*/
|
|
296
|
+
declare function gameIdify(text: string): string;
|
|
297
|
+
/** Is this a legal address? Lower case, digits and hyphens, starting and ending
|
|
298
|
+
* with a letter or a digit. Note "" is NOT legal: an empty pinned address means
|
|
299
|
+
* "derive one", which is a different thing from an address. */
|
|
300
|
+
declare function isValidGameId(gameId: string): boolean;
|
|
301
|
+
|
|
302
|
+
interface GameIdEditorOptions {
|
|
303
|
+
anchor: HTMLElement;
|
|
304
|
+
/** The pinned address, or "" when it is being derived. */
|
|
305
|
+
value: string;
|
|
306
|
+
/** What it derives to, shown as the placeholder. */
|
|
307
|
+
derived: string;
|
|
308
|
+
/** Persist. "" means "stop pinning, go back to deriving". */
|
|
309
|
+
onCommit: (gameId: string) => void;
|
|
310
|
+
/** The panel's title. Both apps say "Game ID"; a third may not. */
|
|
311
|
+
title?: string;
|
|
312
|
+
/** Override the rules. Defaults are the shell's, which is what a new app
|
|
313
|
+
* should use; an app whose domain layer owns them can pass its own. */
|
|
314
|
+
slugify?: (text: string) => string;
|
|
315
|
+
validate?: (gameId: string) => boolean;
|
|
316
|
+
/** Where the panel goes, for hosts that anchor it somewhere tight. */
|
|
317
|
+
prefer?: "left" | "below" | "centre";
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Open it. Returns false when that click TOGGLED the panel shut, which is the
|
|
321
|
+
* anchored panel's contract and worth passing on: a caller that repaints on open
|
|
322
|
+
* needs to know it did not.
|
|
323
|
+
*/
|
|
324
|
+
declare function openGameIdEditor(opts: GameIdEditorOptions): boolean;
|
|
288
325
|
|
|
289
326
|
/** One shard's state as the renderer sees it. The app's own DTO will be this
|
|
290
327
|
* shape (it crosses its own bridge); this is what the view needs. */
|
|
@@ -365,6 +402,11 @@ interface CommentMessage {
|
|
|
365
402
|
author: string;
|
|
366
403
|
ts: string;
|
|
367
404
|
body: string;
|
|
405
|
+
/** A TOMBSTONE: the words are gone, the turn in the conversation is not.
|
|
406
|
+
* Deleting a reply out of a thread would renumber the argument around it, so
|
|
407
|
+
* what is left says who spoke and when and that they withdrew it. The body is
|
|
408
|
+
* emptied on the way out, so "deleted" means deleted from the file too. */
|
|
409
|
+
deleted?: boolean;
|
|
368
410
|
}
|
|
369
411
|
/** A thread, anchored to the id of whatever it is about. */
|
|
370
412
|
interface Comment {
|
|
@@ -385,6 +427,22 @@ interface CommentsOptions {
|
|
|
385
427
|
newThreadId: () => string;
|
|
386
428
|
post: (threadId: string, body: string) => void;
|
|
387
429
|
setResolved: (threadId: string, resolved: boolean) => void;
|
|
430
|
+
/**
|
|
431
|
+
* Withdraw one message. Absent = the host does not offer deletion.
|
|
432
|
+
*
|
|
433
|
+
* By INDEX, because a message has no id of its own and giving every message
|
|
434
|
+
* one would be a format change to every stored thread for the sake of one
|
|
435
|
+
* verb. The index is read off the list the reader is looking at, and these
|
|
436
|
+
* files are edited by one person at a time with a VCS underneath.
|
|
437
|
+
*
|
|
438
|
+
* The host decides what deleting MEANS: the agreed rule is that the message
|
|
439
|
+
* becomes a tombstone, and the whole thread goes when nothing readable would
|
|
440
|
+
* be left (which covers the common case of withdrawing a comment nobody had
|
|
441
|
+
* replied to).
|
|
442
|
+
*/
|
|
443
|
+
deleteMessage?: (threadId: string, index: number) => void;
|
|
444
|
+
/** Where the panel sits: "centre" for a point on a canvas (see anchored.ts). */
|
|
445
|
+
prefer?: "left" | "below" | "centre";
|
|
388
446
|
}
|
|
389
447
|
declare function openComments(opts: CommentsOptions): void;
|
|
390
448
|
|
|
@@ -503,4 +561,4 @@ interface SaveController {
|
|
|
503
561
|
}
|
|
504
562
|
declare function createSaveController(opts: SaveControllerOptions): SaveController;
|
|
505
563
|
|
|
506
|
-
export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type DocLine, type DupGuard, type ElProps, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobProgressOptions, type JobProgressView, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButtonOptions, type SaveController, type SaveControllerOptions, type SaveStatus, type SettingsDialog, type SettingsDialogOptions, type SettingsSection, type SettingsSectionHandle, type ShardVc, type TipRect, type TooltipOptions, type VcMap, askIdentity, closeAnchoredPanel, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, hideTip, icon, iconBtn, iconSvg, initTooltips, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openNotesEditor, paintVcBadges, pinButton, placeAnchored, showAbout, tagChips, tipAt, tipBold, vcBadgeFor };
|
|
564
|
+
export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type DocLine, type DupGuard, type ElProps, type GameIdEditorOptions, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobProgressOptions, type JobProgressView, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButtonOptions, type SaveController, type SaveControllerOptions, type SaveStatus, type SettingsDialog, type SettingsDialogOptions, type SettingsSection, type SettingsSectionHandle, type ShardVc, type TipRect, type TooltipOptions, type VcMap, askIdentity, closeAnchoredPanel, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, gameIdify, hideTip, icon, iconBtn, iconSvg, initTooltips, isValidGameId, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, showAbout, tagChips, tipAt, tipBold, vcBadgeFor };
|
package/dist/index.js
CHANGED
|
@@ -628,9 +628,14 @@ function placeAnchored(panel, anchor, width, prefer, keepClear) {
|
|
|
628
628
|
const CAP = window.innerHeight * 0.7;
|
|
629
629
|
const MIN_H = 140;
|
|
630
630
|
panel.style.width = `${width}px`;
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
631
|
+
if (prefer === "centre") {
|
|
632
|
+
const centred = a.left + a.width / 2 - width / 2;
|
|
633
|
+
panel.style.left = `${Math.round(Math.min(Math.max(8, centred), window.innerWidth - width - 8))}px`;
|
|
634
|
+
} else {
|
|
635
|
+
const leftOf = a.left - width - GAP2;
|
|
636
|
+
const alignedRight = Math.min(a.right - width, window.innerWidth - width - 8);
|
|
637
|
+
panel.style.left = `${Math.round(prefer === "left" && leftOf >= 8 ? leftOf : Math.max(8, prefer === "left" ? a.left : alignedRight))}px`;
|
|
638
|
+
}
|
|
634
639
|
let bottomLimit = window.innerHeight - 8;
|
|
635
640
|
for (const selector of keepClear) {
|
|
636
641
|
const bar = document.querySelector(selector);
|
|
@@ -638,6 +643,16 @@ function placeAnchored(panel, anchor, width, prefer, keepClear) {
|
|
|
638
643
|
const r = bar.getBoundingClientRect();
|
|
639
644
|
if (r.height > 0) bottomLimit = Math.min(bottomLimit, r.top - 6);
|
|
640
645
|
}
|
|
646
|
+
if (prefer === "centre") {
|
|
647
|
+
const height = Math.min(CAP, Math.max(MIN_H, panel.offsetHeight));
|
|
648
|
+
const mid = a.top + a.height / 2 - height / 2;
|
|
649
|
+
if (mid >= 8 && mid + height <= bottomLimit) {
|
|
650
|
+
panel.style.top = `${Math.round(mid)}px`;
|
|
651
|
+
panel.style.bottom = "auto";
|
|
652
|
+
panel.style.maxHeight = `${Math.round(CAP)}px`;
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
641
656
|
const roomBelow = bottomLimit - (a.bottom + GAP2);
|
|
642
657
|
const roomAbove = a.top - GAP2 - 8;
|
|
643
658
|
if (roomAbove > roomBelow) {
|
|
@@ -651,6 +666,75 @@ function placeAnchored(panel, anchor, width, prefer, keepClear) {
|
|
|
651
666
|
}
|
|
652
667
|
}
|
|
653
668
|
|
|
669
|
+
// src/ids.ts
|
|
670
|
+
function gameIdify(text) {
|
|
671
|
+
return text.toLowerCase().replace(/['’]/g, "").replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
672
|
+
}
|
|
673
|
+
function isValidGameId(gameId) {
|
|
674
|
+
return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(gameId);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/id-editor.ts
|
|
678
|
+
function openGameIdEditor(opts) {
|
|
679
|
+
const slugify = opts.slugify ?? gameIdify;
|
|
680
|
+
const valid = opts.validate ?? isValidGameId;
|
|
681
|
+
const panel = openAnchoredPanel({
|
|
682
|
+
anchor: opts.anchor,
|
|
683
|
+
className: "shell-id-editor",
|
|
684
|
+
title: opts.title ?? "Game ID",
|
|
685
|
+
width: 260,
|
|
686
|
+
...opts.prefer !== void 0 ? { prefer: opts.prefer } : {}
|
|
687
|
+
});
|
|
688
|
+
if (!panel) return false;
|
|
689
|
+
const input = el("input", "shell-id-input");
|
|
690
|
+
input.type = "text";
|
|
691
|
+
input.value = opts.value;
|
|
692
|
+
input.placeholder = opts.derived || "<game-id>";
|
|
693
|
+
input.spellcheck = false;
|
|
694
|
+
const hint = el("div", "shell-id-hint", "Lower case, digits, hyphens. Empty = auto from the name.");
|
|
695
|
+
const actions = el("div", "shell-id-actions");
|
|
696
|
+
const reset = el("button", "shell-id-reset", "Reset to auto");
|
|
697
|
+
const set = el("button", "shell-id-set", "Set");
|
|
698
|
+
reset.type = "button";
|
|
699
|
+
set.type = "button";
|
|
700
|
+
actions.append(reset, set);
|
|
701
|
+
panel.body.append(input, hint, actions);
|
|
702
|
+
const legal = () => {
|
|
703
|
+
const v = input.value.trim();
|
|
704
|
+
return v === "" || valid(v);
|
|
705
|
+
};
|
|
706
|
+
const sync = () => {
|
|
707
|
+
set.disabled = !legal();
|
|
708
|
+
hint.classList.toggle("bad", !legal());
|
|
709
|
+
};
|
|
710
|
+
const commit = () => {
|
|
711
|
+
if (!legal()) return;
|
|
712
|
+
opts.onCommit(input.value.trim());
|
|
713
|
+
panel.close();
|
|
714
|
+
};
|
|
715
|
+
input.addEventListener("input", sync);
|
|
716
|
+
input.addEventListener("keydown", (e) => {
|
|
717
|
+
e.stopPropagation();
|
|
718
|
+
if (e.key === "Enter") {
|
|
719
|
+
e.preventDefault();
|
|
720
|
+
commit();
|
|
721
|
+
}
|
|
722
|
+
if (e.key === "Tab" && input.value.trim() !== "") {
|
|
723
|
+
e.preventDefault();
|
|
724
|
+
input.value = slugify(input.value);
|
|
725
|
+
sync();
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
set.addEventListener("click", commit);
|
|
729
|
+
reset.addEventListener("click", () => {
|
|
730
|
+
opts.onCommit("");
|
|
731
|
+
panel.close();
|
|
732
|
+
});
|
|
733
|
+
sync();
|
|
734
|
+
setTimeout(() => input.focus(), 0);
|
|
735
|
+
return true;
|
|
736
|
+
}
|
|
737
|
+
|
|
654
738
|
// src/vc-view.ts
|
|
655
739
|
function foldVc(shards, keys) {
|
|
656
740
|
if (!keys) return void 0;
|
|
@@ -884,7 +968,7 @@ var stamp = (ts) => {
|
|
|
884
968
|
return when.toLocaleString(void 0, { dateStyle: "medium", timeStyle: "short" });
|
|
885
969
|
};
|
|
886
970
|
var gist = (thread) => {
|
|
887
|
-
const first = thread.messages
|
|
971
|
+
const first = thread.messages.find((m) => m.deleted !== true)?.body ?? "";
|
|
888
972
|
return first.length > 60 ? `${first.slice(0, 57)}\u2026` : first;
|
|
889
973
|
};
|
|
890
974
|
function openComments(opts) {
|
|
@@ -897,7 +981,7 @@ function openComments(opts) {
|
|
|
897
981
|
// Below its button (the topline), unlike Patterpad's inspector rows which
|
|
898
982
|
// want it to the left; and never over the problems bar, which an author may
|
|
899
983
|
// be stepping through while reading the thread.
|
|
900
|
-
prefer: "below",
|
|
984
|
+
prefer: opts.prefer ?? "below",
|
|
901
985
|
keepClear: [".problembar"]
|
|
902
986
|
});
|
|
903
987
|
if (!panel) return;
|
|
@@ -933,19 +1017,26 @@ function openComments(opts) {
|
|
|
933
1017
|
const thread = open;
|
|
934
1018
|
if (thread) {
|
|
935
1019
|
const list = el("div", { className: `shell-cmt-list${thread.resolved === true ? " resolved" : ""}` });
|
|
936
|
-
|
|
1020
|
+
thread.messages.forEach((message, index) => {
|
|
1021
|
+
const head = el(
|
|
1022
|
+
"div",
|
|
1023
|
+
{ className: "shell-cmt-msg-head" },
|
|
1024
|
+
el("span", { className: "shell-cmt-author", text: message.author === "" ? "Someone" : message.author }),
|
|
1025
|
+
el("span", { className: "shell-cmt-ts", text: stamp(message.ts) })
|
|
1026
|
+
);
|
|
1027
|
+
if (opts.deleteMessage && message.deleted !== true) {
|
|
1028
|
+
const bin = el("button", { className: "shell-cmt-del", text: "\u2715" });
|
|
1029
|
+
bin.title = "Delete this comment";
|
|
1030
|
+
bin.addEventListener("click", () => opts.deleteMessage?.(thread.id, index));
|
|
1031
|
+
head.append(bin);
|
|
1032
|
+
}
|
|
937
1033
|
list.append(el(
|
|
938
1034
|
"div",
|
|
939
|
-
{ className:
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
{ className: "shell-cmt-msg-head" },
|
|
943
|
-
el("span", { className: "shell-cmt-author", text: message.author === "" ? "Someone" : message.author }),
|
|
944
|
-
el("span", { className: "shell-cmt-ts", text: stamp(message.ts) })
|
|
945
|
-
),
|
|
946
|
-
el("div", { className: "shell-cmt-body", text: message.body })
|
|
1035
|
+
{ className: `shell-cmt-msg${message.deleted === true ? " gone" : ""}` },
|
|
1036
|
+
head,
|
|
1037
|
+
el("div", { className: "shell-cmt-body", text: message.deleted === true ? "Comment deleted" : message.body })
|
|
947
1038
|
));
|
|
948
|
-
}
|
|
1039
|
+
});
|
|
949
1040
|
body.append(list);
|
|
950
1041
|
}
|
|
951
1042
|
const input = el("textarea", { className: "shell-cmt-input" });
|
|
@@ -1273,6 +1364,6 @@ function createSaveController(opts) {
|
|
|
1273
1364
|
};
|
|
1274
1365
|
}
|
|
1275
1366
|
|
|
1276
|
-
export { PALETTE, PALETTE_SIZE, askIdentity, closeAnchoredPanel, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, hideTip, icon, iconBtn, iconSvg, initTooltips, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openNotesEditor, paintVcBadges, pinButton, placeAnchored, showAbout, tagChips, tipAt, tipBold, vcBadgeFor };
|
|
1367
|
+
export { PALETTE, PALETTE_SIZE, askIdentity, closeAnchoredPanel, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, gameIdify, hideTip, icon, iconBtn, iconSvg, initTooltips, isValidGameId, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, showAbout, tagChips, tipAt, tipBold, vcBadgeFor };
|
|
1277
1368
|
//# sourceMappingURL=index.js.map
|
|
1278
1369
|
//# sourceMappingURL=index.js.map
|