@jant/core 0.3.43 → 0.3.45

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 (77) hide show
  1. package/dist/{app-GbfwoeDJ.js → app-C-L7wL6o.js} +485 -452
  2. package/dist/app-Hvqe7Ks_.js +5 -0
  3. package/dist/client/.vite/manifest.json +3 -3
  4. package/dist/client/_assets/client-DDs6NzB3.css +2 -0
  5. package/dist/client/_assets/{client-auth-CXILhW1b.js → client-auth-Dcon89Av.js} +30 -11
  6. package/dist/client/_assets/{client-D95FNDg5.js → client-dSfWfMe9.js} +7 -7
  7. package/dist/{github-sync-7y_nTXx1.js → github-sync-CQ1x271f.js} +3 -0
  8. package/dist/index.js +4 -87
  9. package/dist/node.js +3 -3
  10. package/package.json +1 -1
  11. package/src/client/components/jant-compose-dialog.ts +87 -9
  12. package/src/client/components/jant-compose-editor.ts +5 -1
  13. package/src/client/components/jant-post-menu.ts +23 -5
  14. package/src/client/compose-bridge.ts +2 -1
  15. package/src/client/toast.ts +29 -2
  16. package/src/client/upload-session.ts +1 -1
  17. package/src/db/migrations/0019_bored_magus.sql +2 -0
  18. package/src/db/migrations/0020_free_zaladane.sql +1 -0
  19. package/src/db/migrations/meta/0019_snapshot.json +2238 -0
  20. package/src/db/migrations/meta/0020_snapshot.json +2129 -0
  21. package/src/db/migrations/meta/_journal.json +14 -0
  22. package/src/db/migrations/pg/0017_bright_beyonder.sql +2 -0
  23. package/src/db/migrations/pg/0018_red_warlock.sql +1 -0
  24. package/src/db/migrations/pg/meta/0017_snapshot.json +2862 -0
  25. package/src/db/migrations/pg/meta/0018_snapshot.json +2739 -0
  26. package/src/db/migrations/pg/meta/_journal.json +14 -0
  27. package/src/db/pg/schema.ts +4 -30
  28. package/src/db/schema.ts +4 -39
  29. package/src/i18n/locales/public/en.po +10 -5
  30. package/src/i18n/locales/public/en.ts +1 -1
  31. package/src/i18n/locales/public/zh-Hans.po +10 -5
  32. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  33. package/src/i18n/locales/public/zh-Hant.po +10 -5
  34. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  35. package/src/index.ts +0 -3
  36. package/src/lib/__tests__/resolve-config.test.ts +4 -4
  37. package/src/lib/__tests__/startup-config.test.ts +27 -2
  38. package/src/lib/constants.ts +1 -0
  39. package/src/lib/github-sync-trigger.ts +7 -51
  40. package/src/lib/icons.ts +37 -0
  41. package/src/lib/startup-config.ts +53 -6
  42. package/src/routes/api/github-sync.tsx +36 -14
  43. package/src/routes/api/internal/sites.ts +1 -0
  44. package/src/routes/pages/home.tsx +2 -0
  45. package/src/routes/pages/latest.tsx +2 -0
  46. package/src/runtime/__tests__/readiness.test.ts +34 -0
  47. package/src/runtime/readiness.ts +8 -4
  48. package/src/services/__tests__/collection.test.ts +13 -11
  49. package/src/services/__tests__/site-admin.test.ts +85 -0
  50. package/src/services/github-sync.ts +6 -0
  51. package/src/services/site-admin.ts +66 -1
  52. package/src/styles/components.css +14 -0
  53. package/src/styles/ui.css +109 -0
  54. package/src/types/bindings.ts +0 -2
  55. package/src/types/config.ts +1 -1
  56. package/src/types/props.ts +2 -0
  57. package/src/ui/__tests__/font-themes.test.ts +2 -2
  58. package/src/ui/dash/settings/SettingsRootContent.tsx +17 -17
  59. package/src/ui/feed/LinkCard.tsx +3 -20
  60. package/src/ui/feed/LinkPreview.tsx +5 -19
  61. package/src/ui/feed/PostStatusBadges.tsx +4 -38
  62. package/src/ui/font-themes.ts +17 -17
  63. package/src/ui/layouts/BaseLayout.tsx +14 -29
  64. package/src/ui/pages/HomePage.tsx +21 -5
  65. package/src/ui/shared/DecorativeQuoteMark.tsx +2 -13
  66. package/src/ui/shared/Icon.tsx +60 -0
  67. package/src/ui/shared/IconSprite.tsx +57 -0
  68. package/src/ui/shared/PostFooter.tsx +6 -62
  69. package/src/ui/shared/custom-icons.ts +132 -0
  70. package/src/ui/shared/icon-collector.ts +37 -0
  71. package/dist/app-Ctl0T0zO.js +0 -5
  72. package/dist/client/_assets/client-C_kImWZj.css +0 -2
  73. package/src/lib/github-sync-queue-handler.ts +0 -69
  74. package/src/lib/github-sync-worker.ts +0 -72
  75. package/src/lib/job-queue-cf.ts +0 -18
  76. package/src/lib/job-queue-db.ts +0 -149
  77. package/src/lib/job-queue.ts +0 -35
