@jant/core 0.6.11 → 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.
Files changed (139) hide show
  1. package/bin/commands/import-site.js +47 -30
  2. package/bin/commands/migrate-text-attachments.js +1 -3
  3. package/bin/commands/site/export.js +1 -3
  4. package/bin/commands/telegram/register-webhooks.js +9 -3
  5. package/bin/lib/site-snapshot.js +1 -5
  6. package/dist/app-CWJFPjuR.js +6 -0
  7. package/dist/{app-CpmficmQ.js → app-HnzmsaqU.js} +721 -473
  8. package/dist/client/.vite/manifest.json +3 -3
  9. package/dist/client/_assets/{client-Dd9U383b.js → client-3_OaxhTs.js} +1 -1
  10. package/dist/client/_assets/client-DCrcHVJl.css +2 -0
  11. package/dist/client/_assets/{client-auth-DkpSdIDz.js → client-auth-TsYq90xm.js} +625 -561
  12. package/dist/{export-Ba7NJImL.js → export-Dfbq9aot.js} +12 -15
  13. package/dist/{github-sync-Cb4_6_i7.js → github-sync--PSoE-Ne.js} +1 -1
  14. package/dist/{github-sync-BD4w2m8-.js → github-sync-BGTLx8Mf.js} +2 -2
  15. package/dist/index.js +3 -3
  16. package/dist/node.js +4 -4
  17. package/package.json +1 -1
  18. package/src/__tests__/export-service.test.ts +1 -2
  19. package/src/__tests__/helpers/app.ts +3 -0
  20. package/src/__tests__/helpers/export-fixtures.ts +0 -1
  21. package/src/__tests__/import-site-command.test.ts +76 -2
  22. package/src/__tests__/vite-dev-plugins.test.ts +34 -0
  23. package/src/client/__tests__/compose-launch.test.ts +50 -1
  24. package/src/client/__tests__/compose-shortcuts.test.ts +74 -18
  25. package/src/client/__tests__/site-header-nav.test.ts +36 -2
  26. package/src/client/components/__tests__/compose-title-from-heading.test.ts +57 -0
  27. package/src/client/components/__tests__/jant-command-palette.test.ts +63 -0
  28. package/src/client/components/__tests__/jant-compose-dialog.test.ts +33 -0
  29. package/src/client/components/__tests__/jant-compose-editor-rehost-notice.test.ts +20 -1
  30. package/src/client/components/__tests__/jant-compose-editor.test.ts +94 -2
  31. package/src/client/components/__tests__/jant-nav-manager.test.ts +181 -20
  32. package/src/client/components/__tests__/jant-settings-avatar.test.ts +6 -0
  33. package/src/client/components/__tests__/jant-settings-general.test.ts +72 -0
  34. package/src/client/components/compose-title-from-heading.ts +66 -0
  35. package/src/client/components/compose-types.ts +8 -0
  36. package/src/client/components/inline-image-issues.ts +17 -0
  37. package/src/client/components/jant-command-palette.ts +67 -2
  38. package/src/client/components/jant-compose-dialog.ts +10 -2
  39. package/src/client/components/jant-compose-editor.ts +78 -1
  40. package/src/client/components/jant-compose-fullscreen.ts +2 -0
  41. package/src/client/components/jant-nav-manager.ts +382 -152
  42. package/src/client/components/jant-settings-general.ts +56 -0
  43. package/src/client/components/nav-manager-types.ts +14 -10
  44. package/src/client/components/settings-types.ts +30 -0
  45. package/src/client/compose-launch.ts +30 -0
  46. package/src/client/compose-shortcuts.ts +46 -3
  47. package/src/client/site-header-nav.d.ts +1 -0
  48. package/src/client/site-header-nav.js +105 -47
  49. package/src/client/tiptap/__tests__/block-insertion.test.ts +35 -0
  50. package/src/client/tiptap/__tests__/image-input-rules.test.ts +117 -0
  51. package/src/client/tiptap/__tests__/rehost-images.test.ts +66 -13
  52. package/src/client/tiptap/create-editor.ts +7 -0
  53. package/src/client/tiptap/extensions.ts +7 -2
  54. package/src/client/tiptap/image-input-rules.ts +48 -0
  55. package/src/client/tiptap/image-node.ts +189 -7
  56. package/src/client/tiptap/rehost-images.ts +5 -1
  57. package/src/client-auth.ts +0 -1
  58. package/src/i18n/__tests__/fallback.test.ts +24 -0
  59. package/src/i18n/locales/public/en.po +20 -4
  60. package/src/i18n/locales/public/en.ts +1 -1
  61. package/src/i18n/locales/public/zh-Hans.po +22 -6
  62. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  63. package/src/i18n/locales/public/zh-Hant.po +22 -6
  64. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  65. package/src/i18n/locales/settings/en.po +77 -20
  66. package/src/i18n/locales/settings/en.ts +1 -1
  67. package/src/i18n/locales/settings/zh-Hans.po +85 -28
  68. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  69. package/src/i18n/locales/settings/zh-Hant.po +79 -22
  70. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  71. package/src/lib/__tests__/feed.test.ts +96 -10
  72. package/src/lib/__tests__/navigation.test.ts +4 -37
  73. package/src/lib/__tests__/resolve-config.test.ts +6 -1
  74. package/src/lib/__tests__/view.test.ts +6 -30
  75. package/src/lib/feed.ts +63 -3
  76. package/src/lib/github-sync-site-config.ts +0 -2
  77. package/src/lib/hugo-markdown.ts +1 -1
  78. package/src/lib/navigation.ts +5 -22
  79. package/src/lib/post-display.ts +14 -8
  80. package/src/lib/resolve-config.ts +9 -2
  81. package/src/lib/site-header-fragment.tsx +37 -0
  82. package/src/lib/view.ts +2 -7
  83. package/src/node/__tests__/cli-site-snapshot.test.ts +45 -45
  84. package/src/preset.css +60 -18
  85. package/src/routes/api/__tests__/nav-items.test.ts +26 -0
  86. package/src/routes/api/export.ts +0 -2
  87. package/src/routes/api/internal/__tests__/uploads.test.ts +9 -13
  88. package/src/routes/api/internal/sites.ts +5 -5
  89. package/src/routes/api/nav-items.ts +25 -4
  90. package/src/routes/dash/settings.tsx +48 -3
  91. package/src/routes/feed/__tests__/feed.test.ts +42 -0
  92. package/src/routes/feed/__tests__/sitemap.test.ts +2 -13
  93. package/src/routes/feed/feed.ts +1 -0
  94. package/src/routes/feed/sitemap.ts +2 -6
  95. package/src/routes/pages/__tests__/draft-about-edit.test.ts +65 -0
  96. package/src/routes/pages/archive.tsx +1 -0
  97. package/src/routes/pages/collection.tsx +1 -0
  98. package/src/routes/pages/featured.tsx +0 -4
  99. package/src/routes/pages/home.tsx +4 -51
  100. package/src/routes/pages/latest.tsx +2 -51
  101. package/src/routes/pages/page.tsx +44 -12
  102. package/src/services/__tests__/about-page.test.ts +116 -0
  103. package/src/services/__tests__/github-app-installations.test.ts +10 -10
  104. package/src/services/__tests__/navigation.test.ts +231 -1
  105. package/src/services/__tests__/post-timeline.test.ts +175 -2
  106. package/src/services/__tests__/post.test.ts +33 -0
  107. package/src/services/__tests__/settings.test.ts +5 -20
  108. package/src/services/about-page.ts +143 -0
  109. package/src/services/export-theme/assets/client-site.js +10 -10
  110. package/src/services/export-theme/layouts/_default/rss.xml +7 -16
  111. package/src/services/export-theme/layouts/index.html +26 -39
  112. package/src/services/export-theme/layouts/partials/feed-post-content.xml +25 -1
  113. package/src/services/export-theme/styles/main.css +46 -14
  114. package/src/services/export.ts +5 -10
  115. package/src/services/github-app-installations.ts +1 -3
  116. package/src/services/index.ts +27 -17
  117. package/src/services/navigation.ts +186 -1
  118. package/src/services/post.ts +103 -63
  119. package/src/services/settings.ts +0 -10
  120. package/src/services/site-admin.ts +0 -2
  121. package/src/services/upload-session.ts +4 -18
  122. package/src/styles/components.css +20 -0
  123. package/src/styles/tokens.css +28 -10
  124. package/src/styles/ui.css +349 -13
  125. package/src/types/bindings.ts +0 -1
  126. package/src/types/config.ts +3 -7
  127. package/src/types/views.ts +13 -0
  128. package/src/ui/compose/ComposeDialog.tsx +28 -0
  129. package/src/ui/dash/appearance/NavigationContent.tsx +58 -1
  130. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +19 -0
  131. package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
  132. package/src/ui/dash/settings/GeneralContent.tsx +44 -0
  133. package/src/ui/dash/settings/SettingsRootContent.tsx +3 -3
  134. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +8 -0
  135. package/src/ui/feed/__tests__/timeline-cards.test.ts +162 -1
  136. package/src/ui/layouts/SiteLayout.tsx +249 -216
  137. package/dist/app-DqKkZenB.js +0 -6
  138. package/dist/client/_assets/client-BhHHVvSY.css +0 -2
  139. package/src/client/nav-manager-bridge.ts +0 -54
