@jant/core 0.5.3 → 0.6.0

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 (95) hide show
  1. package/bin/commands/telegram/register-webhooks.js +93 -0
  2. package/dist/{app-C481ssbr.js → app-BIkkbVQk.js} +2252 -383
  3. package/dist/app-Bcr5_wZI.js +6 -0
  4. package/dist/client/.vite/manifest.json +3 -3
  5. package/dist/client/_assets/client-Bo7sKkAQ.js +274 -0
  6. package/dist/client/_assets/client-QHRvzZwk.css +2 -0
  7. package/dist/client/_assets/{client-auth-CfBiCAB7.js → client-auth-D1jDQgbH.js} +49 -49
  8. package/dist/{env-CgaH9Mut.js → env-C7e2Nlnt.js} +30 -1
  9. package/dist/{export-CR9Megtb.js → export-Bbn86HmS.js} +1 -1
  10. package/dist/{github-sync-DYZq9rQp.js → github-sync-CBQPRZ8H.js} +1 -1
  11. package/dist/{github-sync-8Vv06aCr.js → github-sync-dXsiZa_e.js} +2 -2
  12. package/dist/index.js +4 -4
  13. package/dist/node.js +61 -5
  14. package/package.json +2 -1
  15. package/src/__tests__/helpers/app.ts +15 -2
  16. package/src/app.tsx +3 -0
  17. package/src/client/components/jant-compose-editor.ts +72 -0
  18. package/src/client/thread-context.ts +146 -2
  19. package/src/client/tiptap/__tests__/link-toolbar.test.ts +1 -1
  20. package/src/client/tiptap/bubble-menu.ts +1 -16
  21. package/src/client/tiptap/extensions.ts +2 -6
  22. package/src/client/tiptap/link-toolbar.ts +0 -21
  23. package/src/client/tiptap/paste-media.ts +49 -33
  24. package/src/client/tiptap/toolbar-mode.ts +0 -43
  25. package/src/client/video-processor.ts +9 -0
  26. package/src/db/migrations/0022_old_gressill.sql +24 -0
  27. package/src/db/migrations/0023_broad_terror.sql +20 -0
  28. package/src/db/migrations/0024_red_the_twelve.sql +3 -0
  29. package/src/db/migrations/0025_exotic_wendell_rand.sql +1 -0
  30. package/src/db/migrations/meta/0022_snapshot.json +2267 -0
  31. package/src/db/migrations/meta/0023_snapshot.json +2396 -0
  32. package/src/db/migrations/meta/0024_snapshot.json +2417 -0
  33. package/src/db/migrations/meta/0025_snapshot.json +2424 -0
  34. package/src/db/migrations/meta/_journal.json +28 -0
  35. package/src/db/migrations/pg/0020_bizarre_smasher.sql +24 -0
  36. package/src/db/migrations/pg/0021_sharp_puppet_master.sql +20 -0
  37. package/src/db/migrations/pg/0022_blushing_blue_shield.sql +3 -0
  38. package/src/db/migrations/pg/0023_organic_zemo.sql +1 -0
  39. package/src/db/migrations/pg/meta/0020_snapshot.json +2904 -0
  40. package/src/db/migrations/pg/meta/0021_snapshot.json +3060 -0
  41. package/src/db/migrations/pg/meta/0022_snapshot.json +3078 -0
  42. package/src/db/migrations/pg/meta/0023_snapshot.json +3084 -0
  43. package/src/db/migrations/pg/meta/_journal.json +28 -0
  44. package/src/db/pg/schema.ts +82 -0
  45. package/src/db/schema.ts +90 -0
  46. package/src/i18n/coverage.generated.ts +2 -2
  47. package/src/i18n/locales/public/en.po +8 -0
  48. package/src/i18n/locales/public/zh-Hans.po +8 -0
  49. package/src/i18n/locales/public/zh-Hant.po +8 -0
  50. package/src/i18n/locales/settings/en.po +135 -0
  51. package/src/i18n/locales/settings/en.ts +1 -1
  52. package/src/i18n/locales/settings/zh-Hans.po +136 -1
  53. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  54. package/src/i18n/locales/settings/zh-Hant.po +136 -1
  55. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  56. package/src/lib/__tests__/image-dimensions.test.ts +314 -0
  57. package/src/lib/__tests__/mp4-track-flags.test.ts +117 -0
  58. package/src/lib/__tests__/telegram-entities.test.ts +180 -0
  59. package/src/lib/__tests__/telegram-pool-webhooks.test.ts +127 -0
  60. package/src/lib/env.ts +45 -0
  61. package/src/lib/ids.ts +3 -0
  62. package/src/lib/image-dimensions.ts +258 -0
  63. package/src/lib/mp4-track-flags.ts +71 -0
  64. package/src/lib/telegram-entities.ts +240 -0
  65. package/src/lib/telegram-pool-webhooks.ts +86 -0
  66. package/src/lib/telegram-settings-status.tsx +109 -0
  67. package/src/lib/telegram.ts +363 -0
  68. package/src/node/runtime.ts +6 -0
  69. package/src/routes/api/__tests__/telegram.test.ts +612 -0
  70. package/src/routes/api/telegram.ts +782 -0
  71. package/src/routes/api/upload-multipart.ts +34 -12
  72. package/src/routes/api/upload.ts +23 -2
  73. package/src/routes/dash/settings.tsx +131 -1
  74. package/src/routes/pages/__tests__/post-page-title.test.ts +70 -0
  75. package/src/routes/pages/page.tsx +3 -2
  76. package/src/runtime/cloudflare.ts +20 -9
  77. package/src/runtime/node.ts +20 -9
  78. package/src/runtime/site.ts +2 -1
  79. package/src/services/__tests__/telegram.test.ts +148 -0
  80. package/src/services/index.ts +9 -0
  81. package/src/services/telegram.ts +613 -0
  82. package/src/services/upload-session.ts +39 -12
  83. package/src/styles/tokens.css +1 -0
  84. package/src/styles/ui.css +134 -38
  85. package/src/types/app-context.ts +6 -0
  86. package/src/types/bindings.ts +3 -0
  87. package/src/types/config.ts +40 -0
  88. package/src/ui/dash/settings/SettingsRootContent.tsx +48 -17
  89. package/src/ui/dash/settings/TelegramContent.tsx +549 -0
  90. package/src/ui/feed/ThreadPreview.tsx +91 -38
  91. package/src/ui/feed/__tests__/thread-preview.test.ts +67 -5
  92. package/src/ui/pages/PostPage.tsx +78 -15
  93. package/dist/app-BgMwEN-M.js +0 -6
  94. package/dist/client/_assets/client-CJQYvkEx.js +0 -274
  95. package/dist/client/_assets/client-CQvi1Buw.css +0 -2