@@ -1,69 +0,0 @@
1
- /**
2
- * Cloudflare Queue batch handler for GitHub Sync jobs.
3
- *
4
- * This module bridges the CF Queue consumer interface with the
5
- * generic sync worker. It creates a runtime per-batch to access
6
- * services and configuration.
7
- */
8
-
9
- import { createRequestRuntime } from "../runtime/index.js";
10
- import { processGitHubSyncJob } from "./github-sync-worker.js";
11
- import { getGitHubAppConfig } from "./env.js";
12
- import type { JobPayload } from "./job-queue.js";
13
- import type { Bindings } from "../types/bindings.js";
14
-
15
- /**
16
- * Handle a batch of messages from a Cloudflare Queue.
17
- *
18
- * Each message is expected to be a `JobPayload` object.
19
- */
20
- export async function handleQueueBatch(
21
- batch: MessageBatch<unknown>,
22
- env: Record<string, unknown>,
23
- ): Promise<void> {
24
- // We need a runtime to access services. Use a synthetic URL since
25
- // queue handlers don't have an incoming request.
26
- const siteOrigin =
27
- typeof env.SITE_ORIGIN === "string" && env.SITE_ORIGIN
28
- ? env.SITE_ORIGIN
29
- : "http://localhost";
30
-
31
- const runtime = await createRequestRuntime(env as Bindings, siteOrigin);
32
-
33
- for (const message of batch.messages) {
34
- const payload = message.body as JobPayload;
35
- try {
36
- await processGitHubSyncJob(
37
- payload,
38
- runtime.services,
39
- payload.siteId,
40
- {
41
- siteName: "",
42
- siteUrl: siteOrigin,
43
- siteDescription: "",
44
- siteLanguage: "",
45
- showJantBrandingOnHome: false,
46
- homeDefaultView: "",
47
- mainRssFeed: "",
48
- siteFooter: "",
49
- showHeaderAvatar: false,
50
- siteAvatarUrl: "",
51
- themeId: "",
52
- defaultThemeId: "",
53
- fontThemeId: "",
54
- themeMode: "",
55
- noindex: false,
56
- navItems: [],
57
- pageSize: 50,
58
- archivePageSize: 50,
59
- rssFeedLimit: 50,
60
- },
61
- runtime.storage,
62
- getGitHubAppConfig(env),
63
- );
64
- message.ack();
65
- } catch {
66
- message.retry();
67
- }
68
- }
69
- }
@@ -1,72 +0,0 @@
1
- /**
2
- * GitHub Sync Worker
3
- *
4
- * Processes queued sync jobs. Push jobs always run a full sync.
5
- * After completing, re-checks the pending flag to catch changes
6
- * that arrived during execution.
7
- */
8
-
9
- import { createGitHubSyncService } from "../services/github-sync.js";
10
- import type { SiteConfig } from "../services/export.js";
11
- import type { Services } from "../services/index.js";
12
- import type { StorageDriver } from "../lib/storage.js";
13
- import type { GitHubAppEnvConfig } from "./env.js";
14
- import type { JobPayload } from "./job-queue.js";
15
-
16
- /**
17
- * Process a single GitHub Sync job.
18
- */
19
- export async function processGitHubSyncJob(
20
- payload: JobPayload,
21
- services: Services,
22
- siteId: string,
23
- siteConfig: SiteConfig,
24
- storage?: StorageDriver | null,
25
- githubApp?: GitHubAppEnvConfig | null,
26
- ): Promise<void> {
27
- const syncService = createGitHubSyncService(
28
- {
29
- posts: services.posts,
30
- paths: services.paths,
31
- collections: services.collections,
32
- media: services.media,
33
- settings: services.settings,
34
- },
35
- siteId,
36
- siteConfig,
37
- { storage, githubApp },
38
- );
39
-
40
- if (payload.kind === "github-sync-push") {
41
- // Clear pending flag before running so new triggers during
42
- // execution will set it again.
43
- await services.settings.set("GITHUB_SYNC_PENDING", "");
44
-
45
- await syncService.pushFullSync();
46
-
47
- // If the flag was re-set during execution, run once more
48
- const stillPending = await services.settings.get("GITHUB_SYNC_PENDING");
49
- if (stillPending === "true") {
50
- await services.settings.set("GITHUB_SYNC_PENDING", "");
51
- await syncService.pushFullSync();
52
- }
53
- return;
54
- }
55
-
56
- if (payload.kind === "github-sync-pull") {
57
- const webhookPayload = payload.data as {
58
- ref: string;
59
- before: string;
60
- after: string;
61
- commits: Array<{
62
- id: string;
63
- message: string;
64
- added: string[];
65
- modified: string[];
66
- removed: string[];
67
- }>;
68
- };
69
- await syncService.handleWebhookPush(webhookPayload);
70
- return;
71
- }
72
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * Cloudflare Queue adapter for the job queue.
3
- */
4
-
5
- import type { JobPayload, JobQueue } from "./job-queue.js";
6
-
7
- /**
8
- * Create a job queue backed by a Cloudflare Queue binding.
9
- *
10
- * @param queue - The CF Queue binding (e.g. `env.GITHUB_SYNC_QUEUE`)
11
- */
12
- export function createCfJobQueue(queue: Queue): JobQueue {
13
- return {
14
- async enqueue(payload: JobPayload) {
15
- await queue.send(payload);
16
- },
17
- };
18
- }
@@ -1,149 +0,0 @@
1
- /**
2
- * Database-backed job queue.
3
- *
4
- * Used as a fallback when Cloudflare Queues are not available (e.g. Node/Postgres
5
- * or self-hosted deployments). Jobs are stored in the `sync_job` table and
6
- * processed via periodic polling.
7
- */
8
-
9
- import { eq, and, lte, sql } from "drizzle-orm";
10
- import type { Database } from "../db/index.js";
11
- import type { DatabaseSchema } from "../db/schema-bundle.js";
12
- import type { JobPayload, JobQueue } from "./job-queue.js";
13
- import { typeidUnboxed } from "typeid-js";
14
- import { now as unixNow } from "./time.js";
15
-
16
- // ---------------------------------------------------------------------------
17
- // Queue (producer)
18
- // ---------------------------------------------------------------------------
19
-
20
- /**
21
- * Create a job queue backed by a database table.
22
- *
23
- * @param db - Drizzle database instance
24
- * @param schema - Database schema bundle
25
- */
26
- export function createDbJobQueue(
27
- db: Database,
28
- schema: DatabaseSchema,
29
- ): JobQueue {
30
- return {
31
- async enqueue(payload: JobPayload) {
32
- const now = unixNow();
33
- await db.insert(schema.syncJobs).values({
34
- id: typeidUnboxed("job"),
35
- siteId: payload.siteId,
36
- kind: payload.kind,
37
- payload: JSON.stringify(payload.data),
38
- status: "pending",
39
- attempts: 0,
40
- maxAttempts: 3,
41
- createdAt: now,
42
- updatedAt: now,
43
- lockedUntil: null,
44
- });
45
- },
46
- };
47
- }
48
-
49
- // ---------------------------------------------------------------------------
50
- // Worker (consumer) — poll-based dequeue
51
- // ---------------------------------------------------------------------------
52
-
53
- export interface DbJobWorkerOptions {
54
- db: Database;
55
- schema: DatabaseSchema;
56
- /** Called to process each dequeued job. */
57
- handler: (payload: JobPayload) => Promise<void>;
58
- /** Lock duration in seconds. Defaults to 60. */
59
- lockDurationSec?: number;
60
- }
61
-
62
- /**
63
- * Dequeue and process a single pending job.
64
- *
65
- * @returns `true` if a job was processed, `false` if the queue was empty.
66
- */
67
- export async function dequeueAndProcess(
68
- opts: DbJobWorkerOptions,
69
- ): Promise<boolean> {
70
- const { db, schema, handler, lockDurationSec = 60 } = opts;
71
- const now = unixNow();
72
-
73
- // Find one eligible job (pending or timed-out lock)
74
- const [job] = await db
75
- .select()
76
- .from(schema.syncJobs)
77
- .where(
78
- and(
79
- eq(schema.syncJobs.status, "pending"),
80
- sql`(${schema.syncJobs.lockedUntil} IS NULL OR ${schema.syncJobs.lockedUntil} <= ${now})`,
81
- ),
82
- )
83
- .orderBy(schema.syncJobs.createdAt)
84
- .limit(1);
85
-
86
- if (!job) return false;
87
-
88
- // Lock the job
89
- await db
90
- .update(schema.syncJobs)
91
- .set({
92
- status: "processing",
93
- lockedUntil: now + lockDurationSec,
94
- attempts: job.attempts + 1,
95
- updatedAt: now,
96
- })
97
- .where(eq(schema.syncJobs.id, job.id));
98
-
99
- try {
100
- const payload: JobPayload = {
101
- kind: job.kind as JobPayload["kind"],
102
- siteId: job.siteId,
103
- data: JSON.parse(job.payload) as Record<string, unknown>,
104
- };
105
- await handler(payload);
106
-
107
- // Mark completed
108
- await db
109
- .update(schema.syncJobs)
110
- .set({ status: "completed", updatedAt: unixNow() })
111
- .where(eq(schema.syncJobs.id, job.id));
112
- } catch {
113
- const updatedAttempts = job.attempts + 1;
114
- const maxAttempts = job.maxAttempts;
115
-
116
- await db
117
- .update(schema.syncJobs)
118
- .set({
119
- status: updatedAttempts >= maxAttempts ? "failed" : "pending",
120
- lockedUntil: null,
121
- updatedAt: unixNow(),
122
- })
123
- .where(eq(schema.syncJobs.id, job.id));
124
- }
125
-
126
- return true;
127
- }
128
-
129
- /**
130
- * Clean up completed and failed jobs older than the given age.
131
- *
132
- * @param maxAgeSec - Maximum age in seconds. Defaults to 7 days.
133
- */
134
- export async function cleanupOldJobs(
135
- db: Database,
136
- schema: DatabaseSchema,
137
- maxAgeSec = 7 * 24 * 3600,
138
- ): Promise<number> {
139
- const cutoff = unixNow() - maxAgeSec;
140
- const result = await db
141
- .delete(schema.syncJobs)
142
- .where(
143
- and(
144
- sql`${schema.syncJobs.status} IN ('completed', 'failed')`,
145
- lte(schema.syncJobs.updatedAt, cutoff),
146
- ),
147
- );
148
- return (result as { rowsAffected?: number }).rowsAffected ?? 0;
149
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * Job Queue abstraction.
3
- *
4
- * Provides a unified interface for enqueueing background jobs across
5
- * Cloudflare Workers (CF Queues) and Node/Postgres (DB-backed polling).
6
- */
7
-
8
- // ---------------------------------------------------------------------------
9
- // Types
10
- // ---------------------------------------------------------------------------
11
-
12
- export type JobKind = "github-sync-push" | "github-sync-pull";
13
-
14
- export interface JobPayload {
15
- kind: JobKind;
16
- siteId: string;
17
- data: Record<string, unknown>;
18
- }
19
-
20
- export interface JobQueue {
21
- /** Enqueue a job for async background processing. */
22
- enqueue(payload: JobPayload): Promise<void>;
23
- }
24
-
25
- // ---------------------------------------------------------------------------
26
- // Noop Queue (used when no queue backend is available)
27
- // ---------------------------------------------------------------------------
28
-
29
- /**
30
- * A no-op queue that silently drops jobs.
31
- * Used as a fallback when neither CF Queue nor DB queue is configured.
32
- */
33
- export const noopQueue: JobQueue = {
34
- async enqueue() {},
35
- };