package/src/styles/ui.css CHANGED
@@ -2237,13 +2237,13 @@
2237
2237
  }
2238
2238
 
2239
2239
  .post-header-block {
2240
- margin-bottom: 1.5rem;
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.55rem;
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-subtitle);
2948
- line-height: var(--type-heading-leading);
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: 1.32;
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;
@@ -7007,6 +7008,14 @@
7007
7008
  max-width: 100%;
7008
7009
  }
7009
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
+
7010
7019
  .compose-attachment-thumb {
7011
7020
  position: relative;
7012
7021
  border-radius: var(--media-radius, 0.5rem);
@@ -7014,6 +7023,13 @@
7014
7023
  background-color: var(--site-nav-hover-bg);
7015
7024
  }
7016
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
+
7017
7033
  .compose-attachment-sortable {
7018
7034
  display: block;
7019
7035
  cursor: grab;
@@ -7064,8 +7080,11 @@
7064
7080
  display: flex;
7065
7081
  align-items: center;
7066
7082
  justify-content: center;
7067
- width: 120px;
7068
- aspect-ratio: 1 / 1;
7083
+ flex-direction: column;
7084
+ gap: 6px;
7085
+ width: 100%;
7086
+ aspect-ratio: 4 / 3;
7087
+ padding: 10px;
7069
7088
  border: 1px solid color-mix(in srgb, var(--site-divider) 88%, transparent);
7070
7089
  border-radius: var(--media-radius, 0.5rem);
7071
7090
  background: linear-gradient(
@@ -7077,9 +7096,24 @@
7077
7096
  }
7078
7097
 
7079
7098
  .compose-attachment-preview-fallback svg {
7099
+ width: 24px;
7100
+ height: 24px;
7080
7101
  opacity: 0.82;
7081
7102
  }
7082
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
+
7083
7117
  /* Single image: constrain to container width. min-width keeps the preview
7084
7118
  wide enough for the remove button; a very long image overflows it and is
7085
7119
  cropped from the top (object-fit: cover) instead of shrinking to a sliver. */
@@ -7092,8 +7126,7 @@
7092
7126
  }