@@ -321,6 +321,7 @@
321
321
  transparent
322
322
  );
323
323
  --site-thread-item-spacing: 32px;
324
+ --site-thread-context-max-height: 160px;
324
325
  --site-thread-dot-ring: color-mix(
325
326
  in srgb,
326
327
  var(--site-accent) 16%,
package/src/styles/ui.css CHANGED
@@ -2624,6 +2624,113 @@
2624
2624
  var(--site-thread-dot-ring);
2625
2625
  }
2626
2626
 
2627
+ /* Collapsible ancestor context wrapper.
2628
+ Server-rendered with `data-collapsed`. Crop and fade are always active
2629
+ while data-collapsed is set; JS only toggles the Show more/less button
2630
+ visibility based on real overflow. Negative left margin + matching
2631
+ padding extends the shell's box past the content edge so the dot
2632
+ markers (which live at left:-31.5px on desktop) survive
2633
+ `overflow: hidden`. `box-sizing: content-box` keeps the inner content
2634
+ width identical to before. */
2635
+ .thread-context-shell {
2636
+ position: relative;
2637
+ box-sizing: content-box;
2638
+ margin-left: -40px;
2639
+ padding-left: 40px;
2640
+ transition: max-height 0.32s cubic-bezier(0.4, 0, 0.2, 1);
2641
+ }
2642
+
2643
+ .thread-context-shell[data-collapsed] {
2644
+ max-height: var(--site-thread-context-max-height);
2645
+ overflow: hidden;
2646
+ /* Soft fade at the bottom edge so the cap doesn't make a hard cut
2647
+ through text when content barely overflows. Combined with the
2648
+ shell's opacity:0.5 this produces a smooth "context drifts off"
2649
+ look instead of a guillotine slice right above the toggle. */
2650
+ -webkit-mask-image: linear-gradient(
2651
+ to bottom,
2652
+ black 0%,
2653
+ black 60%,
2654
+ transparent 100%
2655
+ );
2656
+ mask-image: linear-gradient(
2657
+ to bottom,
2658
+ black 0%,
2659
+ black 60%,
2660
+ transparent 100%
2661
+ );
2662
+ }
2663
+
2664
+ /* Push ancestor context into the visual background so the focused post
2665
+ below the shell (latest reply on the home feed, anchor post on the
2666
+ detail page) reads as the primary content. Opacity works on both real
2667
+ painted content AND empty space, sidestepping the gradient-on-empty
2668
+ visibility problem. The dots inside the shell get muted along with
2669
+ the content, which actually reinforces the "this is background" feel
2670
+ — the rail line itself lives on `.thread-group::before` and stays at
2671
+ full strength. */
2672
+ .thread-context-shell {
2673
+ opacity: 0.5;
2674
+ transition: opacity 0.22s ease;
2675
+ }
2676
+
2677
+ /* When the user expands the shell, restore full strength — they've
2678
+ opted in to reading the context. */
2679
+ .thread-context-shell:not([data-collapsed]) {
2680
+ opacity: 1;
2681
+ }
2682
+
2683
+ /* Hide the legacy gradient-overlay element — opacity replaces it. */
2684
+ .thread-context-fade {
2685
+ display: none;
2686
+ }
2687
+
2688
+ .thread-context-toggle[hidden] {
2689
+ display: none;
2690
+ }
2691
+
2692
+ .thread-context-toggle {
2693
+ display: inline-flex;
2694
+ align-items: center;
2695
+ gap: 0.3rem;
2696
+ margin: 0 0 6px;
2697
+ padding: 0.3rem 0;
2698
+ border: 0;
2699
+ background: transparent;
2700
+ color: var(--site-text-secondary);
2701
+ font-size: var(--type-thread-context-meta);
2702
+ line-height: 1.2;
2703
+ cursor: pointer;
2704
+ transition: color 0.18s ease;
2705
+ }
2706
+
2707
+ .thread-context-toggle:hover {
2708
+ color: var(--site-text-primary);
2709
+ }
2710
+
2711
+ .thread-context-toggle:focus-visible {
2712
+ outline: 2px solid var(--site-accent);
2713
+ outline-offset: 4px;
2714
+ border-radius: 2px;
2715
+ }
2716
+
2717
+ .thread-context-toggle-chevron {
2718
+ width: 10px;
2719
+ height: 10px;
2720
+ opacity: 0.7;
2721
+ transition:
2722
+ transform 0.2s ease,
2723
+ opacity 0.18s ease;
2724
+ }
2725
+
2726
+ .thread-context-toggle:hover .thread-context-toggle-chevron {
2727
+ opacity: 1;
2728
+ }
2729
+
2730
+ .thread-context-toggle[aria-expanded="true"] .thread-context-toggle-chevron {
2731
+ transform: rotate(180deg);
2732
+ }
2733
+
2627
2734
  .thread-item-gap {
2628
2735
  padding-top: 0.15rem;
2629
2736
  padding-bottom: 0.35rem;
@@ -2701,6 +2808,15 @@
2701
2808
  scroll-margin-top: 80px;
2702
2809
  }
