@jant/core 0.6.10 → 0.6.12
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 +45 -20
- package/dist/app-B5P1b4TF.js +6 -0
- package/dist/{app-CGHkOdme.js → app-D-zAhayc.js} +847 -244
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/client-BUxR-E8O.css +2 -0
- package/dist/client/_assets/{client-DYrWuaIk.js → client-S1cdvulk.js} +1 -1
- package/dist/client/_assets/{client-auth-B5Re0uCd.js → client-auth-YW92ZH7u.js} +158 -97
- package/dist/{export-DY1v5Iqu.js → export-Ckwh4BiE.js} +96 -96
- package/dist/{github-sync-LefaslGJ.js → github-sync-BMmK3JZ0.js} +2 -2
- package/dist/{github-sync-2_T7nbOv.js → github-sync-Dl9wQMar.js} +1 -1
- package/dist/index.js +3 -3
- package/dist/node.js +4 -4
- package/package.json +1 -1
- package/src/__tests__/import-site-command.test.ts +75 -0
- package/src/__tests__/vite-dev-plugins.test.ts +34 -0
- package/src/client/__tests__/compose-launch.test.ts +50 -1
- package/src/client/__tests__/compose-shortcuts.test.ts +74 -18
- package/src/client/components/__tests__/jant-command-palette.test.ts +63 -0
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +33 -0
- package/src/client/components/__tests__/jant-compose-editor-rehost-notice.test.ts +81 -0
- package/src/client/components/__tests__/jant-nav-manager.test.ts +77 -1
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +6 -0
- package/src/client/components/__tests__/jant-settings-general.test.ts +71 -0
- package/src/client/components/compose-types.ts +4 -0
- package/src/client/components/jant-command-palette.ts +67 -2
- package/src/client/components/jant-compose-dialog.ts +8 -2
- package/src/client/components/jant-compose-editor.ts +111 -0
- package/src/client/components/jant-nav-manager.ts +142 -2
- package/src/client/components/jant-settings-general.ts +56 -0
- package/src/client/components/nav-manager-types.ts +14 -0
- package/src/client/components/settings-types.ts +30 -0
- package/src/client/compose-bridge.ts +25 -8
- package/src/client/compose-launch.ts +30 -0
- package/src/client/compose-shortcuts.ts +46 -3
- package/src/client/tiptap/__tests__/inline-image-upload.test.ts +143 -0
- package/src/client/tiptap/__tests__/paste-rehost-e2e.test.ts +65 -0
- package/src/client/tiptap/__tests__/rehost-images.test.ts +192 -0
- package/src/client/tiptap/create-editor.ts +3 -0
- package/src/client/tiptap/extensions.ts +4 -0
- package/src/client/tiptap/inline-image-upload.ts +174 -50
- package/src/client/tiptap/rehost-images.ts +108 -0
- package/src/i18n/__tests__/fallback.test.ts +24 -0
- package/src/i18n/locales/public/en.po +10 -0
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +10 -0
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +10 -0
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +54 -0
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +64 -10
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +56 -2
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/lib/__tests__/feed.test.ts +96 -10
- package/src/lib/__tests__/upload-sideload.test.ts +78 -0
- package/src/lib/__tests__/url-fetch.test.ts +181 -0
- package/src/lib/feed.ts +63 -3
- package/src/lib/post-display.ts +14 -8
- package/src/lib/upload.ts +111 -0
- package/src/lib/url-fetch.ts +263 -0
- package/src/preset.css +60 -18
- package/src/routes/api/__tests__/uploads.test.ts +63 -1
- package/src/routes/api/uploads.ts +52 -0
- package/src/routes/dash/settings.tsx +41 -2
- package/src/routes/pages/__tests__/draft-about-edit.test.ts +65 -0
- package/src/routes/pages/page.tsx +44 -12
- package/src/services/__tests__/about-page.test.ts +116 -0
- package/src/services/__tests__/media.test.ts +168 -1
- package/src/services/__tests__/navigation.test.ts +190 -1
- package/src/services/__tests__/post-timeline.test.ts +119 -2
- package/src/services/about-page.ts +143 -0
- package/src/services/export-theme/layouts/_default/rss.xml +2 -1
- package/src/services/export-theme/layouts/partials/feed-post-content.xml +25 -1
- package/src/services/export-theme/styles/main.css +46 -14
- package/src/services/index.ts +27 -17
- package/src/services/media.ts +111 -0
- package/src/services/navigation.ts +155 -1
- package/src/services/post.ts +24 -7
- package/src/styles/components.css +20 -0
- package/src/styles/tokens.css +26 -8
- package/src/styles/ui.css +149 -7
- package/src/types/views.ts +13 -0
- package/src/ui/compose/ComposeDialog.tsx +16 -0
- package/src/ui/dash/appearance/NavigationContent.tsx +57 -0
- package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +19 -0
- package/src/ui/dash/settings/GeneralContent.tsx +44 -0
- package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +8 -0
- package/src/ui/feed/__tests__/timeline-cards.test.ts +162 -1
- package/src/ui/layouts/BaseLayout.tsx +12 -0
- package/dist/app-D24n0DoH.js +0 -6
- package/dist/client/_assets/client-xWDl78yi.css +0 -2
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { createEntityId } from "../lib/ids.js";
|
|
16
16
|
import { ValidationError } from "../lib/errors.js";
|
|
17
17
|
import { now } from "../lib/time.js";
|
|
18
|
+
import { normalizePath } from "../lib/url.js";
|
|
18
19
|
import { COLLECTION_FRESHNESS_WINDOW_SECONDS } from "../types.js";
|
|
19
20
|
import type {
|
|
20
21
|
NavItem,
|
|
@@ -23,10 +24,15 @@ import type {
|
|
|
23
24
|
CreateNavItem,
|
|
24
25
|
UpdateNavItem,
|
|
25
26
|
SystemNavKey,
|
|
27
|
+
SuggestedNavLink,
|
|
26
28
|
} from "../types.js";
|
|
27
29
|
import { SYSTEM_NAV_KEYS } from "../types.js";
|
|
28
30
|
|
|
29
31
|
const POSITION_RETRY_ATTEMPTS = 5;
|
|
32
|
+
const SUGGESTED_NAV_LINK_CANDIDATES = [
|
|
33
|
+
{ key: "about", path: "/about", label: "About" },
|
|
34
|
+
{ key: "now", path: "/now", label: "Now" },
|
|
35
|
+
] as const;
|
|
30
36
|
|
|
31
37
|
// Re-export shared constraint detection — see db/dialect.ts
|
|
32
38
|
import { isUniqueConstraintError } from "../db/dialect.js";
|
|
@@ -45,6 +51,7 @@ export interface NavItemService {
|
|
|
45
51
|
afterId: string | null,
|
|
46
52
|
beforeId: string | null,
|
|
47
53
|
): Promise<NavItem | null>;
|
|
54
|
+
listSuggestedLinks(): Promise<SuggestedNavLink[]>;
|
|
48
55
|
getCollectionFreshness(collectionIds: string[]): Promise<Map<string, number>>;
|
|
49
56
|
}
|
|
50
57
|
|
|
@@ -53,7 +60,8 @@ export function createNavItemService(
|
|
|
53
60
|
siteId: string,
|
|
54
61
|
databaseSchema: DatabaseSchema = sqliteSchemaBundle,
|
|
55
62
|
): NavItemService {
|
|
56
|
-
const { navItems, postCollections, posts } =
|
|
63
|
+
const { navItems, postCollections, posts, pathRegistry, collections } =
|
|
64
|
+
databaseSchema;
|
|
57
65
|
|
|
58
66
|
const defaultSystemOrder = [
|
|
59
67
|
"latest",
|
|
@@ -121,6 +129,39 @@ export function createNavItemService(
|
|
|
121
129
|
};
|
|
122
130
|
}
|
|
123
131
|
|
|
132
|
+
function withLeadingSlash(path: string): string {
|
|
133
|
+
const normalized = normalizePath(path);
|
|
134
|
+
return normalized ? `/${normalized}` : "/";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function getComparableInternalPath(url: string): string | null {
|
|
138
|
+
if (
|
|
139
|
+
url.startsWith("http://") ||
|
|
140
|
+
url.startsWith("https://") ||
|
|
141
|
+
url.startsWith("//") ||
|
|
142
|
+
url.startsWith("mailto:") ||
|
|
143
|
+
url.startsWith("tel:") ||
|
|
144
|
+
url.startsWith("#")
|
|
145
|
+
) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const pathname = new URL(url, "https://jant.invalid").pathname;
|
|
151
|
+
return withLeadingSlash(pathname);
|
|
152
|
+
} catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function normalizeSuggestedLabel(
|
|
158
|
+
label: string | null | undefined,
|
|
159
|
+
fallback: string,
|
|
160
|
+
): string {
|
|
161
|
+
const trimmed = label?.trim();
|
|
162
|
+
return trimmed && trimmed.length > 0 ? trimmed : fallback;
|
|
163
|
+
}
|
|
164
|
+
|
|
124
165
|
async function getLastPosition(): Promise<string | null> {
|
|
125
166
|
const rows = await db
|
|
126
167
|
.select({ position: navItems.position })
|
|
@@ -414,6 +455,119 @@ export function createNavItemService(
|
|
|
414
455
|
throw new Error("Failed to assign a unique nav item position");
|
|
415
456
|
},
|
|
416
457
|
|
|
458
|
+
async listSuggestedLinks() {
|
|
459
|
+
const existingRows = await db
|
|
460
|
+
.select({
|
|
461
|
+
url: navItems.url,
|
|
462
|
+
collectionId: navItems.collectionId,
|
|
463
|
+
})
|
|
464
|
+
.from(navItems)
|
|
465
|
+
.where(eq(navItems.siteId, siteId));
|
|
466
|
+
const existingPaths = new Set(
|
|
467
|
+
existingRows.flatMap((item) => {
|
|
468
|
+
const path = getComparableInternalPath(item.url);
|
|
469
|
+
return path ? [path] : [];
|
|
470
|
+
}),
|
|
471
|
+
);
|
|
472
|
+
const existingCollectionIds = new Set(
|
|
473
|
+
existingRows.flatMap((item) =>
|
|
474
|
+
item.collectionId ? [item.collectionId] : [],
|
|
475
|
+
),
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
const suggestions: SuggestedNavLink[] = [];
|
|
479
|
+
|
|
480
|
+
for (const candidate of SUGGESTED_NAV_LINK_CANDIDATES) {
|
|
481
|
+
const path = withLeadingSlash(candidate.path);
|
|
482
|
+
if (existingPaths.has(path)) continue;
|
|
483
|
+
|
|
484
|
+
const pathRows = await db
|
|
485
|
+
.select()
|
|
486
|
+
.from(pathRegistry)
|
|
487
|
+
.where(
|
|
488
|
+
and(
|
|
489
|
+
eq(pathRegistry.siteId, siteId),
|
|
490
|
+
eq(pathRegistry.path, normalizePath(candidate.path)),
|
|
491
|
+
),
|
|
492
|
+
)
|
|
493
|
+
.limit(1);
|
|
494
|
+
const record = pathRows[0];
|
|
495
|
+
if (!record || record.kind === "redirect") continue;
|
|
496
|
+
|
|
497
|
+
if (record.postId) {
|
|
498
|
+
const postRows = await db
|
|
499
|
+
.select({
|
|
500
|
+
title: posts.title,
|
|
501
|
+
status: posts.status,
|
|
502
|
+
visibility: posts.visibility,
|
|
503
|
+
})
|
|
504
|
+
.from(posts)
|
|
505
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, record.postId)))
|
|
506
|
+
.limit(1);
|
|
507
|
+
const post = postRows[0];
|
|
508
|
+
if (
|
|
509
|
+
!post ||
|
|
510
|
+
post.status !== "published" ||
|
|
511
|
+
post.visibility === "private"
|
|
512
|
+
) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
suggestions.push({
|
|
517
|
+
key: candidate.key,
|
|
518
|
+
label: normalizeSuggestedLabel(post.title, candidate.label),
|
|
519
|
+
url: path,
|
|
520
|
+
targetType: "page",
|
|
521
|
+
navItemType: "link",
|
|
522
|
+
});
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (record.collectionId) {
|
|
527
|
+
if (existingCollectionIds.has(record.collectionId)) continue;
|
|
528
|
+
|
|
529
|
+
const collectionRows = await db
|
|
530
|
+
.select({
|
|
531
|
+
title: collections.title,
|
|
532
|
+
})
|
|
533
|
+
.from(collections)
|
|
534
|
+
.where(
|
|
535
|
+
and(
|
|
536
|
+
eq(collections.siteId, siteId),
|
|
537
|
+
eq(collections.id, record.collectionId),
|
|
538
|
+
),
|
|
539
|
+
)
|
|
540
|
+
.limit(1);
|
|
541
|
+
const collection = collectionRows[0];
|
|
542
|
+
if (!collection) continue;
|
|
543
|
+
|
|
544
|
+
suggestions.push({
|
|
545
|
+
key: candidate.key,
|
|
546
|
+
label: normalizeSuggestedLabel(collection.title, candidate.label),
|
|
547
|
+
url: path,
|
|
548
|
+
targetType: "collection",
|
|
549
|
+
navItemType: record.kind === "slug" ? "collection" : "link",
|
|
550
|
+
...(record.kind === "slug" && {
|
|
551
|
+
collectionId: record.collectionId,
|
|
552
|
+
}),
|
|
553
|
+
});
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (record.kind === "archive") {
|
|
558
|
+
suggestions.push({
|
|
559
|
+
key: candidate.key,
|
|
560
|
+
label: candidate.label,
|
|
561
|
+
url: path,
|
|
562
|
+
targetType: "archive",
|
|
563
|
+
navItemType: "link",
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return suggestions;
|
|
569
|
+
},
|
|
570
|
+
|
|
417
571
|
async getCollectionFreshness(collectionIds) {
|
|
418
572
|
if (collectionIds.length === 0) return new Map<string, number>();
|
|
419
573
|
|
package/src/services/post.ts
CHANGED
|
@@ -1105,6 +1105,21 @@ export function createPostService(
|
|
|
1105
1105
|
return conditions;
|
|
1106
1106
|
}
|
|
1107
1107
|
|
|
1108
|
+
function buildCollectionThreadActivityExpr(alias: string) {
|
|
1109
|
+
return sql<number>`MAX(
|
|
1110
|
+
COALESCE(
|
|
1111
|
+
(
|
|
1112
|
+
SELECT root.last_activity_at
|
|
1113
|
+
FROM post AS root
|
|
1114
|
+
WHERE root.site_id = ${siteId}
|
|
1115
|
+
AND root.id = ${posts.threadId}
|
|
1116
|
+
),
|
|
1117
|
+
${posts.publishedAt},
|
|
1118
|
+
${posts.updatedAt}
|
|
1119
|
+
)
|
|
1120
|
+
)`.as(alias);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1108
1123
|
function isMediaAttachmentInput(
|
|
1109
1124
|
attachment: PostAttachmentInput,
|
|
1110
1125
|
): attachment is Extract<PostAttachmentInput, { type: "media" }> {
|
|
@@ -2845,6 +2860,8 @@ export function createPostService(
|
|
|
2845
2860
|
sortOrder === "oldest"
|
|
2846
2861
|
? sql<number>`MIN(${posts.publishedAt})`.as("published_at")
|
|
2847
2862
|
: sql<number>`MAX(${posts.publishedAt})`.as("published_at");
|
|
2863
|
+
const threadActivityAt =
|
|
2864
|
+
buildCollectionThreadActivityExpr("thread_activity_at");
|
|
2848
2865
|
const ratingPresence = sql<number>`MAX(
|
|
2849
2866
|
CASE
|
|
2850
2867
|
WHEN ${posts.rating} IS NULL THEN 0
|
|
@@ -2864,6 +2881,7 @@ export function createPostService(
|
|
|
2864
2881
|
.select({
|
|
2865
2882
|
threadId: posts.threadId,
|
|
2866
2883
|
publishedAt,
|
|
2884
|
+
threadActivityAt,
|
|
2867
2885
|
collectionPinnedAt,
|
|
2868
2886
|
ratingPresence,
|
|
2869
2887
|
ratingValue,
|
|
@@ -2891,12 +2909,12 @@ export function createPostService(
|
|
|
2891
2909
|
desc(collectionPinnedAt),
|
|
2892
2910
|
desc(ratingPresence),
|
|
2893
2911
|
desc(ratingValue),
|
|
2894
|
-
desc(
|
|
2912
|
+
desc(threadActivityAt),
|
|
2895
2913
|
desc(posts.threadId),
|
|
2896
2914
|
)
|
|
2897
2915
|
: baseQuery.orderBy(
|
|
2898
2916
|
desc(collectionPinnedAt),
|
|
2899
|
-
desc(
|
|
2917
|
+
desc(threadActivityAt),
|
|
2900
2918
|
desc(posts.threadId),
|
|
2901
2919
|
);
|
|
2902
2920
|
|
|
@@ -2923,9 +2941,8 @@ export function createPostService(
|
|
|
2923
2941
|
...buildThreadRootPageConditions(options),
|
|
2924
2942
|
buildCollectionMembershipCondition(collectionIds),
|
|
2925
2943
|
];
|
|
2926
|
-
const
|
|
2927
|
-
"
|
|
2928
|
-
);
|
|
2944
|
+
const threadActivityAt =
|
|
2945
|
+
buildCollectionThreadActivityExpr("thread_activity_at");
|
|
2929
2946
|
const collectedAt = sql<number>`MAX(${postCollections.createdAt})`.as(
|
|
2930
2947
|
"collected_at",
|
|
2931
2948
|
);
|
|
@@ -2937,7 +2954,7 @@ export function createPostService(
|
|
|
2937
2954
|
let query = db
|
|
2938
2955
|
.select({
|
|
2939
2956
|
threadId: posts.threadId,
|
|
2940
|
-
|
|
2957
|
+
threadActivityAt,
|
|
2941
2958
|
collectedAt,
|
|
2942
2959
|
collectionPinnedAt,
|
|
2943
2960
|
})
|
|
@@ -2953,7 +2970,7 @@ export function createPostService(
|
|
|
2953
2970
|
.groupBy(posts.threadId)
|
|
2954
2971
|
.orderBy(
|
|
2955
2972
|
desc(collectionPinnedAt),
|
|
2956
|
-
desc(
|
|
2973
|
+
desc(threadActivityAt),
|
|
2957
2974
|
desc(posts.threadId),
|
|
2958
2975
|
);
|
|
2959
2976
|
|
|
@@ -1163,6 +1163,26 @@ svg[stroke-width].icon-fine {
|
|
|
1163
1163
|
border-color: var(--color-border);
|
|
1164
1164
|
}
|
|
1165
1165
|
|
|
1166
|
+
.nav-suggestions-list {
|
|
1167
|
+
@apply flex flex-col gap-2;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
.nav-suggestion-item {
|
|
1171
|
+
@apply flex items-center justify-between gap-3 rounded-lg border px-3 py-2;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
.nav-suggestion-info {
|
|
1175
|
+
@apply flex min-w-0 flex-col;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
.nav-suggestion-title {
|
|
1179
|
+
@apply truncate text-sm font-medium;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
.nav-suggestion-meta {
|
|
1183
|
+
@apply truncate text-xs text-muted-foreground;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1166
1186
|
/* Collection picker dropdown menu */
|
|
1167
1187
|
.collection-picker {
|
|
1168
1188
|
@apply absolute left-0 z-10 mt-1.5 w-64 overflow-y-auto rounded-lg border py-1.5;
|
package/src/styles/tokens.css
CHANGED
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
* --type-subtitle 1.8rem (27px) — h3
|
|
66
66
|
* --type-body 1.4rem (21px) — running text, form inputs
|
|
67
67
|
* --type-secondary 1.1rem (16.5px) — meta, nav, sidenotes, UI labels
|
|
68
|
-
* --type-base 1.0rem (15px) —
|
|
69
|
-
* --type-sm 0.9rem (13.5px) —
|
|
68
|
+
* --type-base 1.0rem (15px) — UI buttons, controls
|
|
69
|
+
* --type-sm 0.9rem (13.5px) — small UI text
|
|
70
70
|
* --type-xs 0.8rem (12px) — captions, descriptions, chips
|
|
71
71
|
* --type-2xs 0.65rem (9.75px) — tiny labels, file sizes, badges
|
|
72
72
|
*/
|
|
@@ -84,18 +84,20 @@
|
|
|
84
84
|
mobile overrides below cap sizes for small screens.
|
|
85
85
|
--type-content-scale uniformly scales all content sizes
|
|
86
86
|
without affecting UI controls. */
|
|
87
|
-
--type-content-scale: 0.
|
|
87
|
+
--type-content-scale: 0.8;
|
|
88
88
|
--type-content-display: calc(var(--type-display) * var(--type-content-scale));
|
|
89
89
|
--type-content-title: calc(var(--type-title) * var(--type-content-scale));
|
|
90
90
|
--type-content-subtitle: calc(
|
|
91
91
|
var(--type-subtitle) * var(--type-content-scale)
|
|
92
92
|
);
|
|
93
93
|
--type-content-body: calc(var(--type-body) * var(--type-content-scale));
|
|
94
|
+
--type-content-quote: calc(var(--type-content-body) * 1.16);
|
|
95
|
+
--type-content-quote-leading: 1.4;
|
|
94
96
|
|
|
95
97
|
/* Semantic aliases */
|
|
96
|
-
--type-code: var(--type-base);
|
|
97
|
-
--type-code-block: var(--type-sm);
|
|
98
98
|
--type-body-size: var(--type-content-body);
|
|
99
|
+
--type-code: calc(var(--type-body-size) * 0.94);
|
|
100
|
+
--type-code-block: calc(var(--type-body-size) * 0.9);
|
|
99
101
|
--type-body-tracking: 0;
|
|
100
102
|
--type-ui-title: var(--type-secondary);
|
|
101
103
|
--type-ui-control: var(--type-base);
|
|
@@ -107,9 +109,9 @@
|
|
|
107
109
|
--type-thread-context: var(--type-base);
|
|
108
110
|
--type-thread-context-title: var(--type-secondary);
|
|
109
111
|
--type-thread-context-meta: var(--type-sm);
|
|
110
|
-
--feed-note-title-size: var(--type-content-
|
|
112
|
+
--feed-note-title-size: calc(var(--type-content-body) * 1.36);
|
|
111
113
|
--feed-note-title-leading: var(--type-heading-leading);
|
|
112
|
-
--type-body-leading: 1.
|
|
114
|
+
--type-body-leading: 1.7;
|
|
113
115
|
--type-display-leading: 1.15;
|
|
114
116
|
--type-heading-leading: 1.15;
|
|
115
117
|
--type-heading-weight: var(--fw-regular);
|
|
@@ -264,6 +266,22 @@
|
|
|
264
266
|
var(--site-text-primary) 12%,
|
|
265
267
|
transparent
|
|
266
268
|
);
|
|
269
|
+
--site-code-text: color-mix(
|
|
270
|
+
in srgb,
|
|
271
|
+
var(--site-reading-heading) 86%,
|
|
272
|
+
var(--site-reading-body)
|
|
273
|
+
);
|
|
274
|
+
--site-code-bg: color-mix(in srgb, var(--site-reading-meta) 12%, transparent);
|
|
275
|
+
--site-code-block-bg: color-mix(
|
|
276
|
+
in srgb,
|
|
277
|
+
var(--site-elevated-bg) 94%,
|
|
278
|
+
var(--site-nav-hover-bg)
|
|
279
|
+
);
|
|
280
|
+
--site-code-block-border: color-mix(
|
|
281
|
+
in srgb,
|
|
282
|
+
var(--site-divider) 62%,
|
|
283
|
+
transparent
|
|
284
|
+
);
|
|
267
285
|
--site-feed-divider-color: color-mix(
|
|
268
286
|
in srgb,
|
|
269
287
|
var(--site-text-secondary) 30%,
|
|
@@ -484,7 +502,7 @@
|
|
|
484
502
|
:root {
|
|
485
503
|
/* Content layer — tighter scale for mobile reading.
|
|
486
504
|
Ratio ≈ 1.88 : 1.42 : 1.15 : 1 (display:title:subtitle:body).
|
|
487
|
-
At 15px root × 0.
|
|
505
|
+
At 15px root × 0.8 content-scale: 29.4 / 22.2 / 18 / 15.6px.
|
|
488
506
|
Body is floored to a 16px minimum below (max()) for readability;
|
|
489
507
|
the floor still yields to calc() when the user zooms the root up. */
|
|
490
508
|
--type-content-display: calc(2.45rem * var(--type-content-scale));
|
package/src/styles/ui.css
CHANGED
|
@@ -2237,13 +2237,13 @@
|
|
|
2237
2237
|
}
|
|
2238
2238
|
|
|
2239
2239
|
.post-header-block {
|
|
2240
|
-
margin-bottom:
|
|
2240
|
+
margin-bottom: 1.7rem;
|
|
2241
2241
|
}
|
|
2242
2242
|
|
|
2243
2243
|
.post-header-block-detail {
|
|
2244
2244
|
display: flex;
|
|
2245
2245
|
flex-direction: column;
|
|
2246
|
-
gap: 0.
|
|
2246
|
+
gap: 0.7rem;
|
|
2247
2247
|
}
|
|
2248
2248
|
|
|
2249
2249
|
.post-header-block .feed-link-title,
|
|
@@ -2944,8 +2944,8 @@
|
|
|
2944
2944
|
padding-left: 0;
|
|
2945
2945
|
font-family: var(--font-serif);
|
|
2946
2946
|
color: var(--site-text-primary);
|
|
2947
|
-
font-size: var(--type-content-
|
|
2948
|
-
line-height: var(--type-
|
|
2947
|
+
font-size: var(--type-content-quote);
|
|
2948
|
+
line-height: var(--type-content-quote-leading);
|
|
2949
2949
|
white-space: pre-line;
|
|
2950
2950
|
text-wrap: pretty;
|
|
2951
2951
|
}
|
|
@@ -4535,6 +4535,7 @@
|
|
|
4535
4535
|
--compose-title-input-size: var(--type-content-subtitle);
|
|
4536
4536
|
--compose-title-input-leading: var(--type-display-leading);
|
|
4537
4537
|
--compose-quote-input-size: var(--type-content-subtitle);
|
|
4538
|
+
--compose-quote-input-leading: 1.32;
|
|
4538
4539
|
--compose-inline-input-size: var(--type-ui-input);
|
|
4539
4540
|
--compose-panel-label-size: var(--type-ui-control);
|
|
4540
4541
|
--compose-panel-hint-size: var(--type-ui-hint);
|
|
@@ -5201,7 +5202,7 @@
|
|
|
5201
5202
|
font-size: var(--compose-quote-input-size);
|
|
5202
5203
|
font-style: normal;
|
|
5203
5204
|
font-weight: var(--fw-regular);
|
|
5204
|
-
line-height:
|
|
5205
|
+
line-height: var(--compose-quote-input-leading);
|
|
5205
5206
|
letter-spacing: -0.02em;
|
|
5206
5207
|
min-height: 9rem;
|
|
5207
5208
|
padding-top: 0.5rem;
|
|
@@ -7428,6 +7429,53 @@
|
|
|
7428
7429
|
position: relative;
|
|
7429
7430
|
}
|
|
7430
7431
|
|
|
7432
|
+
.compose-reply-compose-layout {
|
|
7433
|
+
--compose-title-input-size: var(--type-content-subtitle);
|
|
7434
|
+
--compose-title-input-leading: 1.35;
|
|
7435
|
+
--compose-quote-input-size: calc(var(--type-content-body) * 1.06);
|
|
7436
|
+
--compose-quote-input-leading: 1.42;
|
|
7437
|
+
--compose-inline-input-size: var(--type-base);
|
|
7438
|
+
}
|
|
7439
|
+
|
|
7440
|
+
.compose-reply-compose-layout .compose-quote-wrap {
|
|
7441
|
+
margin-top: 0.4rem;
|
|
7442
|
+
padding: 24px 18px 18px;
|
|
7443
|
+
border-radius: 0.95rem;
|
|
7444
|
+
}
|
|
7445
|
+
|
|
7446
|
+
.compose-reply-compose-layout .compose-link-url-wrap {
|
|
7447
|
+
margin-top: 0.4rem;
|
|
7448
|
+
}
|
|
7449
|
+
|
|
7450
|
+
.compose-reply-compose-layout .compose-quote-mark {
|
|
7451
|
+
top: 0.58rem;
|
|
7452
|
+
left: 0.9rem;
|
|
7453
|
+
width: 1.55rem;
|
|
7454
|
+
}
|
|
7455
|
+
|
|
7456
|
+
.compose-reply-compose-layout .compose-quote-text {
|
|
7457
|
+
min-height: 6.5rem;
|
|
7458
|
+
padding-top: 0.35rem;
|
|
7459
|
+
}
|
|
7460
|
+
|
|
7461
|
+
@media (min-width: 700px) {
|
|
7462
|
+
.compose-reply-compose-layout .compose-quote-wrap {
|
|
7463
|
+
padding: 25px 20px 18px;
|
|
7464
|
+
border-radius: 1rem;
|
|
7465
|
+
}
|
|
7466
|
+
|
|
7467
|
+
.compose-reply-compose-layout .compose-quote-mark {
|
|
7468
|
+
top: 0.62rem;
|
|
7469
|
+
left: 1rem;
|
|
7470
|
+
width: 1.6rem;
|
|
7471
|
+
}
|
|
7472
|
+
|
|
7473
|
+
.compose-reply-compose-layout .compose-quote-text {
|
|
7474
|
+
min-height: 6.5rem;
|
|
7475
|
+
padding-top: 0.35rem;
|
|
7476
|
+
}
|
|
7477
|
+
}
|
|
7478
|
+
|
|
7431
7479
|
/* Continuous vertical line through the rail */
|
|
7432
7480
|
.compose-thread-layout::before {
|
|
7433
7481
|
content: "";
|
|
@@ -7490,6 +7538,78 @@
|
|
|
7490
7538
|
background: transparent;
|
|
7491
7539
|
}
|
|
7492
7540
|
|
|
7541
|
+
.compose-reply-compose-layout .compose-editor-row .compose-attachments-dock {
|
|
7542
|
+
--compose-reply-attachment-width: 96px;
|
|
7543
|
+
--compose-reply-attachment-height: 72px;
|
|
7544
|
+
|
|
7545
|
+
max-height: min(116px, 18dvh);
|
|
7546
|
+
padding-block: 4px 2px;
|
|
7547
|
+
}
|
|
7548
|
+
|
|
7549
|
+
.compose-reply-compose-layout .compose-editor-row .compose-attachments {
|
|
7550
|
+
gap: 6px;
|
|
7551
|
+
padding-block: 0;
|
|
7552
|
+
}
|
|
7553
|
+
|
|
7554
|
+
.compose-reply-compose-layout .compose-editor-row .compose-attachment {
|
|
7555
|
+
gap: 2px;
|
|
7556
|
+
max-width: none;
|
|
7557
|
+
}
|
|
7558
|
+
|
|
7559
|
+
.compose-reply-compose-layout
|
|
7560
|
+
.compose-editor-row
|
|
7561
|
+
.compose-attachment:only-child
|
|
7562
|
+
.compose-attachment-img,
|
|
7563
|
+
.compose-reply-compose-layout
|
|
7564
|
+
.compose-editor-row
|
|
7565
|
+
.compose-attachment:not(:only-child)
|
|
7566
|
+
.compose-attachment-img {
|
|
7567
|
+
width: var(--compose-reply-attachment-width);
|
|
7568
|
+
height: var(--compose-reply-attachment-height);
|
|
7569
|
+
min-width: 0;
|
|
7570
|
+
max-width: var(--compose-reply-attachment-width);
|
|
7571
|
+
max-height: var(--compose-reply-attachment-height);
|
|
7572
|
+
object-fit: cover;
|
|
7573
|
+
object-position: center;
|
|
7574
|
+
}
|
|
7575
|
+
|
|
7576
|
+
.compose-reply-compose-layout
|
|
7577
|
+
.compose-editor-row
|
|
7578
|
+
.compose-attachment-preview-fallback,
|
|
7579
|
+
.compose-reply-compose-layout
|
|
7580
|
+
.compose-editor-row
|
|
7581
|
+
.compose-attachment-processing {
|
|
7582
|
+
width: var(--compose-reply-attachment-width);
|
|
7583
|
+
height: var(--compose-reply-attachment-height);
|
|
7584
|
+
aspect-ratio: auto;
|
|
7585
|
+
}
|
|
7586
|
+
|
|
7587
|
+
.compose-reply-compose-layout
|
|
7588
|
+
.compose-editor-row
|
|
7589
|
+
.compose-attachment-file-card,
|
|
7590
|
+
.compose-reply-compose-layout
|
|
7591
|
+
.compose-editor-row
|
|
7592
|
+
.compose-attachment:not(:only-child)
|
|
7593
|
+
.compose-attachment-file-card {
|
|
7594
|
+
width: 112px;
|
|
7595
|
+
height: var(--compose-reply-attachment-height);
|
|
7596
|
+
gap: 4px;
|
|
7597
|
+
padding: 8px 10px;
|
|
7598
|
+
}
|
|
7599
|
+
|
|
7600
|
+
.compose-reply-compose-layout .compose-editor-row .compose-attachment-alt {
|
|
7601
|
+
padding: 1px 3px;
|
|
7602
|
+
font-size: var(--type-ui-micro);
|
|
7603
|
+
line-height: 1.2;
|
|
7604
|
+
}
|
|
7605
|
+
|
|
7606
|
+
.compose-reply-compose-layout .compose-editor-row .compose-attachment-remove {
|
|
7607
|
+
top: 4px;
|
|
7608
|
+
right: 4px;
|
|
7609
|
+
width: 20px;
|
|
7610
|
+
height: 20px;
|
|
7611
|
+
}
|
|
7612
|
+
|
|
7493
7613
|
/* Dot marker for thread positions */
|
|
7494
7614
|
.compose-thread-dot {
|
|
7495
7615
|
width: var(--site-thread-marker-size);
|
|
@@ -7977,6 +8097,10 @@
|
|
|
7977
8097
|
padding-top: 8px;
|
|
7978
8098
|
}
|
|
7979
8099
|
|
|
8100
|
+
.compose-reply-compose-layout .compose-thread-post-header + .compose-body {
|
|
8101
|
+
padding-top: 12px;
|
|
8102
|
+
}
|
|
8103
|
+
|
|
7980
8104
|
/* Thread format selector: pill-tag style (lighter than the main segmented) */
|
|
7981
8105
|
.compose-thread-segmented {
|
|
7982
8106
|
background: transparent;
|
|
@@ -8700,6 +8824,11 @@
|
|
|
8700
8824
|
font-family: var(--font-body);
|
|
8701
8825
|
}
|
|
8702
8826
|
|
|
8827
|
+
.compose-reply-compose-layout .compose-tiptap-body .tiptap {
|
|
8828
|
+
font-size: var(--type-content-body);
|
|
8829
|
+
line-height: var(--type-body-leading);
|
|
8830
|
+
}
|
|
8831
|
+
|
|
8703
8832
|
.compose-tiptap-thoughts .tiptap {
|
|
8704
8833
|
min-height: 72px;
|
|
8705
8834
|
}
|
|
@@ -8738,23 +8867,36 @@
|
|
|
8738
8867
|
|
|
8739
8868
|
.compose-tiptap-body .tiptap h2 {
|
|
8740
8869
|
font-size: var(--type-content-title);
|
|
8741
|
-
font-style: italic;
|
|
8742
8870
|
margin-top: 2.1rem;
|
|
8743
8871
|
margin-bottom: 1.4rem;
|
|
8744
8872
|
}
|
|
8745
8873
|
|
|
8746
8874
|
.compose-tiptap-body .tiptap h3 {
|
|
8747
8875
|
font-size: var(--type-content-subtitle);
|
|
8748
|
-
font-style: italic;
|
|
8749
8876
|
margin-top: 2rem;
|
|
8750
8877
|
margin-bottom: 1.4rem;
|
|
8751
8878
|
}
|
|
8752
8879
|
|
|
8880
|
+
.compose-reply-compose-layout
|
|
8881
|
+
.compose-tiptap-body
|
|
8882
|
+
.tiptap
|
|
8883
|
+
:is(h1, h2, h3, h4) {
|
|
8884
|
+
font-size: var(--type-secondary);
|
|
8885
|
+
line-height: 1.32;
|
|
8886
|
+
margin-top: 1.4rem;
|
|
8887
|
+
margin-bottom: 0.75rem;
|
|
8888
|
+
}
|
|
8889
|
+
|
|
8753
8890
|
.compose-tiptap-body .tiptap p {
|
|
8754
8891
|
margin-top: 1.4rem;
|
|
8755
8892
|
margin-bottom: 1.4rem;
|
|
8756
8893
|
}
|
|
8757
8894
|
|
|
8895
|
+
.compose-reply-compose-layout .compose-tiptap-body .tiptap p {
|
|
8896
|
+
margin-top: 1rem;
|
|
8897
|
+
margin-bottom: 1rem;
|
|
8898
|
+
}
|
|
8899
|
+
|
|
8758
8900
|
/* In inline-format compose (reply / edit-reply / thread — the editors wrapped
|
|
8759
8901
|
in .compose-editor-row), the format header sits right above the body, so the
|
|
8760
8902
|
first block's 1.4rem top margin reads as a gap between the format pills and
|
package/src/types/views.ts
CHANGED
|
@@ -171,6 +171,19 @@ export interface NavItemView {
|
|
|
171
171
|
freshAt?: number;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
export type SuggestedNavLinkTargetType = "page" | "collection" | "archive";
|
|
175
|
+
|
|
176
|
+
export type SuggestedNavLinkNavItemType = "link" | "collection";
|
|
177
|
+
|
|
178
|
+
export interface SuggestedNavLink {
|
|
179
|
+
key: string;
|
|
180
|
+
label: string;
|
|
181
|
+
url: string;
|
|
182
|
+
targetType: SuggestedNavLinkTargetType;
|
|
183
|
+
navItemType: SuggestedNavLinkNavItemType;
|
|
184
|
+
collectionId?: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
174
187
|
/**
|
|
175
188
|
* Search result from FTS5
|
|
176
189
|
*/
|
|
@@ -43,6 +43,22 @@ export const ComposeForm: FC<ComposeFormProps> = ({
|
|
|
43
43
|
comment: "@context: Close compose dialog",
|
|
44
44
|
}),
|
|
45
45
|
),
|
|
46
|
+
imageNotRehosted: i18n._(
|
|
47
|
+
msg({
|
|
48
|
+
message:
|
|
49
|
+
"An image couldn't be saved to your library — its original link was kept.",
|
|
50
|
+
comment:
|
|
51
|
+
"@context: Toast when a single pasted remote image couldn't be rehosted (e.g. blocked by the source's hotlink protection)",
|
|
52
|
+
}),
|
|
53
|
+
),
|
|
54
|
+
imagesNotRehosted: i18n._(
|
|
55
|
+
msg({
|
|
56
|
+
message:
|
|
57
|
+
"{count} images couldn't be saved to your library — their original links were kept.",
|
|
58
|
+
comment:
|
|
59
|
+
"@context: Toast when several pasted remote images couldn't be rehosted; {count} is the number of images",
|
|
60
|
+
}),
|
|
61
|
+
),
|
|
46
62
|
note: i18n._(
|
|
47
63
|
msg({
|
|
48
64
|
message: "Note",
|