7093
7127
 
7094
7128
  .compose-attachment:only-child .compose-attachment-preview-fallback {
7095
- width: min(100%, 320px);
7096
- max-width: 100%;
7129
+ width: 100%;
7097
7130
  aspect-ratio: 4 / 3;
7098
7131
  }
7099
7132
 
@@ -7106,8 +7139,13 @@
7106
7139
  }
7107
7140
 
7108
7141
  .compose-attachment:not(:only-child) .compose-attachment-preview-fallback {
7109
- width: 150px;
7110
- aspect-ratio: 3 / 4;
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;
7111
7149
  }
7112
7150
 
7113
7151
  .compose-attachment-overlay {
@@ -7196,6 +7234,14 @@
7196
7234
  box-shadow 0.18s ease;
7197
7235
  }
7198
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
+
7199
7245
  .compose-attachment-remove:hover {
7200
7246
  transform: translateY(-0.5px);
7201
7247
  background-color: color-mix(in srgb, black 58%, transparent);
@@ -7221,6 +7267,13 @@
7221
7267
  pointer-events: none;
7222
7268
  }
7223
7269
 
7270
+ .compose-attachment:has(.compose-attachment-preview-fallback)
7271
+ .compose-attachment-remove
7272
+ svg {
7273
+ width: 12px;
7274
+ height: 12px;
7275
+ }
7276
+
7224
7277
  .compose-attachment-alt {
7225
7278
  border: none;
7226
7279
  background: transparent;
@@ -7428,6 +7481,53 @@
7428
7481
  position: relative;
7429
7482
  }
