@jant/core 0.3.43 → 0.3.45
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/{app-GbfwoeDJ.js → app-C-L7wL6o.js} +485 -452
- package/dist/app-Hvqe7Ks_.js +5 -0
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/client-DDs6NzB3.css +2 -0
- package/dist/client/_assets/{client-auth-CXILhW1b.js → client-auth-Dcon89Av.js} +30 -11
- package/dist/client/_assets/{client-D95FNDg5.js → client-dSfWfMe9.js} +7 -7
- package/dist/{github-sync-7y_nTXx1.js → github-sync-CQ1x271f.js} +3 -0
- package/dist/index.js +4 -87
- package/dist/node.js +3 -3
- package/package.json +1 -1
- package/src/client/components/jant-compose-dialog.ts +87 -9
- package/src/client/components/jant-compose-editor.ts +5 -1
- package/src/client/components/jant-post-menu.ts +23 -5
- package/src/client/compose-bridge.ts +2 -1
- package/src/client/toast.ts +29 -2
- package/src/client/upload-session.ts +1 -1
- package/src/db/migrations/0019_bored_magus.sql +2 -0
- package/src/db/migrations/0020_free_zaladane.sql +1 -0
- package/src/db/migrations/meta/0019_snapshot.json +2238 -0
- package/src/db/migrations/meta/0020_snapshot.json +2129 -0
- package/src/db/migrations/meta/_journal.json +14 -0
- package/src/db/migrations/pg/0017_bright_beyonder.sql +2 -0
- package/src/db/migrations/pg/0018_red_warlock.sql +1 -0
- package/src/db/migrations/pg/meta/0017_snapshot.json +2862 -0
- package/src/db/migrations/pg/meta/0018_snapshot.json +2739 -0
- package/src/db/migrations/pg/meta/_journal.json +14 -0
- package/src/db/pg/schema.ts +4 -30
- package/src/db/schema.ts +4 -39
- package/src/i18n/locales/public/en.po +10 -5
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +10 -5
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +10 -5
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/index.ts +0 -3
- package/src/lib/__tests__/resolve-config.test.ts +4 -4
- package/src/lib/__tests__/startup-config.test.ts +27 -2
- package/src/lib/constants.ts +1 -0
- package/src/lib/github-sync-trigger.ts +7 -51
- package/src/lib/icons.ts +37 -0
- package/src/lib/startup-config.ts +53 -6
- package/src/routes/api/github-sync.tsx +36 -14
- package/src/routes/api/internal/sites.ts +1 -0
- package/src/routes/pages/home.tsx +2 -0
- package/src/routes/pages/latest.tsx +2 -0
- package/src/runtime/__tests__/readiness.test.ts +34 -0
- package/src/runtime/readiness.ts +8 -4
- package/src/services/__tests__/collection.test.ts +13 -11
- package/src/services/__tests__/site-admin.test.ts +85 -0
- package/src/services/github-sync.ts +6 -0
- package/src/services/site-admin.ts +66 -1
- package/src/styles/components.css +14 -0
- package/src/styles/ui.css +109 -0
- package/src/types/bindings.ts +0 -2
- package/src/types/config.ts +1 -1
- package/src/types/props.ts +2 -0
- package/src/ui/__tests__/font-themes.test.ts +2 -2
- package/src/ui/dash/settings/SettingsRootContent.tsx +17 -17
- package/src/ui/feed/LinkCard.tsx +3 -20
- package/src/ui/feed/LinkPreview.tsx +5 -19
- package/src/ui/feed/PostStatusBadges.tsx +4 -38
- package/src/ui/font-themes.ts +17 -17
- package/src/ui/layouts/BaseLayout.tsx +14 -29
- package/src/ui/pages/HomePage.tsx +21 -5
- package/src/ui/shared/DecorativeQuoteMark.tsx +2 -13
- package/src/ui/shared/Icon.tsx +60 -0
- package/src/ui/shared/IconSprite.tsx +57 -0
- package/src/ui/shared/PostFooter.tsx +6 -62
- package/src/ui/shared/custom-icons.ts +132 -0
- package/src/ui/shared/icon-collector.ts +37 -0
- package/dist/app-Ctl0T0zO.js +0 -5
- package/dist/client/_assets/client-C_kImWZj.css +0 -2
- package/src/lib/github-sync-queue-handler.ts +0 -69
- package/src/lib/github-sync-worker.ts +0 -72
- package/src/lib/job-queue-cf.ts +0 -18
- package/src/lib/job-queue-db.ts +0 -149
- package/src/lib/job-queue.ts +0 -35
|
@@ -641,6 +641,7 @@ export class JantComposeDialog extends LitElement {
|
|
|
641
641
|
private _suppressBeforeUnload = false;
|
|
642
642
|
private _dialogEl: HTMLDialogElement | null = null;
|
|
643
643
|
private _mousedownOnBackdrop = false;
|
|
644
|
+
private _mousedownPos: { x: number; y: number } | null = null;
|
|
644
645
|
private _filePickerActive = false;
|
|
645
646
|
private _ignoreNextEscapeClose = false;
|
|
646
647
|
private _openEditRequestId = 0;
|
|
@@ -2264,13 +2265,36 @@ export class JantComposeDialog extends LitElement {
|
|
|
2264
2265
|
this.requestClose();
|
|
2265
2266
|
};
|
|
2266
2267
|
|
|
2268
|
+
// Returns true if the given point is inside any open top-layer popover.
|
|
2269
|
+
// Browsers sometimes fire backdrop click events even when the pointer is
|
|
2270
|
+
// over a popover that is rendered above the dialog in the top layer —
|
|
2271
|
+
// document.elementFromPoint() ignores the top layer, so we check bounding
|
|
2272
|
+
// rects manually.
|
|
2273
|
+
private _pointInOpenPopover(x: number, y: number): boolean {
|
|
2274
|
+
for (const el of document.querySelectorAll<HTMLElement>(":popover-open")) {
|
|
2275
|
+
const r = el.getBoundingClientRect();
|
|
2276
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) {
|
|
2277
|
+
return true;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
return false;
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2267
2283
|
private _handleDialogMousedown = (e: Event) => {
|
|
2268
2284
|
// Track whether the mousedown originated on the backdrop (the <dialog>
|
|
2269
2285
|
// itself). When the user drag-selects text inside the editor and the
|
|
2270
2286
|
// pointer overshoots to the backdrop, the subsequent click event fires
|
|
2271
2287
|
// with target === dialog. Without this guard, that click triggers
|
|
2272
2288
|
// requestClose() and the unsaved-changes confirmation pops up.
|
|
2273
|
-
|
|
2289
|
+
const me = e as MouseEvent;
|
|
2290
|
+
// Treat as backdrop only when target is the dialog AND the cursor is not
|
|
2291
|
+
// over an open popover (e.g. a toast notification in the top layer).
|
|
2292
|
+
this._mousedownOnBackdrop =
|
|
2293
|
+
e.target === this._dialogEl &&
|
|
2294
|
+
!this._pointInOpenPopover(me.clientX, me.clientY);
|
|
2295
|
+
this._mousedownPos = this._mousedownOnBackdrop
|
|
2296
|
+
? { x: me.clientX, y: me.clientY }
|
|
2297
|
+
: null;
|
|
2274
2298
|
};
|
|
2275
2299
|
|
|
2276
2300
|
private _handleDialogClick = (e: Event) => {
|
|
@@ -2279,6 +2303,26 @@ export class JantComposeDialog extends LitElement {
|
|
|
2279
2303
|
if (!this._mousedownOnBackdrop) return;
|
|
2280
2304
|
|
|
2281
2305
|
const mouseEvent = e as MouseEvent;
|
|
2306
|
+
|
|
2307
|
+
// If the pointer moved more than 4px since mousedown, the user was
|
|
2308
|
+
// dragging (e.g. selecting text in a toast on top of the backdrop) —
|
|
2309
|
+
// don't treat that as an intentional dismiss click.
|
|
2310
|
+
if (this._mousedownPos) {
|
|
2311
|
+
const dx = mouseEvent.clientX - this._mousedownPos.x;
|
|
2312
|
+
const dy = mouseEvent.clientY - this._mousedownPos.y;
|
|
2313
|
+
if (dx * dx + dy * dy > 16) return;
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
// Also guard against text-selection drags that end back on the backdrop.
|
|
2317
|
+
const selection = document.getSelection();
|
|
2318
|
+
if (selection && !selection.isCollapsed) return;
|
|
2319
|
+
|
|
2320
|
+
// Guard against click pass-through from a top-layer popover: browsers can
|
|
2321
|
+
// route a click on a popover to the dialog backdrop simultaneously.
|
|
2322
|
+
if (this._pointInOpenPopover(mouseEvent.clientX, mouseEvent.clientY)) {
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2282
2326
|
const hitTarget = document.elementFromPoint(
|
|
2283
2327
|
mouseEvent.clientX,
|
|
2284
2328
|
mouseEvent.clientY,
|
|
@@ -4962,6 +5006,38 @@ export class JantComposeDialog extends LitElement {
|
|
|
4962
5006
|
`;
|
|
4963
5007
|
}
|
|
4964
5008
|
|
|
5009
|
+
private _renderHideFromLatestQuickToggleRow() {
|
|
5010
|
+
if (this._visibilityLocked) return nothing;
|
|
5011
|
+
if (this._visibility === "private") return nothing;
|
|
5012
|
+
return html`
|
|
5013
|
+
<div class="compose-quick-actions-row">
|
|
5014
|
+
${this._renderHideFromLatestQuickToggle()}
|
|
5015
|
+
</div>
|
|
5016
|
+
`;
|
|
5017
|
+
}
|
|
5018
|
+
|
|
5019
|
+
private _renderHideFromLatestQuickToggle() {
|
|
5020
|
+
if (this._visibilityLocked) return nothing;
|
|
5021
|
+
if (this._visibility === "private") return nothing;
|
|
5022
|
+
|
|
5023
|
+
const checked = this._visibility === "latest_hidden";
|
|
5024
|
+
return html`
|
|
5025
|
+
<label class="compose-publish-quick-toggle">
|
|
5026
|
+
<input
|
|
5027
|
+
type="checkbox"
|
|
5028
|
+
class="input compose-publish-quick-toggle-input"
|
|
5029
|
+
.checked=${checked}
|
|
5030
|
+
?disabled=${this._loading}
|
|
5031
|
+
@change=${(e: Event) => {
|
|
5032
|
+
const target = e.target as HTMLInputElement;
|
|
5033
|
+
this._setVisibility(target.checked ? "latest_hidden" : "public");
|
|
5034
|
+
}}
|
|
5035
|
+
/>
|
|
5036
|
+
<span>${this.labels.publishHideFromLatest}</span>
|
|
5037
|
+
</label>
|
|
5038
|
+
`;
|
|
5039
|
+
}
|
|
5040
|
+
|
|
4965
5041
|
private _renderEditLoadingState() {
|
|
4966
5042
|
return html`
|
|
4967
5043
|
<div
|
|
@@ -5343,14 +5419,16 @@ export class JantComposeDialog extends LitElement {
|
|
|
5343
5419
|
${isOpeningEdit
|
|
5344
5420
|
? nothing
|
|
5345
5421
|
: html`<div
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5422
|
+
class=${classMap({
|
|
5423
|
+
"compose-action-row": true,
|
|
5424
|
+
"compose-action-row-overlay-open":
|
|
5425
|
+
this._showPublishPanel || this._showCollection,
|
|
5426
|
+
})}
|
|
5427
|
+
>
|
|
5428
|
+
${this._renderCollectionSelector()}
|
|
5429
|
+
${this._renderPublishButton()}
|
|
5430
|
+
</div>
|
|
5431
|
+
${this._renderHideFromLatestQuickToggleRow()}`}
|
|
5354
5432
|
${this._renderMobilePublishPanel()} ${this._renderAttachedPanel()}
|
|
5355
5433
|
${this._renderAltPanel()} ${this._renderDraftsPanel()}
|
|
5356
5434
|
${this._renderConfirmPanel()}
|
|
@@ -1961,6 +1961,11 @@ export class JantComposeEditor extends LitElement {
|
|
|
1961
1961
|
</svg>
|
|
1962
1962
|
<span class="compose-retry-label">${this.labels.retryAll}</span>
|
|
1963
1963
|
</span>
|
|
1964
|
+
${a.error
|
|
1965
|
+
? html`<span class="compose-attachment-error-msg"
|
|
1966
|
+
>${a.error}</span
|
|
1967
|
+
>`
|
|
1968
|
+
: nothing}
|
|
1964
1969
|
</button>
|
|
1965
1970
|
`
|
|
1966
1971
|
: nothing}
|
|
@@ -1976,7 +1981,6 @@ export class JantComposeEditor extends LitElement {
|
|
|
1976
1981
|
<button
|
|
1977
1982
|
type="button"
|
|
1978
1983
|
class="compose-attachment-remove"
|
|
1979
|
-
title=${this.labels.removeAttachment}
|
|
1980
1984
|
aria-label=${this.labels.removeAttachment}
|
|
1981
1985
|
@click=${onClick}
|
|
1982
1986
|
>
|
|
@@ -719,11 +719,29 @@ export class JantPostMenu extends LitElement {
|
|
|
719
719
|
const article = document.querySelector<HTMLElement>(
|
|
720
720
|
`article[data-post-id="${this._data.id}"]`,
|
|
721
721
|
);
|
|
722
|
-
//
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
722
|
+
// If the post is inside a thread group, only remove its .thread-item
|
|
723
|
+
// wrapper so sibling posts in the same thread stay visible. Fall back
|
|
724
|
+
// to removing the .feed-item wrapper (or the article itself) only when
|
|
725
|
+
// the post is standalone or the thread group is now empty.
|
|
726
|
+
const threadItem = article?.closest<HTMLElement>(".thread-item");
|
|
727
|
+
const threadGroup = threadItem?.closest<HTMLElement>(".thread-group");
|
|
728
|
+
if (threadItem && threadGroup) {
|
|
729
|
+
threadItem.remove();
|
|
730
|
+
const remainingPosts = threadGroup.querySelectorAll(
|
|
731
|
+
".thread-item:not(.thread-item-gap)",
|
|
732
|
+
);
|
|
733
|
+
if (remainingPosts.length === 0) {
|
|
734
|
+
const feedItem = threadGroup.closest<HTMLElement>(".feed-item");
|
|
735
|
+
const feedContainer = feedItem?.parentElement ?? null;
|
|
736
|
+
(feedItem ?? threadGroup).remove();
|
|
737
|
+
removeLeadingFeedDivider(feedContainer);
|
|
738
|
+
}
|
|
739
|
+
} else {
|
|
740
|
+
const feedItem = article?.closest<HTMLElement>(".feed-item");
|
|
741
|
+
const feedContainer = feedItem?.parentElement ?? null;
|
|
742
|
+
(feedItem ?? article)?.remove();
|
|
743
|
+
removeLeadingFeedDivider(feedContainer);
|
|
744
|
+
}
|
|
727
745
|
|
|
728
746
|
showToast("Post deleted.");
|
|
729
747
|
} catch {
|
|
@@ -423,7 +423,8 @@ async function uploadFile(
|
|
|
423
423
|
} catch (error) {
|
|
424
424
|
const message = error instanceof Error ? error.message : "Upload failed";
|
|
425
425
|
editor?.updateAttachmentStatus(clientId, "error", null, message);
|
|
426
|
-
|
|
426
|
+
// Error is shown on the attachment thumbnail; only toast when there's no editor context.
|
|
427
|
+
if (!editor) showToast(message, "error");
|
|
427
428
|
return null;
|
|
428
429
|
}
|
|
429
430
|
}
|
package/src/client/toast.ts
CHANGED
|
@@ -22,10 +22,17 @@ export const QUEUED_TOAST_STORAGE_KEY = "jant.pendingToast";
|
|
|
22
22
|
|
|
23
23
|
/** Ensure the toast container is in the top layer (above <dialog> etc.) */
|
|
24
24
|
function ensureTopLayer(container: HTMLElement): void {
|
|
25
|
+
if (typeof container.showPopover !== "function") return;
|
|
26
|
+
|
|
27
|
+
// Re-promote above any modal dialog that was opened after the toast container.
|
|
25
28
|
if (
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
container.matches(":popover-open") &&
|
|
30
|
+
document.querySelector("dialog[open]")
|
|
28
31
|
) {
|
|
32
|
+
container.hidePopover();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!container.matches(":popover-open")) {
|
|
29
36
|
container.showPopover();
|
|
30
37
|
}
|
|
31
38
|
}
|
|
@@ -85,6 +92,11 @@ const TOAST_ICONS = {
|
|
|
85
92
|
'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6M9 9l6 6"/></svg>',
|
|
86
93
|
};
|
|
87
94
|
|
|
95
|
+
const COPY_ICON =
|
|
96
|
+
'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>';
|
|
97
|
+
const CHECK_ICON =
|
|
98
|
+
'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path d="M20 6 9 17l-5-5"/></svg>';
|
|
99
|
+
|
|
88
100
|
/** Build toast inner content using safe DOM APIs (icon is trusted, text uses textContent). */
|
|
89
101
|
function setToastContent(
|
|
90
102
|
toast: HTMLElement,
|
|
@@ -103,6 +115,21 @@ function setToastContent(
|
|
|
103
115
|
a.textContent = action.label;
|
|
104
116
|
toast.appendChild(a);
|
|
105
117
|
}
|
|
118
|
+
if (type === "error" && navigator.clipboard) {
|
|
119
|
+
const btn = document.createElement("button");
|
|
120
|
+
btn.className = "toast-copy";
|
|
121
|
+
btn.setAttribute("aria-label", "Copy error message");
|
|
122
|
+
btn.innerHTML = COPY_ICON;
|
|
123
|
+
btn.addEventListener("click", () => {
|
|
124
|
+
navigator.clipboard.writeText(message).then(() => {
|
|
125
|
+
btn.innerHTML = CHECK_ICON;
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
btn.innerHTML = COPY_ICON;
|
|
128
|
+
}, 1500);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
toast.appendChild(btn);
|
|
132
|
+
}
|
|
106
133
|
}
|
|
107
134
|
|
|
108
135
|
/**
|
|
@@ -108,7 +108,7 @@ async function initiateUpload(file: File): Promise<InitiateResponse> {
|
|
|
108
108
|
headers: { "Content-Type": "application/json" },
|
|
109
109
|
body: JSON.stringify({
|
|
110
110
|
filename: file.name,
|
|
111
|
-
contentType: file.type,
|
|
111
|
+
contentType: file.type || "application/octet-stream",
|
|
112
112
|
size: file.size,
|
|
113
113
|
checksumSha256: await sha256Base64(file),
|
|
114
114
|
}),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DROP TABLE `sync_job`;
|