2703
2810
 
2811
+ /* When a child post has an ancestor shell above it (toggle button sits
2812
+ directly before the current post), leave enough room above the scroll
2813
+ landing so the user can see the Show more button + a peek of the
2814
+ faded shell on initial load. Otherwise everything above is hidden in
2815
+ the viewport and the user has to discover it by scrolling up. */
2816
+ .thread-context-toggle + .thread-detail-item {
2817
+ scroll-margin-top: 152px;
2818
+ }
2819
+
2704
2820
  .thread-group-detail .thread-item-current::before {
2705
2821
  background-color: var(--site-accent);
2706
2822
  box-shadow:
@@ -4432,6 +4548,24 @@
4432
4548
  position: relative;
4433
4549
  }
4434
4550
 
4551
+ jant-compose-editor.compose-editor-dragover {
4552
+ position: relative;
4553
+ }
4554
+
4555
+ jant-compose-editor.compose-editor-dragover::after {
4556
+ content: "";
4557
+ position: absolute;
4558
+ inset: 6px;
4559
+ z-index: 5;
4560
+ pointer-events: none;
4561
+ border-radius: 14px;
4562
+ background: color-mix(in srgb, var(--site-accent) 4%, transparent);
4563
+ box-shadow:
4564
+ inset 0 0 0 1.5px color-mix(in srgb, var(--site-accent) 45%, transparent),
4565
+ inset 0 0 28px -12px
4566
+ color-mix(in srgb, var(--site-accent) 45%, transparent);
4567
+ }
4568
+
4435
4569
  .compose-edit-loading {
4436
4570
  display: flex;
4437
4571
  flex: 1;
@@ -9663,19 +9797,6 @@
9663
9797
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.15);
9664
9798
  }
9665
9799
 