7430
7483
 
7484
+ .compose-reply-compose-layout {
7485
+ --compose-title-input-size: var(--type-content-subtitle);
7486
+ --compose-title-input-leading: 1.35;
7487
+ --compose-quote-input-size: calc(var(--type-content-body) * 1.06);
7488
+ --compose-quote-input-leading: 1.42;
7489
+ --compose-inline-input-size: var(--type-base);
7490
+ }
7491
+
7492
+ .compose-reply-compose-layout .compose-quote-wrap {
7493
+ margin-top: 0.4rem;
7494
+ padding: 24px 18px 18px;
7495
+ border-radius: 0.95rem;
7496
+ }
7497
+
7498
+ .compose-reply-compose-layout .compose-link-url-wrap {
7499
+ margin-top: 0.4rem;
7500
+ }
7501
+
7502
+ .compose-reply-compose-layout .compose-quote-mark {
7503
+ top: 0.58rem;
7504
+ left: 0.9rem;
7505
+ width: 1.55rem;
7506
+ }
7507
+
7508
+ .compose-reply-compose-layout .compose-quote-text {
7509
+ min-height: 6.5rem;
7510
+ padding-top: 0.35rem;
7511
+ }
7512
+
7513
+ @media (min-width: 700px) {
7514
+ .compose-reply-compose-layout .compose-quote-wrap {
7515
+ padding: 25px 20px 18px;
7516
+ border-radius: 1rem;
7517
+ }
7518
+
7519
+ .compose-reply-compose-layout .compose-quote-mark {
7520
+ top: 0.62rem;
7521
+ left: 1rem;
7522
+ width: 1.6rem;
7523
+ }
7524
+
7525
+ .compose-reply-compose-layout .compose-quote-text {
7526
+ min-height: 6.5rem;
7527
+ padding-top: 0.35rem;
7528
+ }
7529
+ }
7530
+
7431
7531
  /* Continuous vertical line through the rail */
7432
7532
  .compose-thread-layout::before {
7433
7533
  content: "";
@@ -7490,6 +7590,92 @@
7490
7590
  background: transparent;
7491
7591
  }
7492
7592
 
7593
+ .compose-reply-compose-layout .compose-editor-row .compose-attachments-dock {
7594
+ --compose-reply-attachment-width: 96px;
7595
+ --compose-reply-attachment-height: 72px;
7596
+
7597
+ max-height: min(116px, 18dvh);
7598
+ padding-block: 4px 2px;
7599
+ }
7600
+
7601
+ .compose-reply-compose-layout .compose-editor-row .compose-attachments {
7602
+ gap: 6px;
7603
+ padding-block: 0;
7604
+ }
7605
+
7606
+ .compose-reply-compose-layout .compose-editor-row .compose-attachment {
7607
+ gap: 2px;
7608
+ max-width: none;
7609
+ }
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
+
7617
+ .compose-reply-compose-layout
7618
+ .compose-editor-row
7619
+ .compose-attachment:only-child
7620
+ .compose-attachment-img,
7621
+ .compose-reply-compose-layout
7622
+ .compose-editor-row
7623
+ .compose-attachment:not(:only-child)
7624
+ .compose-attachment-img {
7625
+ width: var(--compose-reply-attachment-width);
7626
+ height: var(--compose-reply-attachment-height);
7627
+ min-width: 0;
7628
+ max-width: var(--compose-reply-attachment-width);
7629
+ max-height: var(--compose-reply-attachment-height);
7630
+ object-fit: cover;
7631
+ object-position: center;
7632
+ }
7633
+
7634
+ .compose-reply-compose-layout
7635
+ .compose-editor-row
7636
+ .compose-attachment-preview-fallback,
7637
+ .compose-reply-compose-layout
7638
+ .compose-editor-row
7639
+ .compose-attachment-processing {
7640
+ width: var(--compose-reply-attachment-width);
7641
+ height: var(--compose-reply-attachment-height);
7642
+ aspect-ratio: auto;
7643
+ }
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
+
7653
+ .compose-reply-compose-layout
7654
+ .compose-editor-row
7655
+ .compose-attachment-file-card,
7656
+ .compose-reply-compose-layout
7657
+ .compose-editor-row
7658
+ .compose-attachment:not(:only-child)
7659
+ .compose-attachment-file-card {
7660
+ width: 112px;
7661
+ height: var(--compose-reply-attachment-height);
7662
+ gap: 4px;
7663
+ padding: 8px 10px;
7664
+ }
7665
+
7666
+ .compose-reply-compose-layout .compose-editor-row .compose-attachment-alt {
7667
+ padding: 1px 3px;
7668
+ font-size: var(--type-ui-micro);
7669
+ line-height: 1.2;
7670
+ }
7671
+
7672
+ .compose-reply-compose-layout .compose-editor-row .compose-attachment-remove {
7673
+ top: 4px;
7674
+ right: 4px;
7675
+ width: 20px;
7676
+ height: 20px;
7677
+ }
7678
+
7493
7679
  /* Dot marker for thread positions */
