@jant/core 0.3.43 → 0.3.44
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-Ctl0T0zO.js → app-BI9bnCkO.js} +1 -1
- package/dist/{app-GbfwoeDJ.js → app-CtJDxZBb.js} +341 -252
- package/dist/client/.vite/manifest.json +1 -1
- package/dist/client/_assets/client-BQH7AQ24.css +2 -0
- package/dist/index.js +1 -1
- package/dist/node.js +2 -2
- package/package.json +1 -1
- package/src/db/migrations/0019_bored_magus.sql +2 -0
- package/src/db/migrations/meta/0019_snapshot.json +2238 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/migrations/pg/0017_bright_beyonder.sql +2 -0
- package/src/db/migrations/pg/meta/0017_snapshot.json +2862 -0
- package/src/db/migrations/pg/meta/_journal.json +7 -0
- package/src/db/pg/schema.ts +4 -0
- package/src/db/schema.ts +4 -0
- package/src/lib/icons.ts +37 -0
- package/src/routes/api/internal/sites.ts +1 -0
- package/src/services/__tests__/site-admin.test.ts +85 -0
- package/src/services/site-admin.ts +66 -1
- package/src/styles/ui.css +12 -0
- 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/layouts/BaseLayout.tsx +14 -29
- 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/client/_assets/client-C_kImWZj.css +0 -2
|
@@ -4,6 +4,7 @@ import { C as coalesceDisplayText, S as shouldUseSecureCookies, _ as getInternal
|
|
|
4
4
|
import { a as listInstallationReposPage, n as getInstallation, o as searchInstallationRepos, t as buildInstallUrl } from "./github-app-WeadXMb8.js";
|
|
5
5
|
import { r as parseRepoSlug, t as createGitHubClient } from "./github-api-BkRWnqMx.js";
|
|
6
6
|
import { I18n } from "@lingui/core";
|
|
7
|
+
import * as lucideIcons from "lucide-static";
|
|
7
8
|
import { z } from "zod";
|
|
8
9
|
import { fromString, typeidUnboxed } from "typeid-js";
|
|
9
10
|
import { decode } from "blurhash";
|
|
@@ -13,7 +14,6 @@ import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
|
13
14
|
import { drizzle as drizzle$1 } from "drizzle-orm/d1";
|
|
14
15
|
import { check, foreignKey, index, integer, primaryKey, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
|
|
15
16
|
import { boolean, check as check$1, customType, foreignKey as foreignKey$1, index as index$1, integer as integer$1, pgTable, primaryKey as primaryKey$1, text as text$1, timestamp, unique, uniqueIndex as uniqueIndex$1 } from "drizzle-orm/pg-core";
|
|
16
|
-
import * as lucideIcons from "lucide-static";
|
|
17
17
|
import { APIError, betterAuth } from "better-auth";
|
|
18
18
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
19
19
|
import { verifyPassword } from "better-auth/crypto";
|
|
@@ -3352,15 +3352,289 @@ function normalizeThemeColorForMeta(color) {
|
|
|
3352
3352
|
* internal paths (e.g. `/_assets/client-HASH.js`) embedded by the Worker build
|
|
3353
3353
|
* from the Vite client manifest. Used only in production (IS_VITE_DEV=false).
|
|
3354
3354
|
*/ var IS_VITE_DEV = typeof __JANT_DEV__ !== "undefined" && __JANT_DEV__ === true;
|
|
3355
|
-
var CORE_VERSION = "0.3.
|
|
3355
|
+
var CORE_VERSION = "0.3.44-c595e1fa6d741ba8";
|
|
3356
3356
|
var CLIENT_JS_FILE = "/_assets/client-D95FNDg5.js";
|
|
3357
3357
|
var CLIENT_AUTH_JS_FILE = "/_assets/client-auth-CXILhW1b.js";
|
|
3358
|
-
var CLIENT_CSS_FILE = "/_assets/client-
|
|
3358
|
+
var CLIENT_CSS_FILE = "/_assets/client-BQH7AQ24.css";
|
|
3359
3359
|
var CLIENT_CJK_CSS_FILE = "/_assets/client-cjk-B7Z0snDu.css";
|
|
3360
3360
|
var CLIENT_CJK_TC_CSS_FILE = "/_assets/client-cjk-tc-BesJYrb2.css";
|
|
3361
3361
|
var CLIENT_CJK_JP_CSS_FILE = "/_assets/client-cjk-jp-DZwrTzQC.css";
|
|
3362
3362
|
var CLIENT_CJK_KR_CSS_FILE = "/_assets/client-cjk-kr-_3ZNI2ZP.css";
|
|
3363
3363
|
//#endregion
|
|
3364
|
+
//#region src/ui/shared/icon-collector.ts
|
|
3365
|
+
/**
|
|
3366
|
+
* Request-scoped icon collector for SSR SVG sprite pattern.
|
|
3367
|
+
*
|
|
3368
|
+
* The <Icon> component registers each used icon name here during render.
|
|
3369
|
+
* At the end of <body>, <IconSprite> reads the collected set and emits a
|
|
3370
|
+
* single <svg><symbol>...</symbol></svg> block so every <use href="#icon-x">
|
|
3371
|
+
* reference in the page resolves to a definition.
|
|
3372
|
+
*
|
|
3373
|
+
* Mirrors the I18nProvider pattern in i18n/context.tsx: Hono JSX renders
|
|
3374
|
+
* synchronously per request, so a module-level singleton is safe.
|
|
3375
|
+
*/ var currentCollector = null;
|
|
3376
|
+
/**
|
|
3377
|
+
* Start a new collection scope for the current render pass.
|
|
3378
|
+
* Call at the top of the root layout before children render.
|
|
3379
|
+
*/ function resetIconCollector() {
|
|
3380
|
+
currentCollector = /* @__PURE__ */ new Set();
|
|
3381
|
+
}
|
|
3382
|
+
/**
|
|
3383
|
+
* Register an icon as used during this render.
|
|
3384
|
+
* Safe to call even when no collector is active (no-op).
|
|
3385
|
+
*/ function collectIcon(name) {
|
|
3386
|
+
currentCollector?.add(name);
|
|
3387
|
+
}
|
|
3388
|
+
/**
|
|
3389
|
+
* Get the icon names collected so far during this render.
|
|
3390
|
+
* Returns an empty set if no collection scope is active.
|
|
3391
|
+
*/ function getCollectedIcons() {
|
|
3392
|
+
return currentCollector ?? /* @__PURE__ */ new Set();
|
|
3393
|
+
}
|
|
3394
|
+
//#endregion
|
|
3395
|
+
//#region src/lib/featured-icons.ts
|
|
3396
|
+
/**
|
|
3397
|
+
* Shared icon definitions for featured post affordances.
|
|
3398
|
+
*
|
|
3399
|
+
* These paths are reused across Hono JSX, Lit, and exported static markup so
|
|
3400
|
+
* "Featured" keeps one visual language everywhere.
|
|
3401
|
+
*/ var FEATURED_SPARKLE_PATH = "M12 3 10.1 10.1 3 12l7.1 1.9L12 21l1.9-7.1L21 12l-7.1-1.9Z";
|
|
3402
|
+
var FEATURED_SPARKLE_OFF_SLASH_PATH = "M4 4 20 20";
|
|
3403
|
+
/**
|
|
3404
|
+
* Build inline SVG markup for the shared featured sparkle icon.
|
|
3405
|
+
*
|
|
3406
|
+
* @param options - Render options for the sparkle icon.
|
|
3407
|
+
* @returns SVG markup string for inline insertion.
|
|
3408
|
+
* @example
|
|
3409
|
+
* getFeaturedIconSvg({ off: true, className: "icon-fine" });
|
|
3410
|
+
*/ function getFeaturedIconSvg(options = {}) {
|
|
3411
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.35" stroke-linecap="round" stroke-linejoin="round"${options.className ? ` class="${options.className}"` : ""} aria-hidden="true"><path d="${FEATURED_SPARKLE_PATH}" />${options.off ? `<path d="${FEATURED_SPARKLE_OFF_SLASH_PATH}" />` : ""}</svg>`;
|
|
3412
|
+
}
|
|
3413
|
+
//#endregion
|
|
3414
|
+
//#region src/lib/decorative-quote-mark.ts
|
|
3415
|
+
var DECORATIVE_QUOTE_MARK_VIEWBOX = "0 0 96 96";
|
|
3416
|
+
var DECORATIVE_QUOTE_MARK_PATHS = ["M24.4 10.5C16.9 17.7 11.5 26.8 8.2 37.7C4.9 48.7 4.8 58.9 7.8 68.2C10.3 75.7 15.4 79.5 22.9 79.5C28 79.5 32.2 77.8 35.4 74.2C38.6 70.7 40.2 66.5 40.2 61.4C40.2 56.5 38.8 52.6 36 49.6C33.3 46.6 29.7 45.1 25.2 45.1C23.4 45.1 21.8 45.3 20.2 45.8C22.2 37.3 26.7 29.2 33.6 21.4L24.4 10.5Z", "M60.8 10.5C53.3 17.7 47.9 26.8 44.6 37.7C41.3 48.7 41.2 58.9 44.2 68.2C46.7 75.7 51.8 79.5 59.3 79.5C64.4 79.5 68.6 77.8 71.8 74.2C75 70.7 76.6 66.5 76.6 61.4C76.6 56.5 75.2 52.6 72.4 49.6C69.7 46.6 66.1 45.1 61.6 45.1C59.8 45.1 58.2 45.3 56.6 45.8C58.6 37.3 63.1 29.2 70 21.4L60.8 10.5Z"];
|
|
3417
|
+
DECORATIVE_QUOTE_MARK_PATHS.map((path) => `<path fill="currentColor" d="${path}" />`).join("");
|
|
3418
|
+
//#endregion
|
|
3419
|
+
//#region src/ui/shared/custom-icons.ts
|
|
3420
|
+
/**
|
|
3421
|
+
* Custom (non-lucide) SVG symbol definitions used by the icon sprite.
|
|
3422
|
+
*
|
|
3423
|
+
* Icons here fall into three groups:
|
|
3424
|
+
* 1. Jant-specific paths (decorative quote mark, featured sparkle).
|
|
3425
|
+
* 2. Lucide-equivalent paths the UI uses with non-default stroke widths
|
|
3426
|
+
* or sizes that don't match the stock lucide symbol (we keep them as
|
|
3427
|
+
* custom symbols to preserve exact visual fidelity during refactor).
|
|
3428
|
+
* 3. Fixed-color SVGs (video play overlay) that don't use currentColor.
|
|
3429
|
+
*
|
|
3430
|
+
* Each entry provides everything needed to render a <symbol> element:
|
|
3431
|
+
* <symbol id="icon-${name}" viewBox={viewBox}>{inner}</symbol>
|
|
3432
|
+
* Consumers of <Icon name="..."> pass `size` / `className` on the outer
|
|
3433
|
+
* <svg><use/></svg>; `<symbol>` children inherit the outer attributes.
|
|
3434
|
+
*/ var STROKE_THIN = "fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.35\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
|
|
3435
|
+
var STROKE_POST_BADGE = "fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
|
|
3436
|
+
var CUSTOM_SYMBOLS = {
|
|
3437
|
+
"featured-sparkle": {
|
|
3438
|
+
viewBox: "0 0 24 24",
|
|
3439
|
+
inner: `<path ${STROKE_THIN} d="${FEATURED_SPARKLE_PATH}" />`
|
|
3440
|
+
},
|
|
3441
|
+
"featured-sparkle-off": {
|
|
3442
|
+
viewBox: "0 0 24 24",
|
|
3443
|
+
inner: `<path ${STROKE_THIN} d="${FEATURED_SPARKLE_PATH}" /><path ${STROKE_THIN} d="${FEATURED_SPARKLE_OFF_SLASH_PATH}" />`
|
|
3444
|
+
},
|
|
3445
|
+
"decorative-quote": {
|
|
3446
|
+
viewBox: DECORATIVE_QUOTE_MARK_VIEWBOX,
|
|
3447
|
+
inner: DECORATIVE_QUOTE_MARK_PATHS.map((path) => `<path fill="currentColor" d="${path}" />`).join("")
|
|
3448
|
+
},
|
|
3449
|
+
"post-collection-lock": {
|
|
3450
|
+
viewBox: "0 0 16 16",
|
|
3451
|
+
inner: `<rect ${STROKE_THIN} x="3" y="5.05" width="10" height="8.15" rx="2.2" /><path ${STROKE_THIN} d="M5.1 5.05V4.2a1.1 1.1 0 0 1 1.1-1.1h3.6a1.1 1.1 0 0 1 1.1 1.1v.85" />`
|
|
3452
|
+
},
|
|
3453
|
+
"post-menu-dots": {
|
|
3454
|
+
viewBox: "0 0 24 24",
|
|
3455
|
+
inner: `<circle cx="5" cy="12" r="1.75" fill="currentColor" /><circle cx="12" cy="12" r="1.75" fill="currentColor" /><circle cx="19" cy="12" r="1.75" fill="currentColor" />`
|
|
3456
|
+
},
|
|
3457
|
+
"post-external-link": {
|
|
3458
|
+
viewBox: "0 0 24 24",
|
|
3459
|
+
inner: `<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M7 17 17 7" /><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M9 7h8v8" />`
|
|
3460
|
+
},
|
|
3461
|
+
"post-reply": {
|
|
3462
|
+
viewBox: "0 0 24 24",
|
|
3463
|
+
inner: `<polyline fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" points="9 17 4 12 9 7" /><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M20 18v-2a4 4 0 0 0-4-4H4" />`
|
|
3464
|
+
},
|
|
3465
|
+
"link-domain": {
|
|
3466
|
+
viewBox: "0 0 24 24",
|
|
3467
|
+
inner: `<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />`
|
|
3468
|
+
},
|
|
3469
|
+
"link-preview-play": {
|
|
3470
|
+
viewBox: "0 0 68 48",
|
|
3471
|
+
inner: "<path class=\"link-preview-play-bg\" fill=\"rgba(0,0,0,.65)\" d=\"M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55C3.97 2.33 2.27 4.81 1.48 7.74.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z\" /><path fill=\"#fff\" d=\"M45 24L27 14v20\" />"
|
|
3472
|
+
},
|
|
3473
|
+
"link-preview-badge-play": {
|
|
3474
|
+
viewBox: "0 0 16 16",
|
|
3475
|
+
inner: "<path fill=\"currentColor\" d=\"M5.5 3.5v9l7-4.5z\" />"
|
|
3476
|
+
},
|
|
3477
|
+
"toast-success": {
|
|
3478
|
+
viewBox: "0 0 24 24",
|
|
3479
|
+
inner: `<circle fill="none" stroke="currentColor" stroke-width="2" cx="12" cy="12" r="10" /><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="m9 12 2 2 4-4" />`
|
|
3480
|
+
},
|
|
3481
|
+
"toast-error": {
|
|
3482
|
+
viewBox: "0 0 24 24",
|
|
3483
|
+
inner: `<circle fill="none" stroke="currentColor" stroke-width="2" cx="12" cy="12" r="10" /><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="m15 9-6 6M9 9l6 6" />`
|
|
3484
|
+
},
|
|
3485
|
+
"toast-close": {
|
|
3486
|
+
viewBox: "0 0 24 24",
|
|
3487
|
+
inner: `<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M18 6 6 18M6 6l12 12" />`
|
|
3488
|
+
},
|
|
3489
|
+
"post-status-pin": {
|
|
3490
|
+
viewBox: "0 0 24 24",
|
|
3491
|
+
inner: `<line ${STROKE_POST_BADGE} x1="12" x2="12" y1="17" y2="22" /><path ${STROKE_POST_BADGE} d="M5 17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4h1v4.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24Z" />`
|
|
3492
|
+
},
|
|
3493
|
+
"post-status-private": {
|
|
3494
|
+
viewBox: "0 0 24 24",
|
|
3495
|
+
inner: `<path ${STROKE_POST_BADGE} d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" /><path ${STROKE_POST_BADGE} d="M14.084 14.158a3 3 0 0 1-4.242-4.242" /><path ${STROKE_POST_BADGE} d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" /><path ${STROKE_POST_BADGE} d="m2 2 20 20" />`
|
|
3496
|
+
}
|
|
3497
|
+
};
|
|
3498
|
+
function getCustomSymbol(name) {
|
|
3499
|
+
return CUSTOM_SYMBOLS[name] ?? null;
|
|
3500
|
+
}
|
|
3501
|
+
/**
|
|
3502
|
+
* Return the viewBox for an icon's outer <svg> wrapper.
|
|
3503
|
+
*
|
|
3504
|
+
* This must match the <symbol>'s viewBox so the browser computes the correct
|
|
3505
|
+
* intrinsic aspect ratio. Without this, outer <svg> with `height: auto` in
|
|
3506
|
+
* CSS falls back to the 300×150 replaced-element default instead of the
|
|
3507
|
+
* icon's real aspect ratio.
|
|
3508
|
+
*
|
|
3509
|
+
* Falls back to lucide's "0 0 24 24" for lucide-sourced icons.
|
|
3510
|
+
*/ function getIconViewBox(name) {
|
|
3511
|
+
return CUSTOM_SYMBOLS[name]?.viewBox ?? "0 0 24 24";
|
|
3512
|
+
}
|
|
3513
|
+
//#endregion
|
|
3514
|
+
//#region src/ui/shared/Icon.tsx
|
|
3515
|
+
/**
|
|
3516
|
+
* <Icon> — sprite-based SVG icon for SSR pages.
|
|
3517
|
+
*
|
|
3518
|
+
* Renders a lightweight <svg><use href="#icon-${name}"/></svg> stub and
|
|
3519
|
+
* registers the icon name with the request-scoped collector so the final
|
|
3520
|
+
* sprite (rendered by <IconSprite>) contains exactly the icons used on
|
|
3521
|
+
* this page.
|
|
3522
|
+
*
|
|
3523
|
+
* Name can refer to any lucide-static icon (kebab-case) or one of the
|
|
3524
|
+
* custom symbols defined in `custom-icons.ts`. Unknown names render an
|
|
3525
|
+
* empty <svg> — the same failure mode as the previous getIconSvg() path.
|
|
3526
|
+
*
|
|
3527
|
+
* Size: outer <svg> width/height in pixels. Defaults to 24 (lucide default).
|
|
3528
|
+
* Pass `class` to add CSS classes, e.g. for sizing via stylesheet instead
|
|
3529
|
+
* of inline width/height.
|
|
3530
|
+
*/ var Icon$1 = ({ name, size, class: cls, "aria-label": ariaLabel, "aria-hidden": ariaHidden }) => {
|
|
3531
|
+
collectIcon(name);
|
|
3532
|
+
const hidden = ariaHidden ?? (ariaLabel ? void 0 : true);
|
|
3533
|
+
return /* @__PURE__ */ jsxDEV$1("svg", {
|
|
3534
|
+
viewBox: getIconViewBox(name),
|
|
3535
|
+
...size !== void 0 ? {
|
|
3536
|
+
width: size,
|
|
3537
|
+
height: size
|
|
3538
|
+
} : {},
|
|
3539
|
+
...cls ? { class: cls } : {},
|
|
3540
|
+
...ariaLabel ? {
|
|
3541
|
+
"aria-label": ariaLabel,
|
|
3542
|
+
role: "img"
|
|
3543
|
+
} : {},
|
|
3544
|
+
...hidden ? { "aria-hidden": "true" } : {},
|
|
3545
|
+
children: /* @__PURE__ */ jsxDEV$1("use", { href: `#icon-${name}` })
|
|
3546
|
+
});
|
|
3547
|
+
};
|
|
3548
|
+
//#endregion
|
|
3549
|
+
//#region src/lib/icons.ts
|
|
3550
|
+
/**
|
|
3551
|
+
* Shared icon utilities.
|
|
3552
|
+
*
|
|
3553
|
+
* Provides a small wrapper around lucide-static so server-rendered UI can fetch
|
|
3554
|
+
* SVG markup by kebab-case icon name.
|
|
3555
|
+
*/
|
|
3556
|
+
/**
|
|
3557
|
+
* Convert a kebab-case icon name to PascalCase for lucide-static lookup.
|
|
3558
|
+
*
|
|
3559
|
+
* @param name - Kebab-case icon name such as "book-open"
|
|
3560
|
+
* @returns PascalCase name such as "BookOpen"
|
|
3561
|
+
*/ function toPascalCase(name) {
|
|
3562
|
+
return name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
3563
|
+
}
|
|
3564
|
+
/**
|
|
3565
|
+
* Get SVG markup for a Lucide icon by kebab-case name.
|
|
3566
|
+
*
|
|
3567
|
+
* @param name - Kebab-case icon name
|
|
3568
|
+
* @returns SVG string or null when the icon is unknown
|
|
3569
|
+
*
|
|
3570
|
+
* @example
|
|
3571
|
+
* ```ts
|
|
3572
|
+
* getIconSvg("book-open");
|
|
3573
|
+
* ```
|
|
3574
|
+
*/ function getIconSvg(name) {
|
|
3575
|
+
const svg = lucideIcons[toPascalCase(name)];
|
|
3576
|
+
return typeof svg === "string" ? svg : null;
|
|
3577
|
+
}
|
|
3578
|
+
/**
|
|
3579
|
+
* Get the inner SVG contents for a Lucide icon (the path children only,
|
|
3580
|
+
* without the outer <svg> wrapper). Used by the icon sprite to build
|
|
3581
|
+
* <symbol> definitions.
|
|
3582
|
+
*
|
|
3583
|
+
* @param name - Kebab-case icon name
|
|
3584
|
+
* @returns Inner SVG markup (e.g. "<path ... />"), or null when unknown
|
|
3585
|
+
*
|
|
3586
|
+
* @example
|
|
3587
|
+
* ```ts
|
|
3588
|
+
* getIconInnerSvg("book-open");
|
|
3589
|
+
* // -> "<path d=\"...\"/><path d=\"...\"/>"
|
|
3590
|
+
* ```
|
|
3591
|
+
*/ function getIconInnerSvg(name) {
|
|
3592
|
+
const svg = getIconSvg(name);
|
|
3593
|
+
if (!svg) return null;
|
|
3594
|
+
const openTagEnd = svg.indexOf(">");
|
|
3595
|
+
const closeTagStart = svg.lastIndexOf("</svg>");
|
|
3596
|
+
if (openTagEnd < 0 || closeTagStart < 0) return null;
|
|
3597
|
+
return svg.slice(openTagEnd + 1, closeTagStart);
|
|
3598
|
+
}
|
|
3599
|
+
/**
|
|
3600
|
+
* Default stroke/fill attributes inherited by <symbol> children when a
|
|
3601
|
+
* lucide icon is referenced via <use>. These mirror the attributes lucide
|
|
3602
|
+
* normally sets on the outer <svg> so the currentColor-based theming keeps
|
|
3603
|
+
* working through <use>.
|
|
3604
|
+
*/ var LUCIDE_SYMBOL_ATTRS = "fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
|
|
3605
|
+
var LUCIDE_VIEWBOX = "0 0 24 24";
|
|
3606
|
+
//#endregion
|
|
3607
|
+
//#region src/ui/shared/IconSprite.tsx
|
|
3608
|
+
/**
|
|
3609
|
+
* <IconSprite> — emits the SVG symbol definitions used by this render.
|
|
3610
|
+
*
|
|
3611
|
+
* Must be rendered AFTER all <Icon> usages in the document (e.g. at the
|
|
3612
|
+
* end of <body>) so the collector has the full set of icon names. Hono
|
|
3613
|
+
* JSX stringifies synchronously in document order, so children declared
|
|
3614
|
+
* earlier in the tree are evaluated before this component.
|
|
3615
|
+
*
|
|
3616
|
+
* <use href="#icon-x"> anywhere in the document resolves correctly even
|
|
3617
|
+
* when the <symbol> definition comes after the reference, since browsers
|
|
3618
|
+
* wire up the references after the full document is parsed.
|
|
3619
|
+
*/ function buildSymbol(name) {
|
|
3620
|
+
const custom = getCustomSymbol(name);
|
|
3621
|
+
if (custom) return `<symbol id="icon-${name}" viewBox="${custom.viewBox}">${custom.inner}</symbol>`;
|
|
3622
|
+
const inner = getIconInnerSvg(name);
|
|
3623
|
+
if (inner === null) return null;
|
|
3624
|
+
return `<symbol id="icon-${name}" viewBox="${LUCIDE_VIEWBOX}" ${LUCIDE_SYMBOL_ATTRS}>${inner}</symbol>`;
|
|
3625
|
+
}
|
|
3626
|
+
var IconSprite = () => {
|
|
3627
|
+
const symbols = Array.from(getCollectedIcons()).sort().map(buildSymbol).filter((s) => s !== null).join("");
|
|
3628
|
+
if (!symbols) return null;
|
|
3629
|
+
return /* @__PURE__ */ jsxDEV$1("svg", {
|
|
3630
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3631
|
+
style: "display:none",
|
|
3632
|
+
"aria-hidden": "true",
|
|
3633
|
+
"data-icon-sprite": true,
|
|
3634
|
+
children: raw(symbols)
|
|
3635
|
+
});
|
|
3636
|
+
};
|
|
3637
|
+
//#endregion
|
|
3364
3638
|
//#region src/ui/layouts/BaseLayout.tsx
|
|
3365
3639
|
/**
|
|
3366
3640
|
* Base HTML Layout
|
|
@@ -3371,6 +3645,7 @@ var CLIENT_CJK_KR_CSS_FILE = "/_assets/client-cjk-kr-_3ZNI2ZP.css";
|
|
|
3371
3645
|
* In dev mode (Vite), serves assets via Vite's dev server.
|
|
3372
3646
|
* In production, serves pre-built assets with content-hashed filenames.
|
|
3373
3647
|
*/ var BaseLayout = ({ title, description, lang, c, toast, faviconHref, appleTouchHref, faviconUrl, faviconVersion, socialImageUrl, canonicalHref, noindex, isAuthenticated = false, clientBundle, children }) => {
|
|
3648
|
+
resetIconCollector();
|
|
3374
3649
|
const resolvedLang = lang ?? (c ? c.get("lang") : "en");
|
|
3375
3650
|
const appConfig = c ? c.get("appConfig") : void 0;
|
|
3376
3651
|
const resolvedSocialImagePath = socialImageUrl ?? faviconUrl ?? appConfig?.siteAvatarUrl ?? getJantIconHref("socialImage", appConfig?.sitePathPrefix || "");
|
|
@@ -3398,7 +3673,7 @@ var CLIENT_CJK_KR_CSS_FILE = "/_assets/client-cjk-kr-_3ZNI2ZP.css";
|
|
|
3398
3673
|
const cjkSerifFont = appConfig?.cjkSerifFont ?? "off";
|
|
3399
3674
|
const cjkStylesheetPath = cjkSerifFont === "zh-Hans" ? IS_VITE_DEV ? assetPath("/src/style-cjk.css") : toPublicAssetPath(CLIENT_CJK_CSS_FILE, assetBasePath) : cjkSerifFont === "zh-Hant" ? IS_VITE_DEV ? assetPath("/src/style-cjk-tc.css") : toPublicAssetPath(CLIENT_CJK_TC_CSS_FILE, assetBasePath) : cjkSerifFont === "ja" ? IS_VITE_DEV ? assetPath("/src/style-cjk-jp.css") : toPublicAssetPath(CLIENT_CJK_JP_CSS_FILE, assetBasePath) : cjkSerifFont === "ko" ? IS_VITE_DEV ? assetPath("/src/style-cjk-kr.css") : toPublicAssetPath(CLIENT_CJK_KR_CSS_FILE, assetBasePath) : null;
|
|
3400
3675
|
const clientScriptPath = IS_VITE_DEV ? resolvedClientBundle === "full" ? assetPath("/src/client-auth.ts") : assetPath("/src/client.ts") : toPublicAssetPath(resolvedClientBundle === "full" ? CLIENT_AUTH_JS_FILE : CLIENT_JS_FILE, assetBasePath);
|
|
3401
|
-
const faviconAssetVersion = resolvedFaviconVersion || "0.3.
|
|
3676
|
+
const faviconAssetVersion = resolvedFaviconVersion || "0.3.44-c595e1fa6d741ba8";
|
|
3402
3677
|
const resolvedFaviconHref = faviconHref ?? (faviconAssetVersion ? toPublicPath(`/favicon.ico?v=${faviconAssetVersion}`, sitePathPrefix) : toPublicPath("/favicon.ico", sitePathPrefix));
|
|
3403
3678
|
const resolvedAppleTouchHref = appleTouchHref ?? (faviconAssetVersion ? toPublicPath(`/apple-touch-icon.png?v=${faviconAssetVersion}`, sitePathPrefix) : toPublicPath("/apple-touch-icon.png", sitePathPrefix));
|
|
3404
3679
|
const socialImageHref = resolvedSocialImagePath && (isFullUrl(resolvedSocialImagePath) || resolvedSocialImagePath.startsWith("//") ? resolvedSocialImagePath : toAbsoluteSiteUrl(resolvedSocialImagePath, appConfig?.siteUrl || "", sitePathPrefix));
|
|
@@ -3559,46 +3834,18 @@ var CLIENT_CJK_KR_CSS_FILE = "/_assets/client-cjk-kr-_3ZNI2ZP.css";
|
|
|
3559
3834
|
class: `toast ${toast.type === "error" ? "toast-error" : "toast-success"}`,
|
|
3560
3835
|
"data-init": "el.closest('[popover]').showPopover(); history.replaceState({}, '', location.pathname); setTimeout(() => { el.classList.add('toast-out'); el.addEventListener('animationend', () => el.remove()) }, 3000)",
|
|
3561
3836
|
children: [
|
|
3562
|
-
toast.type === "error" ? /* @__PURE__ */ jsxDEV$1("
|
|
3563
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
3564
|
-
fill: "none",
|
|
3565
|
-
viewBox: "0 0 24 24",
|
|
3566
|
-
"stroke-width": "2",
|
|
3567
|
-
stroke: "currentColor",
|
|
3568
|
-
children: [/* @__PURE__ */ jsxDEV$1("circle", {
|
|
3569
|
-
cx: "12",
|
|
3570
|
-
cy: "12",
|
|
3571
|
-
r: "10"
|
|
3572
|
-
}), /* @__PURE__ */ jsxDEV$1("path", { d: "m15 9-6 6M9 9l6 6" })]
|
|
3573
|
-
}) : /* @__PURE__ */ jsxDEV$1("svg", {
|
|
3574
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
3575
|
-
fill: "none",
|
|
3576
|
-
viewBox: "0 0 24 24",
|
|
3577
|
-
"stroke-width": "2",
|
|
3578
|
-
stroke: "currentColor",
|
|
3579
|
-
children: [/* @__PURE__ */ jsxDEV$1("circle", {
|
|
3580
|
-
cx: "12",
|
|
3581
|
-
cy: "12",
|
|
3582
|
-
r: "10"
|
|
3583
|
-
}), /* @__PURE__ */ jsxDEV$1("path", { d: "m9 12 2 2 4-4" })]
|
|
3584
|
-
}),
|
|
3837
|
+
toast.type === "error" ? /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "toast-error" }) : /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "toast-success" }),
|
|
3585
3838
|
/* @__PURE__ */ jsxDEV$1("span", { children: toast.message }),
|
|
3586
3839
|
/* @__PURE__ */ jsxDEV$1("button", {
|
|
3587
3840
|
class: "toast-close",
|
|
3588
3841
|
"data-on:click": "el.closest('.toast').classList.add('toast-out'); el.closest('.toast').addEventListener('animationend', () => el.closest('.toast').remove())",
|
|
3589
|
-
children: /* @__PURE__ */ jsxDEV$1("
|
|
3590
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
3591
|
-
fill: "none",
|
|
3592
|
-
viewBox: "0 0 24 24",
|
|
3593
|
-
"stroke-width": "2",
|
|
3594
|
-
stroke: "currentColor",
|
|
3595
|
-
children: /* @__PURE__ */ jsxDEV$1("path", { d: "M18 6 6 18M6 6l12 12" })
|
|
3596
|
-
})
|
|
3842
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "toast-close" })
|
|
3597
3843
|
})
|
|
3598
3844
|
]
|
|
3599
3845
|
})
|
|
3600
3846
|
}),
|
|
3601
|
-
customBodyEndHtml && raw(customBodyEndHtml)
|
|
3847
|
+
customBodyEndHtml && raw(customBodyEndHtml),
|
|
3848
|
+
/* @__PURE__ */ jsxDEV$1(IconSprite, {})
|
|
3602
3849
|
]
|
|
3603
3850
|
})]
|
|
3604
3851
|
})] });
|
|
@@ -9054,25 +9301,6 @@ var MediaGallery = ({ attachments, postPermalink }) => {
|
|
|
9054
9301
|
});
|
|
9055
9302
|
};
|
|
9056
9303
|
//#endregion
|
|
9057
|
-
//#region src/lib/featured-icons.ts
|
|
9058
|
-
/**
|
|
9059
|
-
* Shared icon definitions for featured post affordances.
|
|
9060
|
-
*
|
|
9061
|
-
* These paths are reused across Hono JSX, Lit, and exported static markup so
|
|
9062
|
-
* "Featured" keeps one visual language everywhere.
|
|
9063
|
-
*/ var FEATURED_SPARKLE_PATH = "M12 3 10.1 10.1 3 12l7.1 1.9L12 21l1.9-7.1L21 12l-7.1-1.9Z";
|
|
9064
|
-
var FEATURED_SPARKLE_OFF_SLASH_PATH = "M4 4 20 20";
|
|
9065
|
-
/**
|
|
9066
|
-
* Build inline SVG markup for the shared featured sparkle icon.
|
|
9067
|
-
*
|
|
9068
|
-
* @param options - Render options for the sparkle icon.
|
|
9069
|
-
* @returns SVG markup string for inline insertion.
|
|
9070
|
-
* @example
|
|
9071
|
-
* getFeaturedIconSvg({ off: true, className: "icon-fine" });
|
|
9072
|
-
*/ function getFeaturedIconSvg(options = {}) {
|
|
9073
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.35" stroke-linecap="round" stroke-linejoin="round"${options.className ? ` class="${options.className}"` : ""} aria-hidden="true"><path d="${FEATURED_SPARKLE_PATH}" />${options.off ? `<path d="${FEATURED_SPARKLE_OFF_SLASH_PATH}" />` : ""}</svg>`;
|
|
9074
|
-
}
|
|
9075
|
-
//#endregion
|
|
9076
9304
|
//#region src/ui/shared/PostFooter.tsx
|
|
9077
9305
|
/**
|
|
9078
9306
|
* Post Footer
|
|
@@ -9099,22 +9327,7 @@ var FEATURED_SPARKLE_OFF_SLASH_PATH = "M4 4 20 20";
|
|
|
9099
9327
|
children: [showIcon && /* @__PURE__ */ jsxDEV$1("span", {
|
|
9100
9328
|
class: "post-collection-primary-icon",
|
|
9101
9329
|
"aria-hidden": "true",
|
|
9102
|
-
children: /* @__PURE__ */ jsxDEV$1("
|
|
9103
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9104
|
-
viewBox: "0 0 16 16",
|
|
9105
|
-
fill: "none",
|
|
9106
|
-
stroke: "currentColor",
|
|
9107
|
-
"stroke-width": "1.35",
|
|
9108
|
-
"stroke-linecap": "round",
|
|
9109
|
-
"stroke-linejoin": "round",
|
|
9110
|
-
children: [/* @__PURE__ */ jsxDEV$1("rect", {
|
|
9111
|
-
x: "3",
|
|
9112
|
-
y: "5.05",
|
|
9113
|
-
width: "10",
|
|
9114
|
-
height: "8.15",
|
|
9115
|
-
rx: "2.2"
|
|
9116
|
-
}), /* @__PURE__ */ jsxDEV$1("path", { d: "M5.1 5.05V4.2a1.1 1.1 0 0 1 1.1-1.1h3.6a1.1 1.1 0 0 1 1.1 1.1v.85" })]
|
|
9117
|
-
})
|
|
9330
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "post-collection-lock" })
|
|
9118
9331
|
}), /* @__PURE__ */ jsxDEV$1("span", {
|
|
9119
9332
|
class: "post-collection-tag-text",
|
|
9120
9333
|
children: first.title
|
|
@@ -9185,29 +9398,9 @@ var PostMenuTriggerButton = ({ className = "post-menu-trigger" }) => {
|
|
|
9185
9398
|
"aria-label": i18n._({ id: "JcD7qf" }),
|
|
9186
9399
|
"aria-expanded": "false",
|
|
9187
9400
|
"data-post-menu-trigger": true,
|
|
9188
|
-
children: /* @__PURE__ */ jsxDEV$1(
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
height: "15",
|
|
9192
|
-
viewBox: "0 0 24 24",
|
|
9193
|
-
fill: "currentColor",
|
|
9194
|
-
children: [
|
|
9195
|
-
/* @__PURE__ */ jsxDEV$1("circle", {
|
|
9196
|
-
cx: "5",
|
|
9197
|
-
cy: "12",
|
|
9198
|
-
r: "1.75"
|
|
9199
|
-
}),
|
|
9200
|
-
/* @__PURE__ */ jsxDEV$1("circle", {
|
|
9201
|
-
cx: "12",
|
|
9202
|
-
cy: "12",
|
|
9203
|
-
r: "1.75"
|
|
9204
|
-
}),
|
|
9205
|
-
/* @__PURE__ */ jsxDEV$1("circle", {
|
|
9206
|
-
cx: "19",
|
|
9207
|
-
cy: "12",
|
|
9208
|
-
r: "1.75"
|
|
9209
|
-
})
|
|
9210
|
-
]
|
|
9401
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, {
|
|
9402
|
+
name: "post-menu-dots",
|
|
9403
|
+
size: 15
|
|
9211
9404
|
})
|
|
9212
9405
|
});
|
|
9213
9406
|
};
|
|
@@ -9235,17 +9428,7 @@ var PostFooter = ({ post, detail, display }) => {
|
|
|
9235
9428
|
"aria-label": featuredLabel,
|
|
9236
9429
|
"data-tooltip": featuredLabel,
|
|
9237
9430
|
"data-align": "center",
|
|
9238
|
-
children: /* @__PURE__ */ jsxDEV$1("
|
|
9239
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9240
|
-
viewBox: "0 0 24 24",
|
|
9241
|
-
fill: "none",
|
|
9242
|
-
stroke: "currentColor",
|
|
9243
|
-
"stroke-width": "1.35",
|
|
9244
|
-
"stroke-linecap": "round",
|
|
9245
|
-
"stroke-linejoin": "round",
|
|
9246
|
-
"aria-hidden": "true",
|
|
9247
|
-
children: /* @__PURE__ */ jsxDEV$1("path", { d: FEATURED_SPARKLE_PATH })
|
|
9248
|
-
})
|
|
9431
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "featured-sparkle" })
|
|
9249
9432
|
}),
|
|
9250
9433
|
showTimestamp && /* @__PURE__ */ jsxDEV$1(PostPublishedLink, {
|
|
9251
9434
|
post,
|
|
@@ -9257,16 +9440,7 @@ var PostFooter = ({ post, detail, display }) => {
|
|
|
9257
9440
|
target: "_blank",
|
|
9258
9441
|
rel: "noopener noreferrer",
|
|
9259
9442
|
"aria-label": i18n._({ id: "9dr9Nh" }),
|
|
9260
|
-
children: /* @__PURE__ */ jsxDEV$1("
|
|
9261
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9262
|
-
viewBox: "0 0 24 24",
|
|
9263
|
-
fill: "none",
|
|
9264
|
-
stroke: "currentColor",
|
|
9265
|
-
"stroke-width": "2",
|
|
9266
|
-
"stroke-linecap": "round",
|
|
9267
|
-
"stroke-linejoin": "round",
|
|
9268
|
-
children: [/* @__PURE__ */ jsxDEV$1("path", { d: "M7 17 17 7" }), /* @__PURE__ */ jsxDEV$1("path", { d: "M9 7h8v8" })]
|
|
9269
|
-
})
|
|
9443
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "post-external-link" })
|
|
9270
9444
|
}),
|
|
9271
9445
|
/* @__PURE__ */ jsxDEV$1(CompactCollectionTags, {
|
|
9272
9446
|
collections: post.collections,
|
|
@@ -9281,17 +9455,9 @@ var PostFooter = ({ post, detail, display }) => {
|
|
|
9281
9455
|
class: "reply-trigger",
|
|
9282
9456
|
"aria-label": i18n._({ id: "ImOQa9" }),
|
|
9283
9457
|
"data-reply-trigger": true,
|
|
9284
|
-
children: /* @__PURE__ */ jsxDEV$1(
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
height: "14",
|
|
9288
|
-
viewBox: "0 0 24 24",
|
|
9289
|
-
fill: "none",
|
|
9290
|
-
stroke: "currentColor",
|
|
9291
|
-
"stroke-width": "2",
|
|
9292
|
-
"stroke-linecap": "round",
|
|
9293
|
-
"stroke-linejoin": "round",
|
|
9294
|
-
children: [/* @__PURE__ */ jsxDEV$1("polyline", { points: "9 17 4 12 9 7" }), /* @__PURE__ */ jsxDEV$1("path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" })]
|
|
9458
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, {
|
|
9459
|
+
name: "post-reply",
|
|
9460
|
+
size: 14
|
|
9295
9461
|
})
|
|
9296
9462
|
}), /* @__PURE__ */ jsxDEV$1(PostMenuTriggerButton, {})]
|
|
9297
9463
|
})]
|
|
@@ -9313,57 +9479,15 @@ var PostFooter = ({ post, detail, display }) => {
|
|
|
9313
9479
|
children: [
|
|
9314
9480
|
/* @__PURE__ */ jsxDEV$1("span", {
|
|
9315
9481
|
class: "post-status-badge post-status-pinned",
|
|
9316
|
-
children: [/* @__PURE__ */ jsxDEV$1("
|
|
9317
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9318
|
-
viewBox: "0 0 24 24",
|
|
9319
|
-
fill: "none",
|
|
9320
|
-
stroke: "currentColor",
|
|
9321
|
-
"stroke-width": "1.75",
|
|
9322
|
-
"stroke-linecap": "round",
|
|
9323
|
-
"stroke-linejoin": "round",
|
|
9324
|
-
children: [/* @__PURE__ */ jsxDEV$1("line", {
|
|
9325
|
-
x1: "12",
|
|
9326
|
-
x2: "12",
|
|
9327
|
-
y1: "17",
|
|
9328
|
-
y2: "22"
|
|
9329
|
-
}), /* @__PURE__ */ jsxDEV$1("path", { d: "M5 17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4h1v4.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24Z" })]
|
|
9330
|
-
}), "Pinned"]
|
|
9482
|
+
children: [/* @__PURE__ */ jsxDEV$1(Icon$1, { name: "post-status-pin" }), "Pinned"]
|
|
9331
9483
|
}),
|
|
9332
9484
|
/* @__PURE__ */ jsxDEV$1("span", {
|
|
9333
9485
|
class: "post-status-badge post-status-pinned-in-collection",
|
|
9334
|
-
children: [/* @__PURE__ */ jsxDEV$1("
|
|
9335
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9336
|
-
viewBox: "0 0 24 24",
|
|
9337
|
-
fill: "none",
|
|
9338
|
-
stroke: "currentColor",
|
|
9339
|
-
"stroke-width": "1.75",
|
|
9340
|
-
"stroke-linecap": "round",
|
|
9341
|
-
"stroke-linejoin": "round",
|
|
9342
|
-
children: [/* @__PURE__ */ jsxDEV$1("line", {
|
|
9343
|
-
x1: "12",
|
|
9344
|
-
x2: "12",
|
|
9345
|
-
y1: "17",
|
|
9346
|
-
y2: "22"
|
|
9347
|
-
}), /* @__PURE__ */ jsxDEV$1("path", { d: "M5 17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4h1v4.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24Z" })]
|
|
9348
|
-
}), "Pinned"]
|
|
9486
|
+
children: [/* @__PURE__ */ jsxDEV$1(Icon$1, { name: "post-status-pin" }), "Pinned"]
|
|
9349
9487
|
}),
|
|
9350
9488
|
/* @__PURE__ */ jsxDEV$1("span", {
|
|
9351
9489
|
class: "post-status-badge post-status-private",
|
|
9352
|
-
children: [/* @__PURE__ */ jsxDEV$1("
|
|
9353
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9354
|
-
viewBox: "0 0 24 24",
|
|
9355
|
-
fill: "none",
|
|
9356
|
-
stroke: "currentColor",
|
|
9357
|
-
"stroke-width": "1.75",
|
|
9358
|
-
"stroke-linecap": "round",
|
|
9359
|
-
"stroke-linejoin": "round",
|
|
9360
|
-
children: [
|
|
9361
|
-
/* @__PURE__ */ jsxDEV$1("path", { d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" }),
|
|
9362
|
-
/* @__PURE__ */ jsxDEV$1("path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242" }),
|
|
9363
|
-
/* @__PURE__ */ jsxDEV$1("path", { d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" }),
|
|
9364
|
-
/* @__PURE__ */ jsxDEV$1("path", { d: "m2 2 20 20" })
|
|
9365
|
-
]
|
|
9366
|
-
}), "Private"]
|
|
9490
|
+
children: [/* @__PURE__ */ jsxDEV$1(Icon$1, { name: "post-status-private" }), "Private"]
|
|
9367
9491
|
})
|
|
9368
9492
|
]
|
|
9369
9493
|
});
|
|
@@ -9497,29 +9621,17 @@ var LinkPreview = ({ imageUrl, linkUrl, kind, provider }) => {
|
|
|
9497
9621
|
isVideo && /* @__PURE__ */ jsxDEV$1("div", {
|
|
9498
9622
|
class: "link-preview-play",
|
|
9499
9623
|
"aria-hidden": "true",
|
|
9500
|
-
children: /* @__PURE__ */ jsxDEV$1(
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9504
|
-
children: [/* @__PURE__ */ jsxDEV$1("path", {
|
|
9505
|
-
class: "link-preview-play-bg",
|
|
9506
|
-
d: "M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55C3.97 2.33 2.27 4.81 1.48 7.74.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z",
|
|
9507
|
-
fill: "rgba(0,0,0,.65)"
|
|
9508
|
-
}), /* @__PURE__ */ jsxDEV$1("path", {
|
|
9509
|
-
d: "M45 24L27 14v20",
|
|
9510
|
-
fill: "#fff"
|
|
9511
|
-
})]
|
|
9624
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, {
|
|
9625
|
+
name: "link-preview-play",
|
|
9626
|
+
class: "link-preview-play-icon"
|
|
9512
9627
|
})
|
|
9513
9628
|
}),
|
|
9514
9629
|
providerLabel && /* @__PURE__ */ jsxDEV$1("span", {
|
|
9515
9630
|
class: "link-preview-badge",
|
|
9516
9631
|
"aria-hidden": "true",
|
|
9517
|
-
children: [isVideo && /* @__PURE__ */ jsxDEV$1(
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
9521
|
-
fill: "currentColor",
|
|
9522
|
-
children: /* @__PURE__ */ jsxDEV$1("path", { d: "M5.5 3.5v9l7-4.5z" })
|
|
9632
|
+
children: [isVideo && /* @__PURE__ */ jsxDEV$1(Icon$1, {
|
|
9633
|
+
name: "link-preview-badge-play",
|
|
9634
|
+
class: "link-preview-badge-icon"
|
|
9523
9635
|
}), providerLabel]
|
|
9524
9636
|
})
|
|
9525
9637
|
]
|
|
@@ -9546,25 +9658,15 @@ var LinkPreview = ({ imageUrl, linkUrl, kind, provider }) => {
|
|
|
9546
9658
|
class: "feed-link-domain",
|
|
9547
9659
|
target: "_blank",
|
|
9548
9660
|
rel: "noopener noreferrer",
|
|
9549
|
-
children: [/* @__PURE__ */ jsxDEV$1(
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
fill: "none",
|
|
9553
|
-
viewBox: "0 0 24 24",
|
|
9554
|
-
"stroke-width": "2",
|
|
9555
|
-
stroke: "currentColor",
|
|
9556
|
-
children: /* @__PURE__ */ jsxDEV$1("path", { d: "M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" })
|
|
9661
|
+
children: [/* @__PURE__ */ jsxDEV$1(Icon$1, {
|
|
9662
|
+
name: "link-domain",
|
|
9663
|
+
class: "feed-link-domain-icon"
|
|
9557
9664
|
}), /* @__PURE__ */ jsxDEV$1("span", { children: domain })]
|
|
9558
9665
|
}) : /* @__PURE__ */ jsxDEV$1("div", {
|
|
9559
9666
|
class: "feed-link-domain",
|
|
9560
|
-
children: [/* @__PURE__ */ jsxDEV$1(
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
fill: "none",
|
|
9564
|
-
viewBox: "0 0 24 24",
|
|
9565
|
-
"stroke-width": "2",
|
|
9566
|
-
stroke: "currentColor",
|
|
9567
|
-
children: /* @__PURE__ */ jsxDEV$1("path", { d: "M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" })
|
|
9667
|
+
children: [/* @__PURE__ */ jsxDEV$1(Icon$1, {
|
|
9668
|
+
name: "link-domain",
|
|
9669
|
+
class: "feed-link-domain-icon"
|
|
9568
9670
|
}), /* @__PURE__ */ jsxDEV$1("span", { children: domain })]
|
|
9569
9671
|
}));
|
|
9570
9672
|
const previewEl = !isCompact && post.previewImageUrl && /* @__PURE__ */ jsxDEV$1(LinkPreview, {
|
|
@@ -9645,11 +9747,6 @@ var LinkPreview = ({ imageUrl, linkUrl, kind, provider }) => {
|
|
|
9645
9747
|
});
|
|
9646
9748
|
};
|
|
9647
9749
|
//#endregion
|
|
9648
|
-
//#region src/lib/decorative-quote-mark.ts
|
|
9649
|
-
var DECORATIVE_QUOTE_MARK_VIEWBOX = "0 0 96 96";
|
|
9650
|
-
var DECORATIVE_QUOTE_MARK_PATHS = ["M24.4 10.5C16.9 17.7 11.5 26.8 8.2 37.7C4.9 48.7 4.8 58.9 7.8 68.2C10.3 75.7 15.4 79.5 22.9 79.5C28 79.5 32.2 77.8 35.4 74.2C38.6 70.7 40.2 66.5 40.2 61.4C40.2 56.5 38.8 52.6 36 49.6C33.3 46.6 29.7 45.1 25.2 45.1C23.4 45.1 21.8 45.3 20.2 45.8C22.2 37.3 26.7 29.2 33.6 21.4L24.4 10.5Z", "M60.8 10.5C53.3 17.7 47.9 26.8 44.6 37.7C41.3 48.7 41.2 58.9 44.2 68.2C46.7 75.7 51.8 79.5 59.3 79.5C64.4 79.5 68.6 77.8 71.8 74.2C75 70.7 76.6 66.5 76.6 61.4C76.6 56.5 75.2 52.6 72.4 49.6C69.7 46.6 66.1 45.1 61.6 45.1C59.8 45.1 58.2 45.3 56.6 45.8C58.6 37.3 63.1 29.2 70 21.4L60.8 10.5Z"];
|
|
9651
|
-
DECORATIVE_QUOTE_MARK_PATHS.map((path) => `<path fill="currentColor" d="${path}" />`).join("");
|
|
9652
|
-
//#endregion
|
|
9653
9750
|
//#region src/ui/shared/DecorativeQuoteMark.tsx
|
|
9654
9751
|
/**
|
|
9655
9752
|
* Decorative double-quote mark rendered as SVG so the shape stays consistent
|
|
@@ -9658,15 +9755,7 @@ DECORATIVE_QUOTE_MARK_PATHS.map((path) => `<path fill="currentColor" d="${path}"
|
|
|
9658
9755
|
class: `decorative-quote-mark${cls ? ` ${cls}` : ""}`,
|
|
9659
9756
|
"data-direction": direction,
|
|
9660
9757
|
"aria-hidden": "true",
|
|
9661
|
-
children: /* @__PURE__ */ jsxDEV$1("
|
|
9662
|
-
viewBox: DECORATIVE_QUOTE_MARK_VIEWBOX,
|
|
9663
|
-
role: "presentation",
|
|
9664
|
-
focusable: "false",
|
|
9665
|
-
children: DECORATIVE_QUOTE_MARK_PATHS.map((path) => /* @__PURE__ */ jsxDEV$1("path", {
|
|
9666
|
-
fill: "currentColor",
|
|
9667
|
-
d: path
|
|
9668
|
-
}))
|
|
9669
|
-
})
|
|
9758
|
+
children: /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "decorative-quote" })
|
|
9670
9759
|
});
|
|
9671
9760
|
//#endregion
|
|
9672
9761
|
//#region src/ui/feed/QuoteCard.tsx
|
|
@@ -10427,9 +10516,14 @@ var sites$1 = sqliteTable("site", {
|
|
|
10427
10516
|
id: text("id").primaryKey(),
|
|
10428
10517
|
key: text("key").notNull(),
|
|
10429
10518
|
status: text("status", { enum: SITE_STATUSES$1 }).notNull().default("active"),
|
|
10519
|
+
provisioningIdempotencyKey: text("provisioning_idempotency_key"),
|
|
10430
10520
|
createdAt: integer("created_at").notNull(),
|
|
10431
10521
|
updatedAt: integer("updated_at").notNull()
|
|
10432
|
-
}, (table) => [
|
|
10522
|
+
}, (table) => [
|
|
10523
|
+
uniqueIndex("uq_site_key").on(table.key),
|
|
10524
|
+
uniqueIndex("uq_site_provisioning_idempotency_key").on(table.provisioningIdempotencyKey).where(sql`${table.provisioningIdempotencyKey} IS NOT NULL`),
|
|
10525
|
+
check("chk_site_status", sql`${table.status} IN (${sqlTextEnum$1(SITE_STATUSES$1)})`)
|
|
10526
|
+
]);
|
|
10433
10527
|
var siteDomains$1 = sqliteTable("site_domain", {
|
|
10434
10528
|
id: text("id").primaryKey(),
|
|
10435
10529
|
siteId: text("site_id").notNull().references(() => sites$1.id, { onDelete: "cascade" }),
|
|
@@ -10995,9 +11089,14 @@ var sites = pgTable("site", {
|
|
|
10995
11089
|
id: text$1("id").primaryKey(),
|
|
10996
11090
|
key: text$1("key").notNull(),
|
|
10997
11091
|
status: text$1("status", { enum: SITE_STATUSES }).notNull().default("active"),
|
|
11092
|
+
provisioningIdempotencyKey: text$1("provisioning_idempotency_key"),
|
|
10998
11093
|
createdAt: integer$1("created_at").notNull(),
|
|
10999
11094
|
updatedAt: integer$1("updated_at").notNull()
|
|
11000
|
-
}, (table) => [
|
|
11095
|
+
}, (table) => [
|
|
11096
|
+
uniqueIndex$1("uq_site_key").on(table.key),
|
|
11097
|
+
uniqueIndex$1("uq_site_provisioning_idempotency_key").on(table.provisioningIdempotencyKey).where(sql`${table.provisioningIdempotencyKey} IS NOT NULL`),
|
|
11098
|
+
check$1("chk_site_status", sql`${table.status} IN (${sqlTextEnum(SITE_STATUSES)})`)
|
|
11099
|
+
]);
|
|
11001
11100
|
var siteDomains = pgTable("site_domain", {
|
|
11002
11101
|
id: text$1("id").primaryKey(),
|
|
11003
11102
|
siteId: text$1("site_id").notNull().references(() => sites.id, { onDelete: "cascade" }),
|
|
@@ -11848,36 +11947,6 @@ function createMediaService(db, siteId, databaseSchema = sqliteSchemaBundle, dat
|
|
|
11848
11947
|
};
|
|
11849
11948
|
}
|
|
11850
11949
|
//#endregion
|
|
11851
|
-
//#region src/lib/icons.ts
|
|
11852
|
-
/**
|
|
11853
|
-
* Shared icon utilities.
|
|
11854
|
-
*
|
|
11855
|
-
* Provides a small wrapper around lucide-static so server-rendered UI can fetch
|
|
11856
|
-
* SVG markup by kebab-case icon name.
|
|
11857
|
-
*/
|
|
11858
|
-
/**
|
|
11859
|
-
* Convert a kebab-case icon name to PascalCase for lucide-static lookup.
|
|
11860
|
-
*
|
|
11861
|
-
* @param name - Kebab-case icon name such as "book-open"
|
|
11862
|
-
* @returns PascalCase name such as "BookOpen"
|
|
11863
|
-
*/ function toPascalCase(name) {
|
|
11864
|
-
return name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
11865
|
-
}
|
|
11866
|
-
/**
|
|
11867
|
-
* Get SVG markup for a Lucide icon by kebab-case name.
|
|
11868
|
-
*
|
|
11869
|
-
* @param name - Kebab-case icon name
|
|
11870
|
-
* @returns SVG string or null when the icon is unknown
|
|
11871
|
-
*
|
|
11872
|
-
* @example
|
|
11873
|
-
* ```ts
|
|
11874
|
-
* getIconSvg("book-open");
|
|
11875
|
-
* ```
|
|
11876
|
-
*/ function getIconSvg(name) {
|
|
11877
|
-
const svg = lucideIcons[toPascalCase(name)];
|
|
11878
|
-
return typeof svg === "string" ? svg : null;
|
|
11879
|
-
}
|
|
11880
|
-
//#endregion
|
|
11881
11950
|
//#region src/ui/pages/ArchivePage.tsx
|
|
11882
11951
|
/**
|
|
11883
11952
|
* Archive Page
|
|
@@ -24639,7 +24708,8 @@ var CreateManagedSiteSchema = z.object({
|
|
|
24639
24708
|
primaryHost: z.string().trim().toLowerCase().min(1).max(255).regex(/^(?=.{1,255}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/, "Primary host must be a valid hostname."),
|
|
24640
24709
|
siteName: z.string().trim().min(1).max(120),
|
|
24641
24710
|
siteLanguage: z.string().trim().max(35).optional(),
|
|
24642
|
-
timeZone: z.string().trim().max(100).optional()
|
|
24711
|
+
timeZone: z.string().trim().max(100).optional(),
|
|
24712
|
+
idempotencyKey: z.string().trim().min(1).max(128).optional()
|
|
24643
24713
|
});
|
|
24644
24714
|
var ManagedSiteDomainSchema = z.object({
|
|
24645
24715
|
host: z.string().trim().toLowerCase().min(1).max(255).regex(/^(?=.{1,255}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/, "Domain host must be a valid hostname."),
|
|
@@ -30366,10 +30436,28 @@ function createSiteAdminService(db, databaseSchema = sqliteSchemaBundle, databas
|
|
|
30366
30436
|
const pathPrefix = domain.pathPrefix ?? "";
|
|
30367
30437
|
return `${protocol}//${domain.host}${pathPrefix}`;
|
|
30368
30438
|
}
|
|
30439
|
+
async function loadByIdempotencyKey(targetDb, idempotencyKey) {
|
|
30440
|
+
const siteRow = (await targetDb.select().from(sites).where(eq(sites.provisioningIdempotencyKey, idempotencyKey)).limit(1))[0];
|
|
30441
|
+
if (!siteRow) return null;
|
|
30442
|
+
const domainRow = (await targetDb.select().from(siteDomains).where(and(eq(siteDomains.siteId, siteRow.id), eq(siteDomains.kind, "primary"))).limit(1))[0];
|
|
30443
|
+
if (!domainRow) return null;
|
|
30444
|
+
return {
|
|
30445
|
+
site: toSite(siteRow),
|
|
30446
|
+
domain: toSiteDomain(domainRow)
|
|
30447
|
+
};
|
|
30448
|
+
}
|
|
30369
30449
|
async function createWithDatabase(targetDb, input) {
|
|
30370
30450
|
const siteKey = input.key.trim();
|
|
30371
30451
|
const primaryHost = input.primaryHost.trim().toLowerCase();
|
|
30372
30452
|
const siteName = input.siteName.trim();
|
|
30453
|
+
const idempotencyKey = input.idempotencyKey?.trim() || null;
|
|
30454
|
+
if (idempotencyKey) {
|
|
30455
|
+
const existing = await loadByIdempotencyKey(targetDb, idempotencyKey);
|
|
30456
|
+
if (existing) {
|
|
30457
|
+
if (existing.site.key !== siteKey || existing.domain.host !== primaryHost) throw new ConflictError("Idempotency key was reused with a different site key or primary host.");
|
|
30458
|
+
return existing;
|
|
30459
|
+
}
|
|
30460
|
+
}
|
|
30373
30461
|
if ((await targetDb.select({ id: sites.id }).from(sites).where(eq(sites.key, siteKey)).limit(1))[0]) throw new ConflictError("Site key is already in use.");
|
|
30374
30462
|
if ((await targetDb.select({ id: siteDomains.id }).from(siteDomains).where(eq(siteDomains.host, primaryHost)).limit(1))[0]) throw new ConflictError("Primary host is already in use.");
|
|
30375
30463
|
const timestamp = now();
|
|
@@ -30379,6 +30467,7 @@ function createSiteAdminService(db, databaseSchema = sqliteSchemaBundle, databas
|
|
|
30379
30467
|
id: siteId,
|
|
30380
30468
|
key: siteKey,
|
|
30381
30469
|
status: "active",
|
|
30470
|
+
provisioningIdempotencyKey: idempotencyKey,
|
|
30382
30471
|
createdAt: timestamp,
|
|
30383
30472
|
updatedAt: timestamp
|
|
30384
30473
|
}).returning())[0];
|