9666
- .tiptap-bubble-menu-docked {
9667
- left: 50% !important;
9668
- top: auto !important;
9669
- bottom: calc(
9670
- env(safe-area-inset-bottom) + var(--tiptap-docked-offset, 16px)
9671
- );
9672
- transform: translateX(-50%);
9673
- gap: 4px;
9674
- padding: 6px;
9675
- border-radius: 12px;
9676
- max-width: calc(100vw - 24px);
9677
- }
9678
-
9679
9800
  .tiptap-bubble-menu::after {
9680
9801
  content: "";
9681
9802
  position: absolute;
@@ -9687,10 +9808,6 @@
9687
9808
  border-top: 5px solid var(--color-foreground);
9688
9809
  }
9689
9810
 
9690
- .tiptap-bubble-menu-docked::after {
9691
- display: none;
9692
- }
9693
-
9694
9811
  .tiptap-bubble-btn {
9695
9812
  display: flex;
9696
9813
  align-items: center;
@@ -9747,11 +9864,6 @@
9747
9864
  flex: 1;
9748
9865
  }
9749
9866
 
9750
- .tiptap-link-input-docked .tiptap-link-input-fields {
9751
- min-width: 0;
9752
- width: 100%;
9753
- }
9754
-
9755
9867
  .tiptap-link-input-text {
9756
9868
  border: none;
9757
9869
  outline: none;
@@ -9767,17 +9879,6 @@
9767
9879
  color: var(--color-muted-foreground);
9768
9880
  }
9769
9881
 
9770
- .tiptap-link-input-docked {
9771
- left: 50% !important;
9772
- top: auto !important;
9773
- bottom: calc(
9774
- env(safe-area-inset-bottom) + var(--tiptap-docked-offset, 16px)
9775
- );
9776
- transform: translateX(-50%);
9777
- width: min(calc(100vw - 24px), 420px);
9778
- max-width: calc(100vw - 24px);
9779
- }
9780
-
9781
9882
  .tiptap-link-input::before {
9782
9883
  content: "";
9783
9884
  position: absolute;
@@ -9800,11 +9901,6 @@
9800
9901
  border-top: 5px solid var(--color-background);
9801
9902
  }
9802
9903
 