7494
7680
  .compose-thread-dot {
7495
7681
  width: var(--site-thread-marker-size);
@@ -7977,6 +8163,10 @@
7977
8163
  padding-top: 8px;
7978
8164
  }
7979
8165
 
8166
+ .compose-reply-compose-layout .compose-thread-post-header + .compose-body {
8167
+ padding-top: 12px;
8168
+ }
8169
+
7980
8170
  /* Thread format selector: pill-tag style (lighter than the main segmented) */
7981
8171
  .compose-thread-segmented {
7982
8172
  background: transparent;
@@ -8700,6 +8890,11 @@
8700
8890
  font-family: var(--font-body);
8701
8891
  }
8702
8892
 
8893
+ .compose-reply-compose-layout .compose-tiptap-body .tiptap {
8894
+ font-size: var(--type-content-body);
8895
+ line-height: var(--type-body-leading);
8896
+ }
8897
+
8703
8898
  .compose-tiptap-thoughts .tiptap {
8704
8899
  min-height: 72px;
8705
8900
  }
@@ -8738,23 +8933,36 @@
8738
8933
 
8739
8934
  .compose-tiptap-body .tiptap h2 {
8740
8935
  font-size: var(--type-content-title);
8741
- font-style: italic;
8742
8936
  margin-top: 2.1rem;
8743
8937
  margin-bottom: 1.4rem;
8744
8938
  }
8745
8939
 
8746
8940
  .compose-tiptap-body .tiptap h3 {
8747
8941
  font-size: var(--type-content-subtitle);
8748
- font-style: italic;
8749
8942
  margin-top: 2rem;
8750
8943
  margin-bottom: 1.4rem;
8751
8944
  }
8752
8945
 
8946
+ .compose-reply-compose-layout
8947
+ .compose-tiptap-body
8948
+ .tiptap
8949
+ :is(h1, h2, h3, h4) {
8950
+ font-size: var(--type-secondary);
8951
+ line-height: 1.32;
8952
+ margin-top: 1.4rem;
8953
+ margin-bottom: 0.75rem;
8954
+ }
8955
+
8753
8956
  .compose-tiptap-body .tiptap p {
8754
8957
  margin-top: 1.4rem;
8755
8958
  margin-bottom: 1.4rem;
8756
8959
  }
8757
8960
 
8961
+ .compose-reply-compose-layout .compose-tiptap-body .tiptap p {
8962
+ margin-top: 1rem;
8963
+ margin-bottom: 1rem;
8964
+ }
8965
+
8758
8966
  /* In inline-format compose (reply / edit-reply / thread — the editors wrapped
8759
8967
  in .compose-editor-row), the format header sits right above the body, so the
8760
8968
  first block's 1.4rem top margin reads as a gap between the format pills and
@@ -8952,6 +9160,134 @@
8952
9160
  display: block;
8953
9161
  }
8954
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
+
8955
9291
  /* Layout variants — in compact dialog, wide/full just fill the container */
8956
9292
  .tiptap-image-figure[data-layout="wide"],
