@jant/core 0.6.12 → 0.6.13
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/bin/commands/import-site.js +2 -10
- package/bin/commands/migrate-text-attachments.js +1 -3
- package/bin/commands/site/export.js +1 -3
- package/bin/commands/telegram/register-webhooks.js +9 -3
- package/bin/lib/site-snapshot.js +1 -5
- package/dist/app-CWJFPjuR.js +6 -0
- package/dist/{app-D-zAhayc.js → app-HnzmsaqU.js} +416 -444
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/{client-S1cdvulk.js → client-3_OaxhTs.js} +1 -1
- package/dist/client/_assets/{client-BUxR-E8O.css → client-DCrcHVJl.css} +1 -1
- package/dist/client/_assets/{client-auth-YW92ZH7u.js → client-auth-TsYq90xm.js} +570 -567
- package/dist/{export-Ckwh4BiE.js → export-Dfbq9aot.js} +10 -13
- package/dist/{github-sync-Dl9wQMar.js → github-sync--PSoE-Ne.js} +1 -1
- package/dist/{github-sync-BMmK3JZ0.js → github-sync-BGTLx8Mf.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/node.js +4 -4
- package/package.json +1 -1
- package/src/__tests__/export-service.test.ts +1 -2
- package/src/__tests__/helpers/app.ts +3 -0
- package/src/__tests__/helpers/export-fixtures.ts +0 -1
- package/src/__tests__/import-site-command.test.ts +1 -2
- package/src/client/__tests__/site-header-nav.test.ts +36 -2
- package/src/client/components/__tests__/compose-title-from-heading.test.ts +57 -0
- package/src/client/components/__tests__/jant-compose-editor.test.ts +94 -2
- package/src/client/components/__tests__/jant-nav-manager.test.ts +107 -22
- package/src/client/components/__tests__/jant-settings-general.test.ts +1 -0
- package/src/client/components/compose-title-from-heading.ts +66 -0
- package/src/client/components/compose-types.ts +8 -0
- package/src/client/components/inline-image-issues.ts +17 -0
- package/src/client/components/jant-compose-dialog.ts +2 -0
- package/src/client/components/jant-compose-editor.ts +78 -1
- package/src/client/components/jant-compose-fullscreen.ts +2 -0
- package/src/client/components/jant-nav-manager.ts +245 -155
- package/src/client/components/nav-manager-types.ts +0 -10
- package/src/client/site-header-nav.d.ts +1 -0
- package/src/client/site-header-nav.js +105 -47
- package/src/client/tiptap/__tests__/block-insertion.test.ts +35 -0
- package/src/client/tiptap/__tests__/image-input-rules.test.ts +117 -0
- package/src/client/tiptap/create-editor.ts +7 -0
- package/src/client/tiptap/extensions.ts +7 -2
- package/src/client/tiptap/image-input-rules.ts +48 -0
- package/src/client/tiptap/image-node.ts +189 -7
- package/src/client-auth.ts +0 -1
- package/src/i18n/locales/public/en.po +20 -4
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +20 -4
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +20 -4
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +23 -20
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +23 -20
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +23 -20
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/lib/__tests__/navigation.test.ts +4 -37
- package/src/lib/__tests__/resolve-config.test.ts +6 -1
- package/src/lib/__tests__/view.test.ts +6 -30
- package/src/lib/github-sync-site-config.ts +0 -2
- package/src/lib/hugo-markdown.ts +1 -1
- package/src/lib/navigation.ts +5 -22
- package/src/lib/resolve-config.ts +9 -2
- package/src/lib/site-header-fragment.tsx +37 -0
- package/src/lib/view.ts +2 -7
- package/src/node/__tests__/cli-site-snapshot.test.ts +45 -45
- package/src/routes/api/__tests__/nav-items.test.ts +26 -0
- package/src/routes/api/export.ts +0 -2
- package/src/routes/api/internal/__tests__/uploads.test.ts +9 -13
- package/src/routes/api/internal/sites.ts +5 -5
- package/src/routes/api/nav-items.ts +25 -4
- package/src/routes/dash/settings.tsx +8 -2
- package/src/routes/feed/__tests__/feed.test.ts +42 -0
- package/src/routes/feed/__tests__/sitemap.test.ts +2 -13
- package/src/routes/feed/feed.ts +1 -0
- package/src/routes/feed/sitemap.ts +2 -6
- package/src/routes/pages/archive.tsx +1 -0
- package/src/routes/pages/collection.tsx +1 -0
- package/src/routes/pages/featured.tsx +0 -4
- package/src/routes/pages/home.tsx +4 -51
- package/src/routes/pages/latest.tsx +2 -51
- package/src/services/__tests__/github-app-installations.test.ts +10 -10
- package/src/services/__tests__/navigation.test.ts +41 -0
- package/src/services/__tests__/post-timeline.test.ts +56 -0
- package/src/services/__tests__/post.test.ts +33 -0
- package/src/services/__tests__/settings.test.ts +5 -20
- package/src/services/export-theme/assets/client-site.js +10 -10
- package/src/services/export-theme/layouts/_default/rss.xml +5 -15
- package/src/services/export-theme/layouts/index.html +26 -39
- package/src/services/export.ts +5 -10
- package/src/services/github-app-installations.ts +1 -3
- package/src/services/navigation.ts +44 -13
- package/src/services/post.ts +80 -57
- package/src/services/settings.ts +0 -10
- package/src/services/site-admin.ts +0 -2
- package/src/services/upload-session.ts +4 -18
- package/src/styles/tokens.css +2 -2
- package/src/styles/ui.css +200 -6
- package/src/types/bindings.ts +0 -1
- package/src/types/config.ts +3 -7
- package/src/ui/compose/ComposeDialog.tsx +28 -0
- package/src/ui/dash/appearance/NavigationContent.tsx +1 -1
- package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
- package/src/ui/dash/settings/SettingsRootContent.tsx +3 -3
- package/src/ui/layouts/SiteLayout.tsx +249 -216
- package/dist/app-B5P1b4TF.js +0 -6
- package/src/client/nav-manager-bridge.ts +0 -54
package/src/services/post.ts
CHANGED
|
@@ -119,6 +119,8 @@ export interface PostFilters {
|
|
|
119
119
|
hasReplies?: boolean;
|
|
120
120
|
/** Explicit result sort order */
|
|
121
121
|
sortOrder?: SortOrder;
|
|
122
|
+
/** Ignore global pinned ordering when sorting subscription/feed results. */
|
|
123
|
+
ignorePinnedSort?: boolean;
|
|
122
124
|
limit?: number;
|
|
123
125
|
cursor?: string;
|
|
124
126
|
offset?: number; // offset for page-based pagination
|
|
@@ -137,6 +139,11 @@ interface ThreadRootPageOptions {
|
|
|
137
139
|
offset?: number;
|
|
138
140
|
}
|
|
139
141
|
|
|
142
|
+
interface CollectionFeedEntryOptions extends ThreadRootPageOptions {
|
|
143
|
+
/** Ignore per-collection pinned ordering when sorting collection feeds. */
|
|
144
|
+
ignoreCollectionPinnedSort?: boolean;
|
|
145
|
+
}
|
|
146
|
+
|
|
140
147
|
interface CollectionThreadRootPageOptions extends ThreadRootPageOptions {
|
|
141
148
|
sortOrder?: CollectionSortOrder;
|
|
142
149
|
}
|
|
@@ -314,12 +321,12 @@ export interface PostService {
|
|
|
314
321
|
/** List collection feed entries ordered by latest added-at timestamp */
|
|
315
322
|
listCollectionFeedEntries(
|
|
316
323
|
collectionId: string,
|
|
317
|
-
options?:
|
|
324
|
+
options?: CollectionFeedEntryOptions,
|
|
318
325
|
): Promise<CollectionFeedEntry[]>;
|
|
319
326
|
/** List collection feed entries for a union of collections */
|
|
320
327
|
listCollectionFeedEntriesForCollections(
|
|
321
328
|
collectionIds: string[],
|
|
322
|
-
options?:
|
|
329
|
+
options?: CollectionFeedEntryOptions,
|
|
323
330
|
): Promise<CollectionFeedEntry[]>;
|
|
324
331
|
/** Fetch all published, non-deleted posts for each requested thread root */
|
|
325
332
|
getPublishedThreads(rootIds: string[]): Promise<Map<string, Post[]>>;
|
|
@@ -902,76 +909,90 @@ export function createPostService(
|
|
|
902
909
|
const cursorSortTimestamp = getCursorSortTimestamp(cursorPost);
|
|
903
910
|
const cursorRatingPresence = cursorPost.rating === null ? 0 : 1;
|
|
904
911
|
const cursorRating = cursorPost.rating ?? -1;
|
|
912
|
+
const withPinnedSortKey = (
|
|
913
|
+
keys: [CursorSortKey, ...CursorSortKey[]],
|
|
914
|
+
): [CursorSortKey, ...CursorSortKey[]] =>
|
|
915
|
+
filters.ignorePinnedSort
|
|
916
|
+
? keys
|
|
917
|
+
: [
|
|
918
|
+
{ direction: "desc", expr: pinnedSortExpr, value: cursorPinnedAt },
|
|
919
|
+
...keys,
|
|
920
|
+
];
|
|
905
921
|
|
|
906
922
|
if (filters.featured || filters.sortOrder === undefined) {
|
|
907
923
|
if (filters.featured) {
|
|
908
|
-
return buildLexicographicCursorCondition(
|
|
909
|
-
|
|
924
|
+
return buildLexicographicCursorCondition(
|
|
925
|
+
withPinnedSortKey([
|
|
926
|
+
{
|
|
927
|
+
direction: "desc",
|
|
928
|
+
expr: featuredSortExpr,
|
|
929
|
+
value: cursorFeaturedAt,
|
|
930
|
+
},
|
|
931
|
+
{ direction: "desc", expr: posts.id, value: cursorPost.id },
|
|
932
|
+
]),
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
return buildLexicographicCursorCondition(
|
|
937
|
+
withPinnedSortKey([
|
|
910
938
|
{
|
|
911
939
|
direction: "desc",
|
|
912
|
-
expr:
|
|
913
|
-
value:
|
|
940
|
+
expr: sortTimestampSortExpr,
|
|
941
|
+
value: cursorSortTimestamp,
|
|
914
942
|
},
|
|
915
943
|
{ direction: "desc", expr: posts.id, value: cursorPost.id },
|
|
916
|
-
])
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
return buildLexicographicCursorCondition([
|
|
920
|
-
{ direction: "desc", expr: pinnedSortExpr, value: cursorPinnedAt },
|
|
921
|
-
{
|
|
922
|
-
direction: "desc",
|
|
923
|
-
expr: sortTimestampSortExpr,
|
|
924
|
-
value: cursorSortTimestamp,
|
|
925
|
-
},
|
|
926
|
-
{ direction: "desc", expr: posts.id, value: cursorPost.id },
|
|
927
|
-
]);
|
|
944
|
+
]),
|
|
945
|
+
);
|
|
928
946
|
}
|
|
929
947
|
|
|
930
948
|
if (filters.sortOrder === "oldest") {
|
|
931
|
-
return buildLexicographicCursorCondition(
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
949
|
+
return buildLexicographicCursorCondition(
|
|
950
|
+
withPinnedSortKey([
|
|
951
|
+
{
|
|
952
|
+
direction: "asc",
|
|
953
|
+
expr: sortTimestampSortExpr,
|
|
954
|
+
value: cursorSortTimestamp,
|
|
955
|
+
},
|
|
956
|
+
{ direction: "asc", expr: posts.id, value: cursorPost.id },
|
|
957
|
+
]),
|
|
958
|
+
);
|
|
940
959
|
}
|
|
941
960
|
|
|
942
961
|
if (filters.sortOrder === "rating_desc") {
|
|
943
|
-
return buildLexicographicCursorCondition(
|
|
944
|
-
|
|
962
|
+
return buildLexicographicCursorCondition(
|
|
963
|
+
withPinnedSortKey([
|
|
964
|
+
{
|
|
965
|
+
direction: "desc",
|
|
966
|
+
expr: ratingPresenceExpr,
|
|
967
|
+
value: cursorRatingPresence,
|
|
968
|
+
},
|
|
969
|
+
{ direction: "desc", expr: ratingSortExpr, value: cursorRating },
|
|
970
|
+
{
|
|
971
|
+
direction: "desc",
|
|
972
|
+
expr: sortTimestampSortExpr,
|
|
973
|
+
value: cursorSortTimestamp,
|
|
974
|
+
},
|
|
975
|
+
{ direction: "desc", expr: posts.id, value: cursorPost.id },
|
|
976
|
+
]),
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
return buildLexicographicCursorCondition(
|
|
981
|
+
withPinnedSortKey([
|
|
945
982
|
{
|
|
946
983
|
direction: "desc",
|
|
947
984
|
expr: ratingPresenceExpr,
|
|
948
985
|
value: cursorRatingPresence,
|
|
949
986
|
},
|
|
950
|
-
{ direction: "
|
|
987
|
+
{ direction: "asc", expr: ratingSortExpr, value: cursorRating },
|
|
951
988
|
{
|
|
952
989
|
direction: "desc",
|
|
953
990
|
expr: sortTimestampSortExpr,
|
|
954
991
|
value: cursorSortTimestamp,
|
|
955
992
|
},
|
|
956
993
|
{ direction: "desc", expr: posts.id, value: cursorPost.id },
|
|
957
|
-
])
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
return buildLexicographicCursorCondition([
|
|
961
|
-
{ direction: "desc", expr: pinnedSortExpr, value: cursorPinnedAt },
|
|
962
|
-
{
|
|
963
|
-
direction: "desc",
|
|
964
|
-
expr: ratingPresenceExpr,
|
|
965
|
-
value: cursorRatingPresence,
|
|
966
|
-
},
|
|
967
|
-
{ direction: "asc", expr: ratingSortExpr, value: cursorRating },
|
|
968
|
-
{
|
|
969
|
-
direction: "desc",
|
|
970
|
-
expr: sortTimestampSortExpr,
|
|
971
|
-
value: cursorSortTimestamp,
|
|
972
|
-
},
|
|
973
|
-
{ direction: "desc", expr: posts.id, value: cursorPost.id },
|
|
974
|
-
]);
|
|
994
|
+
]),
|
|
995
|
+
);
|
|
975
996
|
}
|
|
976
997
|
|
|
977
998
|
function toPost(
|
|
@@ -1328,6 +1349,9 @@ export function createPostService(
|
|
|
1328
1349
|
const pinnedSortExpr = sql<number>`coalesce(${posts.pinnedAt}, -1)`;
|
|
1329
1350
|
const featuredSortExpr = sql<number>`coalesce(${posts.featuredAt}, -1)`;
|
|
1330
1351
|
const sortTimestampSortExpr = sql<number>`coalesce(${sortTimestamp}, -1)`;
|
|
1352
|
+
const pinnedOrder = filters.ignorePinnedSort
|
|
1353
|
+
? []
|
|
1354
|
+
: [desc(pinnedSortExpr)];
|
|
1331
1355
|
|
|
1332
1356
|
const baseQuery = db
|
|
1333
1357
|
.select()
|
|
@@ -1338,7 +1362,7 @@ export function createPostService(
|
|
|
1338
1362
|
let query =
|
|
1339
1363
|
filters.featured || filters.sortOrder === undefined
|
|
1340
1364
|
? baseQuery.orderBy(
|
|
1341
|
-
|
|
1365
|
+
...pinnedOrder,
|
|
1342
1366
|
filters.featured
|
|
1343
1367
|
? desc(featuredSortExpr)
|
|
1344
1368
|
: desc(sortTimestampSortExpr),
|
|
@@ -1346,20 +1370,20 @@ export function createPostService(
|
|
|
1346
1370
|
)
|
|
1347
1371
|
: filters.sortOrder === "oldest"
|
|
1348
1372
|
? baseQuery.orderBy(
|
|
1349
|
-
|
|
1373
|
+
...pinnedOrder,
|
|
1350
1374
|
asc(sortTimestampSortExpr),
|
|
1351
1375
|
asc(posts.id),
|
|
1352
1376
|
)
|
|
1353
1377
|
: filters.sortOrder === "rating_desc"
|
|
1354
1378
|
? baseQuery.orderBy(
|
|
1355
|
-
|
|
1379
|
+
...pinnedOrder,
|
|
1356
1380
|
desc(ratingPresence),
|
|
1357
1381
|
desc(posts.rating),
|
|
1358
1382
|
desc(sortTimestampSortExpr),
|
|
1359
1383
|
desc(posts.id),
|
|
1360
1384
|
)
|
|
1361
1385
|
: baseQuery.orderBy(
|
|
1362
|
-
|
|
1386
|
+
...pinnedOrder,
|
|
1363
1387
|
desc(ratingPresence),
|
|
1364
1388
|
asc(posts.rating),
|
|
1365
1389
|
desc(sortTimestampSortExpr),
|
|
@@ -2950,6 +2974,9 @@ export function createPostService(
|
|
|
2950
2974
|
sql<number>`MAX(coalesce(${postCollections.pinnedAt}, -1))`.as(
|
|
2951
2975
|
"collection_pinned_at",
|
|
2952
2976
|
);
|
|
2977
|
+
const pinnedOrder = options.ignoreCollectionPinnedSort
|
|
2978
|
+
? []
|
|
2979
|
+
: [desc(collectionPinnedAt)];
|
|
2953
2980
|
|
|
2954
2981
|
let query = db
|
|
2955
2982
|
.select({
|
|
@@ -2968,11 +2995,7 @@ export function createPostService(
|
|
|
2968
2995
|
)
|
|
2969
2996
|
.where(and(...conditions))
|
|
2970
2997
|
.groupBy(posts.threadId)
|
|
2971
|
-
.orderBy(
|
|
2972
|
-
desc(collectionPinnedAt),
|
|
2973
|
-
desc(threadActivityAt),
|
|
2974
|
-
desc(posts.threadId),
|
|
2975
|
-
);
|
|
2998
|
+
.orderBy(...pinnedOrder, desc(threadActivityAt), desc(posts.threadId));
|
|
2976
2999
|
|
|
2977
3000
|
if (options.limit !== undefined) {
|
|
2978
3001
|
query = query.limit(options.limit) as typeof query;
|
package/src/services/settings.ts
CHANGED
|
@@ -46,7 +46,6 @@ export interface GeneralSettingsData {
|
|
|
46
46
|
dashboardLanguage?: string;
|
|
47
47
|
cjkSerifFont: string;
|
|
48
48
|
showJantBrandingOnHome: boolean;
|
|
49
|
-
homeDefaultView?: FeedKind;
|
|
50
49
|
mainRssFeed?: FeedKind;
|
|
51
50
|
timeZone: string;
|
|
52
51
|
}
|
|
@@ -404,15 +403,6 @@ export function createSettingsService(
|
|
|
404
403
|
oldCjkSerifFont: opts.oldCjkSerifFont,
|
|
405
404
|
});
|
|
406
405
|
|
|
407
|
-
// Homepage default view: only update if provided (may be managed separately)
|
|
408
|
-
if (data.homeDefaultView !== undefined) {
|
|
409
|
-
if (data.homeDefaultView === "featured") {
|
|
410
|
-
await this.set("HOME_DEFAULT_VIEW", data.homeDefaultView);
|
|
411
|
-
} else {
|
|
412
|
-
await this.remove("HOME_DEFAULT_VIEW");
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
406
|
await this.updateFeedSettings({ mainRssFeed: data.mainRssFeed });
|
|
417
407
|
|
|
418
408
|
return {
|
|
@@ -15,7 +15,6 @@ import { SETTINGS_KEYS } from "../lib/constants.js";
|
|
|
15
15
|
import { ConflictError, NotFoundError } from "../lib/errors.js";
|
|
16
16
|
import { createEntityId } from "../lib/ids.js";
|
|
17
17
|
import { getConfiguredSingleSiteUrl } from "../lib/env.js";
|
|
18
|
-
import { getHomeDefaultViewFromNavItems } from "../lib/navigation.js";
|
|
19
18
|
import { resolveConfig } from "../lib/resolve-config.js";
|
|
20
19
|
import { buildThemeStyle } from "../lib/theme.js";
|
|
21
20
|
import { now } from "../lib/time.js";
|
|
@@ -793,7 +792,6 @@ export function createSiteAdminService(
|
|
|
793
792
|
siteDescription: appConfig.siteDescription,
|
|
794
793
|
siteLanguage: appConfig.siteLanguage,
|
|
795
794
|
showJantBrandingOnHome: appConfig.showJantBrandingOnHome,
|
|
796
|
-
homeDefaultView: getHomeDefaultViewFromNavItems(navItemList),
|
|
797
795
|
mainRssFeed: appConfig.mainRssFeed,
|
|
798
796
|
siteFooter: appConfig.siteFooter,
|
|
799
797
|
showHeaderAvatar: appConfig.showHeaderAvatar,
|
|
@@ -52,10 +52,6 @@ const RELAY_MULTIPART_THRESHOLD = 95 * 1024 * 1024;
|
|
|
52
52
|
const RELAY_MULTIPART_PART_SIZE = 50 * 1024 * 1024;
|
|
53
53
|
const IMMUTABLE_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
54
54
|
const DEFAULT_EXPIRED_UPLOAD_CLEANUP_LIMIT = 20;
|
|
55
|
-
// Grace window before a finalized-but-unattached media row (uploaded during
|
|
56
|
-
// compose, `postId IS NULL`) is reaped. Generous enough that an in-progress
|
|
57
|
-
// compose/edit is never affected; based on `createdAt`, no heartbeat needed.
|
|
58
|
-
const ORPHAN_MEDIA_GRACE_SECONDS = 7 * 24 * 60 * 60;
|
|
59
55
|
type CleanupableUploadSessionState = "pending" | "uploaded" | "failed";
|
|
60
56
|
const CLEANUPABLE_UPLOAD_SESSION_STATES = [
|
|
61
57
|
"pending",
|
|
@@ -766,19 +762,6 @@ export function createUploadSessionService(
|
|
|
766
762
|
deletedSessions += 1;
|
|
767
763
|
}
|
|
768
764
|
|
|
769
|
-
// Reap finalized media that was uploaded during compose but never
|
|
770
|
-
// attached to a post (`postId IS NULL`) past the grace window. This now
|
|
771
|
-
// soft-deletes via the media service: the DB row is removed and the
|
|
772
|
-
// storage object is enqueued for deferred deletion. Bounded by the same
|
|
773
|
-
// per-run `limit`; any backlog drains over subsequent runs.
|
|
774
|
-
const orphanIds = await media.listOrphanedMediaIds({
|
|
775
|
-
before: now() - ORPHAN_MEDIA_GRACE_SECONDS,
|
|
776
|
-
limit,
|
|
777
|
-
});
|
|
778
|
-
if (orphanIds.length > 0) {
|
|
779
|
-
await media.deleteByIds(orphanIds, deps.storage);
|
|
780
|
-
}
|
|
781
|
-
|
|
782
765
|
// Physically delete storage objects whose recycle window has elapsed.
|
|
783
766
|
// Skips any object a live media row still references (re-uploads).
|
|
784
767
|
const purgedStorageObjects = await media.purgeDueStorageObjects(
|
|
@@ -789,7 +772,10 @@ export function createUploadSessionService(
|
|
|
789
772
|
return {
|
|
790
773
|
abortedMultipartUploads,
|
|
791
774
|
deletedSessions,
|
|
792
|
-
|
|
775
|
+
// Retained for response compatibility. Finalized media may be referenced
|
|
776
|
+
// from post bodies without a `media.post_id`, so upload cleanup must not
|
|
777
|
+
// infer liveness from attachment state.
|
|
778
|
+
deletedOrphanMedia: 0,
|
|
793
779
|
purgedStorageObjects,
|
|
794
780
|
};
|
|
795
781
|
},
|
package/src/styles/tokens.css
CHANGED
|
@@ -114,9 +114,9 @@
|
|
|
114
114
|
--type-body-leading: 1.7;
|
|
115
115
|
--type-display-leading: 1.15;
|
|
116
116
|
--type-heading-leading: 1.15;
|
|
117
|
-
--type-heading-weight: var(--fw-
|
|
117
|
+
--type-heading-weight: var(--fw-medium);
|
|
118
118
|
--type-heading-tracking: 0;
|
|
119
|
-
--type-display-weight: var(--fw-
|
|
119
|
+
--type-display-weight: var(--fw-medium);
|
|
120
120
|
--type-display-tracking: 0;
|
|
121
121
|
--type-label-weight: var(--fw-medium);
|
|
122
122
|
--type-label-tracking: 0.08em;
|
package/src/styles/ui.css
CHANGED
|
@@ -7008,6 +7008,14 @@
|
|
|
7008
7008
|
max-width: 100%;
|
|
7009
7009
|
}
|
|
7010
7010
|
|
|
7011
|
+
.compose-attachment:has(.compose-attachment-preview-fallback) {
|
|
7012
|
+
width: 112px;
|
|
7013
|
+
}
|
|
7014
|
+
|
|
7015
|
+
.compose-attachment:only-child:has(.compose-attachment-preview-fallback) {
|
|
7016
|
+
width: min(100%, 160px);
|
|
7017
|
+
}
|
|
7018
|
+
|
|
7011
7019
|
.compose-attachment-thumb {
|
|
7012
7020
|
position: relative;
|
|
7013
7021
|
border-radius: var(--media-radius, 0.5rem);
|
|
@@ -7015,6 +7023,13 @@
|
|
|
7015
7023
|
background-color: var(--site-nav-hover-bg);
|
|
7016
7024
|
}
|
|
7017
7025
|
|
|
7026
|
+
.compose-attachment:has(.compose-attachment-preview-fallback)
|
|
7027
|
+
.compose-attachment-thumb,
|
|
7028
|
+
.compose-attachment:has(.compose-attachment-preview-fallback)
|
|
7029
|
+
.compose-attachment-sortable {
|
|
7030
|
+
width: 100%;
|
|
7031
|
+
}
|
|
7032
|
+
|
|
7018
7033
|
.compose-attachment-sortable {
|
|
7019
7034
|
display: block;
|
|
7020
7035
|
cursor: grab;
|
|
@@ -7065,8 +7080,11 @@
|
|
|
7065
7080
|
display: flex;
|
|
7066
7081
|
align-items: center;
|
|
7067
7082
|
justify-content: center;
|
|
7068
|
-
|
|
7069
|
-
|
|
7083
|
+
flex-direction: column;
|
|
7084
|
+
gap: 6px;
|
|
7085
|
+
width: 100%;
|
|
7086
|
+
aspect-ratio: 4 / 3;
|
|
7087
|
+
padding: 10px;
|
|
7070
7088
|
border: 1px solid color-mix(in srgb, var(--site-divider) 88%, transparent);
|
|
7071
7089
|
border-radius: var(--media-radius, 0.5rem);
|
|
7072
7090
|
background: linear-gradient(
|
|
@@ -7078,9 +7096,24 @@
|
|
|
7078
7096
|
}
|
|
7079
7097
|
|
|
7080
7098
|
.compose-attachment-preview-fallback svg {
|
|
7099
|
+
width: 24px;
|
|
7100
|
+
height: 24px;
|
|
7081
7101
|
opacity: 0.82;
|
|
7082
7102
|
}
|
|
7083
7103
|
|
|
7104
|
+
.compose-attachment-preview-fallback-label {
|
|
7105
|
+
max-width: 100%;
|
|
7106
|
+
overflow: hidden;
|
|
7107
|
+
color: var(--site-text-secondary);
|
|
7108
|
+
font-family: var(--font-ui);
|
|
7109
|
+
font-size: var(--type-2xs);
|
|
7110
|
+
font-weight: var(--fw-medium);
|
|
7111
|
+
line-height: 1.2;
|
|
7112
|
+
text-align: center;
|
|
7113
|
+
text-overflow: ellipsis;
|
|
7114
|
+
white-space: nowrap;
|
|
7115
|
+
}
|
|
7116
|
+
|
|
7084
7117
|
/* Single image: constrain to container width. min-width keeps the preview
|
|
7085
7118
|
wide enough for the remove button; a very long image overflows it and is
|
|
7086
7119
|
cropped from the top (object-fit: cover) instead of shrinking to a sliver. */
|
|
@@ -7093,8 +7126,7 @@
|
|
|
7093
7126
|
}
|
|
7094
7127
|
|
|
7095
7128
|
.compose-attachment:only-child .compose-attachment-preview-fallback {
|
|
7096
|
-
width:
|
|
7097
|
-
max-width: 100%;
|
|
7129
|
+
width: 100%;
|
|
7098
7130
|
aspect-ratio: 4 / 3;
|
|
7099
7131
|
}
|
|
7100
7132
|
|
|
@@ -7107,8 +7139,13 @@
|
|
|
7107
7139
|
}
|
|
7108
7140
|
|
|
7109
7141
|
.compose-attachment:not(:only-child) .compose-attachment-preview-fallback {
|
|
7110
|
-
width:
|
|
7111
|
-
aspect-ratio:
|
|
7142
|
+
width: 100%;
|
|
7143
|
+
aspect-ratio: 4 / 3;
|
|
7144
|
+
}
|
|
7145
|
+
|
|
7146
|
+
.compose-attachment:only-child .compose-attachment-preview-fallback svg {
|
|
7147
|
+
width: 28px;
|
|
7148
|
+
height: 28px;
|
|
7112
7149
|
}
|
|
7113
7150
|
|
|
7114
7151
|
.compose-attachment-overlay {
|
|
@@ -7197,6 +7234,14 @@
|
|
|
7197
7234
|
box-shadow 0.18s ease;
|
|
7198
7235
|
}
|
|
7199
7236
|
|
|
7237
|
+
.compose-attachment:has(.compose-attachment-preview-fallback)
|
|
7238
|
+
.compose-attachment-remove {
|
|
7239
|
+
top: 5px;
|
|
7240
|
+
right: 5px;
|
|
7241
|
+
width: 24px;
|
|
7242
|
+
height: 24px;
|
|
7243
|
+
}
|
|
7244
|
+
|
|
7200
7245
|
.compose-attachment-remove:hover {
|
|
7201
7246
|
transform: translateY(-0.5px);
|
|
7202
7247
|
background-color: color-mix(in srgb, black 58%, transparent);
|
|
@@ -7222,6 +7267,13 @@
|
|
|
7222
7267
|
pointer-events: none;
|
|
7223
7268
|
}
|
|
7224
7269
|
|
|
7270
|
+
.compose-attachment:has(.compose-attachment-preview-fallback)
|
|
7271
|
+
.compose-attachment-remove
|
|
7272
|
+
svg {
|
|
7273
|
+
width: 12px;
|
|
7274
|
+
height: 12px;
|
|
7275
|
+
}
|
|
7276
|
+
|
|
7225
7277
|
.compose-attachment-alt {
|
|
7226
7278
|
border: none;
|
|
7227
7279
|
background: transparent;
|
|
@@ -7556,6 +7608,12 @@
|
|
|
7556
7608
|
max-width: none;
|
|
7557
7609
|
}
|
|
7558
7610
|
|
|
7611
|
+
.compose-reply-compose-layout
|
|
7612
|
+
.compose-editor-row
|
|
7613
|
+
.compose-attachment:has(.compose-attachment-preview-fallback) {
|
|
7614
|
+
width: var(--compose-reply-attachment-width);
|
|
7615
|
+
}
|
|
7616
|
+
|
|
7559
7617
|
.compose-reply-compose-layout
|
|
7560
7618
|
.compose-editor-row
|
|
7561
7619
|
.compose-attachment:only-child
|
|
@@ -7584,6 +7642,14 @@
|
|
|
7584
7642
|
aspect-ratio: auto;
|
|
7585
7643
|
}
|
|
7586
7644
|
|
|
7645
|
+
.compose-reply-compose-layout
|
|
7646
|
+
.compose-editor-row
|
|
7647
|
+
.compose-attachment-preview-fallback
|
|
7648
|
+
svg {
|
|
7649
|
+
width: 28px;
|
|
7650
|
+
height: 28px;
|
|
7651
|
+
}
|
|
7652
|
+
|
|
7587
7653
|
.compose-reply-compose-layout
|
|
7588
7654
|
.compose-editor-row
|
|
7589
7655
|
.compose-attachment-file-card,
|
|
@@ -9094,6 +9160,134 @@
|
|
|
9094
9160
|
display: block;
|
|
9095
9161
|
}
|
|
9096
9162
|
|
|
9163
|
+
.tiptap-image-figure[data-load-state="missing"] img {
|
|
9164
|
+
display: none;
|
|
9165
|
+
}
|
|
9166
|
+
|
|
9167
|
+
.tiptap-image-missing {
|
|
9168
|
+
display: none;
|
|
9169
|
+
align-items: center;
|
|
9170
|
+
justify-content: center;
|
|
9171
|
+
gap: 14px;
|
|
9172
|
+
min-height: clamp(132px, 26vw, 220px);
|
|
9173
|
+
padding: 18px;
|
|
9174
|
+
border-radius: 4px;
|
|
9175
|
+
background: linear-gradient(
|
|
9176
|
+
135deg,
|
|
9177
|
+
color-mix(in srgb, var(--site-elevated-bg) 88%, var(--site-accent)),
|
|
9178
|
+
var(--site-elevated-bg)
|
|
9179
|
+
);
|
|
9180
|
+
color: var(--site-text-secondary);
|
|
9181
|
+
text-align: left;
|
|
9182
|
+
}
|
|
9183
|
+
|
|
9184
|
+
.tiptap-image-figure[data-load-state="missing"] .tiptap-image-missing {
|
|
9185
|
+
display: flex;
|
|
9186
|
+
}
|
|
9187
|
+
|
|
9188
|
+
.tiptap-image-missing:focus-visible {
|
|
9189
|
+
outline: 2px solid var(--ring);
|
|
9190
|
+
outline-offset: 3px;
|
|
9191
|
+
}
|
|
9192
|
+
|
|
9193
|
+
.tiptap-image-missing-icon {
|
|
9194
|
+
display: inline-flex;
|
|
9195
|
+
align-items: center;
|
|
9196
|
+
justify-content: center;
|
|
9197
|
+
width: 38px;
|
|
9198
|
+
height: 38px;
|
|
9199
|
+
border-radius: 999px;
|
|
9200
|
+
border: 1px solid var(--site-divider);
|
|
9201
|
+
background: var(--compose-paper-bg);
|
|
9202
|
+
color: var(--site-text-secondary);
|
|
9203
|
+
flex-shrink: 0;
|
|
9204
|
+
}
|
|
9205
|
+
|
|
9206
|
+
.tiptap-image-missing-copy {
|
|
9207
|
+
display: grid;
|
|
9208
|
+
gap: 3px;
|
|
9209
|
+
min-width: 0;
|
|
9210
|
+
flex: 1 1 auto;
|
|
9211
|
+
}
|
|
9212
|
+
|
|
9213
|
+
.tiptap-image-missing-copy strong {
|
|
9214
|
+
color: var(--site-text-primary);
|
|
9215
|
+
font-family: var(--font-ui);
|
|
9216
|
+
font-size: var(--type-sm);
|
|
9217
|
+
line-height: 1.2;
|
|
9218
|
+
}
|
|
9219
|
+
|
|
9220
|
+
.tiptap-image-missing-source {
|
|
9221
|
+
max-width: 100%;
|
|
9222
|
+
overflow: hidden;
|
|
9223
|
+
color: var(--site-text-secondary);
|
|
9224
|
+
font-family: var(--font-mono);
|
|
9225
|
+
font-size: var(--type-2xs);
|
|
9226
|
+
line-height: 1.35;
|
|
9227
|
+
text-overflow: ellipsis;
|
|
9228
|
+
white-space: nowrap;
|
|
9229
|
+
}
|
|
9230
|
+
|
|
9231
|
+
.tiptap-image-missing-actions {
|
|
9232
|
+
display: flex;
|
|
9233
|
+
align-items: center;
|
|
9234
|
+
gap: 6px;
|
|
9235
|
+
flex-wrap: wrap;
|
|
9236
|
+
justify-content: flex-end;
|
|
9237
|
+
flex: 0 0 auto;
|
|
9238
|
+
}
|
|
9239
|
+
|
|
9240
|
+
.tiptap-image-missing-actions button {
|
|
9241
|
+
border: 1px solid var(--site-divider);
|
|
9242
|
+
border-radius: 5px;
|
|
9243
|
+
padding: 5px 8px;
|
|
9244
|
+
background: var(--compose-paper-bg);
|
|
9245
|
+
color: var(--site-text-primary);
|
|
9246
|
+
font-family: var(--font-ui);
|
|
9247
|
+
font-size: var(--type-2xs);
|
|
9248
|
+
line-height: 1.2;
|
|
9249
|
+
cursor: pointer;
|
|
9250
|
+
transition:
|
|
9251
|
+
border-color 0.16s ease,
|
|
9252
|
+
color 0.16s ease,
|
|
9253
|
+
background-color 0.16s ease;
|
|
9254
|
+
}
|
|
9255
|
+
|
|
9256
|
+
.tiptap-image-missing-actions button:hover {
|
|
9257
|
+
border-color: color-mix(
|
|
9258
|
+
in srgb,
|
|
9259
|
+
var(--site-accent) 45%,
|
|
9260
|
+
var(--site-divider)
|
|
9261
|
+
);
|
|
9262
|
+
color: var(--site-accent);
|
|
9263
|
+
}
|
|
9264
|
+
|
|
9265
|
+
.tiptap-image-missing-actions button:focus-visible {
|
|
9266
|
+
outline: 2px solid var(--ring);
|
|
9267
|
+
outline-offset: 2px;
|
|
9268
|
+
}
|
|
9269
|
+
|
|
9270
|
+
.tiptap-image-missing-actions button:disabled {
|
|
9271
|
+
cursor: not-allowed;
|
|
9272
|
+
opacity: 0.45;
|
|
9273
|
+
}
|
|
9274
|
+
|
|
9275
|
+
.tiptap-image-missing-delete {
|
|
9276
|
+
color: var(--destructive);
|
|
9277
|
+
}
|
|
9278
|
+
|
|
9279
|
+
@media (max-width: 640px) {
|
|
9280
|
+
.tiptap-image-missing {
|
|
9281
|
+
align-items: stretch;
|
|
9282
|
+
flex-direction: column;
|
|
9283
|
+
}
|
|
9284
|
+
|
|
9285
|
+
.tiptap-image-missing-actions,
|
|
9286
|
+
.tiptap-image-missing-actions button {
|
|
9287
|
+
width: 100%;
|
|
9288
|
+
}
|
|
9289
|
+
}
|
|
9290
|
+
|
|
9097
9291
|
/* Layout variants — in compact dialog, wide/full just fill the container */
|
|
9098
9292
|
.tiptap-image-figure[data-layout="wide"],
|
|
9099
9293
|
.tiptap-image-figure[data-layout="full"] {
|
package/src/types/bindings.ts
CHANGED
|
@@ -36,7 +36,6 @@ export interface Bindings {
|
|
|
36
36
|
SITE_DESCRIPTION?: EnvBindingValue;
|
|
37
37
|
SITE_LANGUAGE?: EnvBindingValue;
|
|
38
38
|
DASHBOARD_LANGUAGE?: EnvBindingValue;
|
|
39
|
-
HOME_DEFAULT_VIEW?: EnvBindingValue;
|
|
40
39
|
MAIN_RSS_FEED?: EnvBindingValue;
|
|
41
40
|
TIME_ZONE?: EnvBindingValue;
|
|
42
41
|
SITE_FOOTER?: EnvBindingValue;
|
package/src/types/config.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Single Source of Truth for all configuration fields.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import type { FeedKind } from "./constants.js";
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
10
|
* Configuration Registry - Single Source of Truth
|
|
9
11
|
*
|
|
@@ -53,11 +55,6 @@ export const CONFIG_FIELDS = {
|
|
|
53
55
|
envOnly: false,
|
|
54
56
|
envKeys: ["CJK_SERIF_FONT"],
|
|
55
57
|
},
|
|
56
|
-
HOME_DEFAULT_VIEW: {
|
|
57
|
-
defaultValue: "latest",
|
|
58
|
-
envOnly: false,
|
|
59
|
-
envKeys: ["HOME_DEFAULT_VIEW"],
|
|
60
|
-
},
|
|
61
58
|
MAIN_RSS_FEED: {
|
|
62
59
|
defaultValue: "featured",
|
|
63
60
|
envOnly: false,
|
|
@@ -470,8 +467,7 @@ export interface AppConfig {
|
|
|
470
467
|
dashboardLanguage: string;
|
|
471
468
|
/** CJK serif font locale: "off", "zh-Hans", "zh-Hant", "ja", or "ko" */
|
|
472
469
|
cjkSerifFont: string;
|
|
473
|
-
|
|
474
|
-
mainRssFeed: string;
|
|
470
|
+
mainRssFeed: FeedKind;
|
|
475
471
|
/** Canonical IANA timezone identifier used for date/time display. */
|
|
476
472
|
timeZone: string;
|
|
477
473
|
siteFooter: string;
|
|
@@ -59,6 +59,34 @@ export const ComposeForm: FC<ComposeFormProps> = ({
|
|
|
59
59
|
"@context: Toast when several pasted remote images couldn't be rehosted; {count} is the number of images",
|
|
60
60
|
}),
|
|
61
61
|
),
|
|
62
|
+
brokenImageUnavailable: i18n._(
|
|
63
|
+
msg({
|
|
64
|
+
message: "Image unavailable",
|
|
65
|
+
comment:
|
|
66
|
+
"@context: Inline editor placeholder title shown when an image cannot load",
|
|
67
|
+
}),
|
|
68
|
+
),
|
|
69
|
+
brokenImageDelete: i18n._(
|
|
70
|
+
msg({
|
|
71
|
+
message: "Delete image",
|
|
72
|
+
comment:
|
|
73
|
+
"@context: Inline editor button label for removing a broken image",
|
|
74
|
+
}),
|
|
75
|
+
),
|
|
76
|
+
brokenImageReplace: i18n._(
|
|
77
|
+
msg({
|
|
78
|
+
message: "Replace image",
|
|
79
|
+
comment:
|
|
80
|
+
"@context: Inline editor button label for replacing a broken image",
|
|
81
|
+
}),
|
|
82
|
+
),
|
|
83
|
+
brokenImageOpen: i18n._(
|
|
84
|
+
msg({
|
|
85
|
+
message: "Open image URL",
|
|
86
|
+
comment:
|
|
87
|
+
"@context: Inline editor button label for opening the original URL of a broken image",
|
|
88
|
+
}),
|
|
89
|
+
),
|
|
62
90
|
note: i18n._(
|
|
63
91
|
msg({
|
|
64
92
|
message: "Note",
|
|
@@ -268,7 +268,7 @@ export function NavigationContent({
|
|
|
268
268
|
systemLinksDescription: i18n._(
|
|
269
269
|
msg({
|
|
270
270
|
message:
|
|
271
|
-
"
|
|
271
|
+
"Show or hide built-in destinations in the header and More menu.",
|
|
272
272
|
comment: "@context: Description for built-in nav toggles",
|
|
273
273
|
}),
|
|
274
274
|
),
|