9803
- .tiptap-link-input-docked::before,
9804
- .tiptap-link-input-docked::after {
9805
- display: none;
9806
- }
9807
-
9808
9904
  .tiptap-link-input-field {
9809
9905
  border: none;
9810
9906
  outline: none;
@@ -24,6 +24,12 @@ export type AppSession = Awaited<ReturnType<Auth["api"]["getSession"]>>;
24
24
 
25
25
  export interface AppVariables {
26
26
  services: Services;
27
+ /**
28
+ * Builds a `Services` object scoped to an arbitrary site. Used by
29
+ * host-agnostic handlers (e.g. the Telegram webhook) that resolve the
30
+ * target site from request data rather than the hostname.
31
+ */
32
+ servicesForSite: (siteId: string) => Services;
27
33
  hostedHandoff: HostedHandoffService;
28
34
  auth: Auth;
29
35
  currentSite: Site;
@@ -53,6 +53,9 @@ export interface Bindings {
53
53
  HOSTED_CONTROL_PLANE_PROVIDER_NAME?: EnvBindingValue;
54
54
  HOSTED_CONTROL_PLANE_SSO_SECRET?: EnvBindingValue;
55
55
  HOSTED_CONTROL_PLANE_INTERNAL_TOKEN?: EnvBindingValue;
56
+ // Telegram bot integration
57
+ TELEGRAM_BOT_TOKENS?: EnvBindingValue;
58
+ TELEGRAM_WEBHOOK_SECRET?: EnvBindingValue;
56
59
  // Timeline
57
60
  PAGE_SIZE?: EnvBindingValue;
58
61
  SEARCH_PAGE_SIZE?: EnvBindingValue;
@@ -396,6 +396,46 @@ export const CONFIG_FIELDS = {
396
396
  envOnly: true,
397
397
  envKeys: ["GITHUB_APP_WEBHOOK_SECRET"],
398
398
  },
399
+
400
+ // Telegram bot pool (env-only). When TELEGRAM_BOT_TOKENS is set the bot
401
+ // tokens are platform-managed (hosted, or a single-site operator who opts
402
+ // in) and the settings UI hides the token field. Comma-separated list of
403
+ // `<bot_id>:<secret>` tokens; TELEGRAM_WEBHOOK_SECRET is the shared
404
+ // `secret_token` used when registering every pool bot's webhook.
405
+ TELEGRAM_BOT_TOKENS: {
406
+ defaultValue: "",
407
+ envOnly: true,
408
+ envKeys: ["TELEGRAM_BOT_TOKENS"],
409
+ },
410
+ TELEGRAM_WEBHOOK_SECRET: {
411
+ defaultValue: "",
412
+ envOnly: true,
413
+ envKeys: ["TELEGRAM_WEBHOOK_SECRET"],
414
+ },
415
+
416
+ // Telegram bring-your-own bot (DB-only, single-site, managed via the
417
+ // Telegram settings page when TELEGRAM_BOT_TOKENS is not set).
418
+ TELEGRAM_BOT_TOKEN: {
419
+ defaultValue: "",
420
+ envOnly: false,
421
+ internal: true,
422
+ },
423
+ TELEGRAM_BOT_ID: {
424
+ defaultValue: "",
425
+ envOnly: false,
426
+ internal: true,
427
+ },
428
+ TELEGRAM_BOT_USERNAME: {
429
+ defaultValue: "",
430
+ envOnly: false,
431
+ internal: true,
432
+ },
433
+ /** Per-site `secret_token` for a bring-your-own bot's webhook. */
434
+ TELEGRAM_BOT_WEBHOOK_SECRET: {
435
+ defaultValue: "",
436
+ envOnly: false,
437
+ internal: true,
438
+ },
399
439
  } as const satisfies Record<string, ConfigField>;
400
440
 
401
441
  export type ConfigKey = keyof typeof CONFIG_FIELDS;
@@ -24,6 +24,7 @@ const ICONS = {
24
24
  key: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4"/><path d="m21 2-9.6 9.6"/><circle cx="7.5" cy="15.5" r="5.5"/></svg>`,
25
25
  shield: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/></svg>`,
26
26
  gitBranch: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="6" x2="6" y1="3" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>`,
27
+ send: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/></svg>`,
27
28
  };
28
29
 
29
30
  export function SettingsRootContent({
@@ -114,23 +115,6 @@ export function SettingsRootContent({
114
115
  }),
115
116
  )}
116
117
  />
117
- <SettingsDirectoryLink
118
- href={toPublicPath("/settings/github-sync", sitePathPrefix)}
119
- icon={ICONS.gitBranch}
120
- tone="subtle"
121
- name={i18n._(
122
- msg({
123
- message: "GitHub Sync",
124
- comment: "@context: Settings item — GitHub sync settings",
125
- }),
126
- )}
127
- description={i18n._(
128
- msg({
129
- message: "Back up and sync content with a GitHub repository",
130
- comment: "@context: Settings item description for GitHub sync",
131
- }),
132
- )}
133
- />
134
118
  </SettingsDirectorySection>
135
119
 
136
120
  <SettingsDirectorySection
@@ -225,6 +209,53 @@ export function SettingsRootContent({
225
209
  />
226
210
  </SettingsDirectorySection>
227
211
 
212
+ <SettingsDirectorySection
213
+ title={i18n._(
214
+ msg({
215
+ message: "Integrations",
216
+ comment:
217
+ "@context: Settings group label for third-party integrations",
218
+ }),
219
+ )}
220
+ >
221
+ <SettingsDirectoryLink
222
+ href={toPublicPath("/settings/github-sync", sitePathPrefix)}
223
+ icon={ICONS.gitBranch}
224
+ tone="subtle"
225
+ name={i18n._(
226
+ msg({
227
+ message: "GitHub Sync",
228
+ comment: "@context: Settings item — GitHub sync settings",
229
+ }),
230
+ )}
231
+ description={i18n._(
232
+ msg({
233
+ message: "Back up and sync content with a GitHub repository",
234
+ comment: "@context: Settings item description for GitHub sync",
235
+ }),
236
+ )}
237
+ />
238
+ <SettingsDirectoryLink
239
+ href={toPublicPath("/settings/telegram", sitePathPrefix)}
240
+ icon={ICONS.send}
241
+ tone="subtle"
242
+ name={i18n._(
243
+ msg({
244
+ message: "Telegram",
245
+ comment:
246
+ "@context: Settings item — Telegram integration settings",
247
+ }),
248
+ )}
249
+ description={i18n._(
250
+ msg({
251
+ message: "Post notes by messaging a Telegram bot",
252
+ comment:
253
+ "@context: Settings item description for Telegram integration",
254
+ }),
255
+ )}
256
+ />
257
+ </SettingsDirectorySection>
258
+
228
259
  <SettingsDirectorySection
229
260
  title={i18n._(
230
261
  msg({