8957
9293
  .tiptap-image-figure[data-layout="full"] {
@@ -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;
@@ -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
- homeDefaultView: string;
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;
@@ -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
  */
@@ -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",
@@ -7,12 +7,14 @@ import { useLingui } from "../../../i18n/context.js";
7
7
  import type {
8
8
  CollectionsDirectoryData,
9
9
  NavItem,
10
+ SuggestedNavLink,
10
11
  SystemNavKey,
11
12
  } from "../../../types.js";
12
13
  import { SYSTEM_NAV_KEYS } from "../../../types.js";
13
14
  import type {
14
15
  NavManagerCollection,
15
16
  NavManagerLabels,
17
+ NavManagerSuggestedLink,
16
18
  SystemNavConfig,
17
19
  } from "../../../client/components/nav-manager-types.js";
18
20
  import { toPublicHref, toPublicPath } from "../../../lib/url.js";
@@ -30,12 +32,14 @@ import {
30
32
  export function NavigationContent({
31
33
  navItems,
32
34
  directoryData,
35
+ suggestedLinks,
33
36
  mainRssFeed,
34
37
  siteName,
35
38
  sitePathPrefix = "",
36
39
  }: {
37
40
  navItems: NavItem[];
38
41
  directoryData: CollectionsDirectoryData;
42
+ suggestedLinks: SuggestedNavLink[];
39
43
  mainRssFeed: string;
40
44
  siteName: string;
41
45
  sitePathPrefix?: string;
@@ -61,6 +65,27 @@ export function NavigationContent({
61
65
  );
62
66
  const moreLabel = i18n._(NAV_MORE_LABEL);
63
67
 
68
+ const suggestedTargetLabels = {
69
+ page: i18n._(
70
+ msg({
71
+ message: "Page",
72
+ comment: "@context: Suggested navigation link target type",
73
+ }),
74
+ ),
75
+ collection: i18n._(
76
+ msg({
77
+ message: "Collection",
78
+ comment: "@context: Suggested navigation link target type",
79
+ }),
80
+ ),
81
+ archive: i18n._(
82
+ msg({
83
+ message: "Archive",
84
+ comment: "@context: Suggested navigation link target type",
85
+ }),
86
+ ),
87
+ } satisfies Record<SuggestedNavLink["targetType"], string>;
88
+
64
89
  // Serialize nav items for the Lit component
65
90
  const itemsData = navItems.map((item) => {
66
91
  // System link URLs are always computed from constants, never from DB
@@ -110,6 +135,13 @@ export function NavigationContent({
110
135
  return result;
111
136
  })();
112
137
 
138
+ const suggestedLinksData: NavManagerSuggestedLink[] = suggestedLinks.map(
139
+ (link) => ({
140
+ ...link,
141
+ targetLabel: suggestedTargetLabels[link.targetType],
142
+ }),
143
+ );
144
+
113
145
  // Build system nav config array for the Lit component
114
146
  const systemNavData: SystemNavConfig[] = (
115
147
  Object.keys(SYSTEM_NAV_KEYS) as SystemNavKey[]
@@ -236,7 +268,7 @@ export function NavigationContent({
236
268
  systemLinksDescription: i18n._(
237
269
  msg({
238
270
  message:
239
- "Toggle built-in navigation items. Their order controls what shows in the header and which feed the homepage opens first.",
271
+ "Show or hide built-in destinations in the header and More menu.",
240
272
  comment: "@context: Description for built-in nav toggles",
241
273
  }),
242
274
  ),
@@ -297,6 +329,30 @@ export function NavigationContent({
297
329
  comment: "@context: Error toast when nav link fields are empty",
298
330
  }),
299
331
  ),
332
+ suggestedLinks: i18n._(
333
+ msg({
334
+ message: "Suggested links",
335
+ comment: "@context: Section heading for suggested nav links",
336
+ }),
337
+ ),
338
+ suggestedLinksDescription: i18n._(
339
+ msg({
340
+ message: "Add common destinations that already exist on your site.",
341
+ comment: "@context: Description for suggested nav links",
342
+ }),
343
+ ),
344
+ addSuggestedLink: i18n._(
345
+ msg({
346
+ message: "Add",
347
+ comment: "@context: Button to add a suggested nav link",
348
+ }),
349
+ ),
350
+ suggestedLinkAdded: i18n._(
351
+ msg({
352
+ message: "Link added to navigation.",
353
+ comment: "@context: Toast after adding a suggested nav link",
354
+ }),
355
+ ),
300
356
  collection: i18n._(
301
357
  msg({
302
358
  message: "collection",
@@ -356,6 +412,7 @@ export function NavigationContent({
356
412
  labels={escapeJson(labels)}
357
413
  system-nav-items={escapeJson(systemNavData)}
358
414
  collections={escapeJson(collectionsData)}
415
+ suggested-links={escapeJson(suggestedLinksData)}
359
416
  site-name={siteName}
360
417
  >
361
418
  {/* SSR fallback: static preview until JS hydrates */}