@jant/core 0.6.10 → 0.6.11
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/dist/{app-CGHkOdme.js → app-CpmficmQ.js} +531 -204
- package/dist/app-DqKkZenB.js +6 -0
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/client-BhHHVvSY.css +2 -0
- package/dist/client/_assets/{client-DYrWuaIk.js → client-Dd9U383b.js} +1 -1
- package/dist/client/_assets/{client-auth-B5Re0uCd.js → client-auth-DkpSdIDz.js} +80 -80
- package/dist/{export-DY1v5Iqu.js → export-Ba7NJImL.js} +92 -92
- package/dist/{github-sync-LefaslGJ.js → github-sync-BD4w2m8-.js} +2 -2
- package/dist/{github-sync-2_T7nbOv.js → github-sync-Cb4_6_i7.js} +1 -1
- package/dist/index.js +3 -3
- package/dist/node.js +4 -4
- package/package.json +1 -1
- package/src/client/components/__tests__/jant-compose-editor-rehost-notice.test.ts +62 -0
- package/src/client/components/compose-types.ts +4 -0
- package/src/client/components/jant-compose-editor.ts +111 -0
- package/src/client/compose-bridge.ts +25 -8
- 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 +139 -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 +104 -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/lib/__tests__/upload-sideload.test.ts +78 -0
- package/src/lib/__tests__/url-fetch.test.ts +181 -0
- package/src/lib/upload.ts +111 -0
- package/src/lib/url-fetch.ts +263 -0
- package/src/routes/api/__tests__/uploads.test.ts +63 -1
- package/src/routes/api/uploads.ts +52 -0
- package/src/services/__tests__/media.test.ts +168 -1
- package/src/services/media.ts +111 -0
- package/src/styles/ui.css +1 -1
- package/src/ui/compose/ComposeDialog.tsx +16 -0
- package/src/ui/layouts/BaseLayout.tsx +12 -0
- package/dist/app-D24n0DoH.js +0 -6
- package/dist/client/_assets/client-xWDl78yi.css +0 -2
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { Editor, JSONContent } from "@tiptap/core";
|
|
9
9
|
import { uploadWithMetadata } from "../upload-with-metadata.js";
|
|
10
|
+
import { publicPath } from "../runtime-paths.js";
|
|
10
11
|
|
|
11
12
|
type InlineImageUpload = (file: File) => Promise<{ url: string }>;
|
|
12
13
|
|
|
@@ -26,12 +27,15 @@ const inflightUploads = new Map<string, Promise<string>>();
|
|
|
26
27
|
*/
|
|
27
28
|
const adoptedUploads = new Map<string, Promise<string>>();
|
|
28
29
|
|
|
29
|
-
function replaceInlineImage(editor: Editor,
|
|
30
|
+
function replaceInlineImage(editor: Editor, fromUrl: string, realUrl: string) {
|
|
30
31
|
if (editor.isDestroyed) return;
|
|
31
32
|
const { doc } = editor.state;
|
|
32
|
-
|
|
33
|
+
// Replace every node sharing this src. Blob placeholders are unique, but a
|
|
34
|
+
// deduped remote URL can appear in several nodes after a paste, and they all
|
|
35
|
+
// need to point at the one rehosted copy. setNodeMarkup preserves node size,
|
|
36
|
+
// so positions stay valid across the walk.
|
|
33
37
|
doc.descendants((node, pos) => {
|
|
34
|
-
if (
|
|
38
|
+
if (node.type.name !== "image" || node.attrs.src !== fromUrl) {
|
|
35
39
|
return;
|
|
36
40
|
}
|
|
37
41
|
|
|
@@ -45,7 +49,6 @@ function replaceInlineImage(editor: Editor, blobUrl: string, realUrl: string) {
|
|
|
45
49
|
return true;
|
|
46
50
|
})
|
|
47
51
|
.run();
|
|
48
|
-
replaced = true;
|
|
49
52
|
});
|
|
50
53
|
}
|
|
51
54
|
|
|
@@ -65,6 +68,26 @@ function removeInlineImage(editor: Editor, blobUrl: string) {
|
|
|
65
68
|
});
|
|
66
69
|
}
|
|
67
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Apply the outcome of an inline upload/rehost to the placeholder node(s).
|
|
73
|
+
*
|
|
74
|
+
* On success the src is swapped to the final URL. On failure a `blob:`
|
|
75
|
+
* placeholder (from the insert flow) is removed, while a remote/`data:`
|
|
76
|
+
* placeholder (from the rehost flow) is left untouched so the original image
|
|
77
|
+
* still shows — rehosting is best-effort.
|
|
78
|
+
*/
|
|
79
|
+
function settlePlaceholder(
|
|
80
|
+
editor: Editor,
|
|
81
|
+
placeholderSrc: string,
|
|
82
|
+
realUrl: string | null,
|
|
83
|
+
) {
|
|
84
|
+
if (realUrl) {
|
|
85
|
+
replaceInlineImage(editor, placeholderSrc, realUrl);
|
|
86
|
+
} else if (placeholderSrc.startsWith("blob:")) {
|
|
87
|
+
removeInlineImage(editor, placeholderSrc);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
68
91
|
/**
|
|
69
92
|
* Uploads an image file and inserts it into the editor as an inline image.
|
|
70
93
|
*
|
|
@@ -93,9 +116,9 @@ export async function uploadAndInsertInlineImage(
|
|
|
93
116
|
|
|
94
117
|
try {
|
|
95
118
|
const realUrl = await uploaded;
|
|
96
|
-
|
|
119
|
+
settlePlaceholder(editor, placeholderUrl, realUrl);
|
|
97
120
|
} catch {
|
|
98
|
-
|
|
121
|
+
settlePlaceholder(editor, placeholderUrl, null);
|
|
99
122
|
} finally {
|
|
100
123
|
// Only cleanup if not adopted by another editor
|
|
101
124
|
if (inflightUploads.delete(placeholderUrl)) {
|
|
@@ -105,16 +128,88 @@ export async function uploadAndInsertInlineImage(
|
|
|
105
128
|
}
|
|
106
129
|
|
|
107
130
|
/**
|
|
108
|
-
*
|
|
131
|
+
* Result of the remote-image sideload endpoint.
|
|
132
|
+
*/
|
|
133
|
+
export interface SideloadResult {
|
|
134
|
+
id: string;
|
|
135
|
+
url: string;
|
|
136
|
+
width?: number;
|
|
137
|
+
height?: number;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Ask the server to rehost a remote image URL into the site's own storage.
|
|
142
|
+
*
|
|
143
|
+
* The server fetches the bytes (browser fetch of a third-party image is blocked
|
|
144
|
+
* by CORS), stores them, and returns the new public URL.
|
|
145
|
+
*
|
|
146
|
+
* @param url - The remote http(s) image URL
|
|
147
|
+
* @param alt - Optional alt text to persist on the media row
|
|
148
|
+
* @returns The created media's id, public URL, and dimensions
|
|
149
|
+
* @throws {Error} When the endpoint responds with a non-OK status
|
|
150
|
+
*/
|
|
151
|
+
export async function sideloadImage(
|
|
152
|
+
url: string,
|
|
153
|
+
alt?: string,
|
|
154
|
+
): Promise<SideloadResult> {
|
|
155
|
+
const res = await fetch(publicPath("/api/uploads/sideload"), {
|
|
156
|
+
method: "POST",
|
|
157
|
+
headers: { "Content-Type": "application/json" },
|
|
158
|
+
body: JSON.stringify({ url, alt }),
|
|
159
|
+
});
|
|
160
|
+
if (!res.ok) {
|
|
161
|
+
throw new Error(`Sideload failed: HTTP ${res.status}`);
|
|
162
|
+
}
|
|
163
|
+
return (await res.json()) as SideloadResult;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Rehost an image node that already exists in the document (inserted by a paste)
|
|
168
|
+
* whose `src` is a remote or `data:` URL. Tracks the work in the shared registry
|
|
169
|
+
* so submit waits for it, swaps the src to the stored URL on success, and leaves
|
|
170
|
+
* the node untouched on failure (the original image keeps showing).
|
|
171
|
+
*
|
|
172
|
+
* Does NOT insert a node — the paste already created it.
|
|
173
|
+
*
|
|
174
|
+
* @param editor - TipTap editor containing the placeholder node
|
|
175
|
+
* @param placeholderSrc - The current (remote/data) src of the node to rehost
|
|
176
|
+
* @param resolveUrl - Produces the final stored URL (server sideload or client upload)
|
|
177
|
+
* @returns Resolves after the node is updated or left in place
|
|
178
|
+
* @example
|
|
179
|
+
* ```ts
|
|
180
|
+
* await rehostInlineImage(editor, src, () => sideloadImage(src).then((r) => r.url));
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
export async function rehostInlineImage(
|
|
184
|
+
editor: Editor,
|
|
185
|
+
placeholderSrc: string,
|
|
186
|
+
resolveUrl: () => Promise<string>,
|
|
187
|
+
): Promise<void> {
|
|
188
|
+
const uploaded = resolveUrl();
|
|
189
|
+
inflightUploads.set(placeholderSrc, uploaded);
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const realUrl = await uploaded;
|
|
193
|
+
settlePlaceholder(editor, placeholderSrc, realUrl);
|
|
194
|
+
} catch {
|
|
195
|
+
settlePlaceholder(editor, placeholderSrc, null);
|
|
196
|
+
} finally {
|
|
197
|
+
inflightUploads.delete(placeholderSrc);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Adopt in-flight inline image uploads/rehosts into a new editor instance.
|
|
109
203
|
*
|
|
110
|
-
* Scans the editor's document for
|
|
111
|
-
*
|
|
112
|
-
*
|
|
204
|
+
* Scans the editor's document for image srcs that match pending registry entries
|
|
205
|
+
* (`blob:` placeholders from the insert flow, or remote/`data:` placeholders
|
|
206
|
+
* from the paste-rehost flow). For each match, takes ownership from the original
|
|
207
|
+
* editor and sets up replacement/removal watchers on the new editor.
|
|
113
208
|
*
|
|
114
|
-
* Call this immediately after `setContent` with JSON that may contain
|
|
115
|
-
* (e.g. after fullscreen close transfers content back to compose
|
|
209
|
+
* Call this immediately after `setContent` with JSON that may contain pending
|
|
210
|
+
* placeholders (e.g. after fullscreen close transfers content back to compose).
|
|
116
211
|
*
|
|
117
|
-
* @param editor - The TipTap editor that now contains the content
|
|
212
|
+
* @param editor - The TipTap editor that now contains the placeholder content
|
|
118
213
|
* @returns Array of promises that resolve when each adopted upload completes
|
|
119
214
|
*/
|
|
120
215
|
export function adoptPendingInlineImageUploads(
|
|
@@ -126,7 +221,7 @@ export function adoptPendingInlineImageUploads(
|
|
|
126
221
|
doc.descendants((node) => {
|
|
127
222
|
if (node.type.name !== "image") return;
|
|
128
223
|
const src = node.attrs.src as string;
|
|
129
|
-
if (
|
|
224
|
+
if (typeof src !== "string") return;
|
|
130
225
|
|
|
131
226
|
const uploaded = inflightUploads.get(src);
|
|
132
227
|
if (!uploaded) return;
|
|
@@ -137,14 +232,17 @@ export function adoptPendingInlineImageUploads(
|
|
|
137
232
|
inflightUploads.delete(src);
|
|
138
233
|
adoptedUploads.set(src, uploaded);
|
|
139
234
|
|
|
235
|
+
// Blob placeholders own an object URL and are removed on failure; remote/
|
|
236
|
+
// data rehost placeholders have neither (keep the node, nothing to revoke).
|
|
237
|
+
const isBlob = src.startsWith("blob:");
|
|
140
238
|
const promise = uploaded
|
|
141
239
|
.then(
|
|
142
240
|
(realUrl) => replaceInlineImage(editor, src, realUrl),
|
|
143
|
-
() =>
|
|
241
|
+
() => settlePlaceholder(editor, src, null),
|
|
144
242
|
)
|
|
145
243
|
.finally(() => {
|
|
146
244
|
adoptedUploads.delete(src);
|
|
147
|
-
URL.revokeObjectURL(src);
|
|
245
|
+
if (isBlob) URL.revokeObjectURL(src);
|
|
148
246
|
});
|
|
149
247
|
adopted.push(promise);
|
|
150
248
|
});
|
|
@@ -153,80 +251,106 @@ export function adoptPendingInlineImageUploads(
|
|
|
153
251
|
}
|
|
154
252
|
|
|
155
253
|
/**
|
|
156
|
-
* Resolve all
|
|
254
|
+
* Resolve all pending inline image placeholders in a TipTap JSON document.
|
|
157
255
|
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
256
|
+
* Covers both placeholder kinds tracked in the registries: `blob:` URLs from
|
|
257
|
+
* the insert/upload flow and remote/`data:` URLs from the paste-rehost flow.
|
|
258
|
+
* Waits for the pending work and returns a new JSON tree with stored URLs.
|
|
259
|
+
* Unresolved `blob:` placeholders (upload failed) are removed; unresolved
|
|
260
|
+
* remote/`data:` placeholders are kept with their original src (rehost is
|
|
261
|
+
* best-effort, so the original image still shows).
|
|
160
262
|
*
|
|
161
263
|
* Used by the submit bridge to finalize content before posting.
|
|
162
264
|
*
|
|
163
|
-
* @param json - TipTap JSON document that may contain
|
|
164
|
-
* @returns Resolved JSON with
|
|
265
|
+
* @param json - TipTap JSON document that may contain placeholder image srcs
|
|
266
|
+
* @returns Resolved JSON with placeholder srcs replaced (or kept/removed)
|
|
165
267
|
*/
|
|
166
268
|
export async function resolveInlineImageUrls(
|
|
167
269
|
json: JSONContent | null,
|
|
168
270
|
): Promise<JSONContent | null> {
|
|
169
271
|
if (!json) return json;
|
|
170
272
|
|
|
171
|
-
// Collect
|
|
172
|
-
const
|
|
173
|
-
|
|
273
|
+
// Collect every placeholder src present in the registries + its promise.
|
|
274
|
+
const placeholders = new Map<string, Promise<string>>();
|
|
275
|
+
collectPlaceholderUrls(json, placeholders);
|
|
174
276
|
|
|
175
|
-
if (
|
|
277
|
+
if (placeholders.size === 0) return json;
|
|
176
278
|
|
|
177
|
-
// Wait for all
|
|
178
|
-
const
|
|
279
|
+
// Wait for all pending work; record success (URL) or failure (null) per src.
|
|
280
|
+
const outcomes = new Map<string, string | null>();
|
|
179
281
|
await Promise.allSettled(
|
|
180
|
-
Array.from(
|
|
181
|
-
|
|
182
|
-
|
|
282
|
+
Array.from(placeholders.entries()).map(async ([src, promise]) => {
|
|
283
|
+
try {
|
|
284
|
+
outcomes.set(src, await promise);
|
|
285
|
+
} catch {
|
|
286
|
+
outcomes.set(src, null);
|
|
287
|
+
}
|
|
183
288
|
}),
|
|
184
289
|
);
|
|
185
290
|
|
|
186
|
-
return
|
|
291
|
+
return applyPlaceholderOutcomes(json, outcomes);
|
|
187
292
|
}
|
|
188
293
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
294
|
+
/**
|
|
295
|
+
* Whether a TipTap JSON document still references inline image placeholders that
|
|
296
|
+
* are pending upload/rehost. Used to decide the "uploading" toast and whether to
|
|
297
|
+
* run {@link resolveInlineImageUrls} before submit.
|
|
298
|
+
*
|
|
299
|
+
* @param json - TipTap JSON document to inspect
|
|
300
|
+
* @returns True if any image src is a pending placeholder
|
|
301
|
+
*/
|
|
302
|
+
export function hasPendingInlineImagePlaceholders(
|
|
303
|
+
json: JSONContent | null,
|
|
304
|
+
): boolean {
|
|
305
|
+
if (!json) return false;
|
|
306
|
+
const placeholders = new Map<string, Promise<string>>();
|
|
307
|
+
collectPlaceholderUrls(json, placeholders);
|
|
308
|
+
return placeholders.size > 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function collectPlaceholderUrls(
|
|
312
|
+
node: JSONContent,
|
|
313
|
+
out: Map<string, Promise<string>>,
|
|
314
|
+
) {
|
|
315
|
+
if (node.type === "image" && typeof node.attrs?.src === "string") {
|
|
316
|
+
const src = node.attrs.src;
|
|
317
|
+
const promise = inflightUploads.get(src) ?? adoptedUploads.get(src);
|
|
197
318
|
if (promise) {
|
|
198
|
-
out.set(
|
|
319
|
+
out.set(src, promise);
|
|
199
320
|
}
|
|
200
321
|
}
|
|
201
322
|
if (node.content) {
|
|
202
323
|
for (const child of node.content) {
|
|
203
|
-
|
|
324
|
+
collectPlaceholderUrls(child, out);
|
|
204
325
|
}
|
|
205
326
|
}
|
|
206
327
|
}
|
|
207
328
|
|
|
208
|
-
function
|
|
329
|
+
function applyPlaceholderOutcomes(
|
|
209
330
|
node: JSONContent,
|
|
210
|
-
|
|
331
|
+
outcomes: Map<string, string | null>,
|
|
211
332
|
): JSONContent {
|
|
212
|
-
// Remove image nodes with unresolved blob URLs (upload failed or orphaned)
|
|
213
333
|
if (
|
|
214
334
|
node.type === "image" &&
|
|
215
335
|
typeof node.attrs?.src === "string" &&
|
|
216
|
-
node.attrs.src
|
|
336
|
+
outcomes.has(node.attrs.src)
|
|
217
337
|
) {
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
338
|
+
const src = node.attrs.src;
|
|
339
|
+
const realUrl = outcomes.get(src);
|
|
340
|
+
if (realUrl) {
|
|
341
|
+
return { ...node, attrs: { ...node.attrs, src: realUrl } };
|
|
342
|
+
}
|
|
343
|
+
// Unresolved: drop blob placeholders (orphaned upload), keep remote/data.
|
|
344
|
+
if (src.startsWith("blob:")) {
|
|
221
345
|
return { type: "__removed__" };
|
|
222
346
|
}
|
|
223
|
-
return
|
|
347
|
+
return node;
|
|
224
348
|
}
|
|
225
349
|
|
|
226
350
|
if (!node.content) return node;
|
|
227
351
|
|
|
228
352
|
const newContent = node.content
|
|
229
|
-
.map((child) =>
|
|
353
|
+
.map((child) => applyPlaceholderOutcomes(child, outcomes))
|
|
230
354
|
.filter((child) => child.type !== "__removed__");
|
|
231
355
|
|
|
232
356
|
return newContent === node.content ? node : { ...node, content: newContent };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rehost-on-paste plugin for inline images.
|
|
3
|
+
*
|
|
4
|
+
* When an author pastes article HTML, its `<img>` tags become image nodes whose
|
|
5
|
+
* `src` still points at the original remote URL (or is an inline `data:` URL).
|
|
6
|
+
* This extension detects those nodes after a paste and asks the host to rehost
|
|
7
|
+
* them into the site's own storage, swapping the node's `src` to the stored URL.
|
|
8
|
+
*
|
|
9
|
+
* It only schedules work — the actual upload/swap happens in the host callback
|
|
10
|
+
* (see `jant-compose-editor`), tracked by the shared inline-image registry so
|
|
11
|
+
* submit waits for it. The remote URL stays visible as an instant preview until
|
|
12
|
+
* the swap completes; on failure the node keeps its original src.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Extension } from "@tiptap/core";
|
|
16
|
+
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
17
|
+
|
|
18
|
+
export interface RehostImagesOptions {
|
|
19
|
+
/** Returns true when an image src should be rehosted (remote/data, not ours). */
|
|
20
|
+
shouldRehost?: (src: string) => boolean;
|
|
21
|
+
/** Starts rehosting the given src. Must call clearRehostInFlight when settled. */
|
|
22
|
+
rehost?: (src: string) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Generous per-paste cap. Beyond this, extra images are left as external links
|
|
27
|
+
* (and logged) rather than firing an unbounded number of fetches at once.
|
|
28
|
+
*/
|
|
29
|
+
const REHOST_MAX_PER_DOC = 50;
|
|
30
|
+
|
|
31
|
+
const rehostImagesKey = new PluginKey("jantRehostImages");
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Srcs currently being rehosted, deduped so repeated transactions (e.g. typing
|
|
35
|
+
* after a paste, or the src-swap itself) don't re-trigger the same work.
|
|
36
|
+
*/
|
|
37
|
+
const rehostInFlight = new Set<string>();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Release a src once its rehost has settled, so an identical URL pasted later
|
|
41
|
+
* can be rehosted again.
|
|
42
|
+
*
|
|
43
|
+
* @param src - The placeholder src that was being rehosted
|
|
44
|
+
*/
|
|
45
|
+
export function clearRehostInFlight(src: string): void {
|
|
46
|
+
rehostInFlight.delete(src);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const RehostImages = Extension.create<RehostImagesOptions>({
|
|
50
|
+
name: "rehostImages",
|
|
51
|
+
|
|
52
|
+
addOptions() {
|
|
53
|
+
return {
|
|
54
|
+
shouldRehost: undefined,
|
|
55
|
+
rehost: undefined,
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
addProseMirrorPlugins() {
|
|
60
|
+
const options = this.options;
|
|
61
|
+
return [
|
|
62
|
+
new Plugin({
|
|
63
|
+
key: rehostImagesKey,
|
|
64
|
+
appendTransaction(transactions, _oldState, newState) {
|
|
65
|
+
if (!transactions.some((tr) => tr.docChanged)) return null;
|
|
66
|
+
const { shouldRehost, rehost } = options;
|
|
67
|
+
if (!shouldRehost || !rehost) return null;
|
|
68
|
+
|
|
69
|
+
const candidates: string[] = [];
|
|
70
|
+
const seen = new Set<string>();
|
|
71
|
+
newState.doc.descendants((node) => {
|
|
72
|
+
if (node.type.name !== "image") return;
|
|
73
|
+
const src = node.attrs.src;
|
|
74
|
+
if (typeof src !== "string" || !src) return;
|
|
75
|
+
if (seen.has(src) || rehostInFlight.has(src)) return;
|
|
76
|
+
if (!shouldRehost(src)) return;
|
|
77
|
+
seen.add(src);
|
|
78
|
+
candidates.push(src);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (candidates.length === 0) return null;
|
|
82
|
+
|
|
83
|
+
const accepted = candidates.slice(0, REHOST_MAX_PER_DOC);
|
|
84
|
+
const dropped = candidates.length - accepted.length;
|
|
85
|
+
if (dropped > 0) {
|
|
86
|
+
// eslint-disable-next-line no-console -- surface a silently-skipped cap
|
|
87
|
+
console.warn(
|
|
88
|
+
`[jant] ${dropped} pasted image(s) left as external links — ` +
|
|
89
|
+
`per-paste rehost cap of ${REHOST_MAX_PER_DOC} reached.`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const src of accepted) {
|
|
94
|
+
rehostInFlight.add(src);
|
|
95
|
+
queueMicrotask(() => rehost(src));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// We only schedule side effects; the doc isn't modified here.
|
|
99
|
+
return null;
|
|
100
|
+
},
|
|
101
|
+
}),
|
|
102
|
+
];
|
|
103
|
+
},
|
|
104
|
+
});
|
|
@@ -33,6 +33,11 @@ msgstr "{count, plural, one {# more post} other {# more posts}}"
|
|
|
33
33
|
msgid "{count, plural, one {Found # result} other {Found # results}}"
|
|
34
34
|
msgstr "{count, plural, one {Found # result} other {Found # results}}"
|
|
35
35
|
|
|
36
|
+
#. @context: Toast when several pasted remote images couldn't be rehosted; {count} is the number of images
|
|
37
|
+
#: src/ui/compose/ComposeDialog.tsx
|
|
38
|
+
msgid "{count} images couldn't be saved to your library — their original links were kept."
|
|
39
|
+
msgstr "{count} images couldn't be saved to your library — their original links were kept."
|
|
40
|
+
|
|
36
41
|
#. @context: Placeholder for the custom collections link URL
|
|
37
42
|
#: src/ui/shared/collection-management-labels.ts
|
|
38
43
|
msgid "/archive?format=quote or https://example.com"
|
|
@@ -193,6 +198,11 @@ msgstr "All years"
|
|
|
193
198
|
msgid "An abstract editorial layout in warm paper colors"
|
|
194
199
|
msgstr "An abstract editorial layout in warm paper colors"
|
|
195
200
|
|
|
201
|
+
#. @context: Toast when a single pasted remote image couldn't be rehosted (e.g. blocked by the source's hotlink protection)
|
|
202
|
+
#: src/ui/compose/ComposeDialog.tsx
|
|
203
|
+
msgid "An image couldn't be saved to your library — its original link was kept."
|
|
204
|
+
msgstr "An image couldn't be saved to your library — its original link was kept."
|
|
205
|
+
|
|
196
206
|
#. @context: Sample link post body suffix on the theme sample page
|
|
197
207
|
#: src/ui/pages/ThemeSamplePage.tsx
|
|
198
208
|
msgid "and checking whether the accent is guiding attention or pulling too hard."
|
|
@@ -1 +1 @@
|
|
|
1
|
-
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"+4u2g6\":[\"A ready-made 1:1 PNG for decks, mockups, directories, and other square placements.\"],\"+DPYOZ\":[\"Add a link to your main RSS feed. Change what /feed returns in General.\"],\"+G8qqW\":[\"Collection saved.\"],\"+IJm1Z\":[\"Muted\"],\"+Irvp3\":[\"Everything on this page is ready to use for articles, launch posts, directories, and product coverage.\"],\"+Qaboy\":[\"Favicon\"],\"+fWu2O\":[\"A calmer, warmer accent makes the default theme feel quieter and more intentional.\"],\"+nHhRH\":[\"Use \",[\"brandColorName\"]],\"+siMqD\":[\"Journal\"],\"/DFKdU\":[\"Type the quote...\"],\"/PfPLc\":[\"Label (optional)\"],\"/PoNoq\":[\"Edit link\"],\"/Ui2OV\":[\"Use the reverse logo on dark backgrounds.\"],\"/Ybds4\":[\"Primary Jant logo for websites, docs, press coverage, and editorial layouts.\"],\"/rTz0M\":[\"Audio\"],\"0EcUWz\":[\"Discard changes?\"],\"0Lj7or\":[\"Save text attachment?\"],\"0XDp7X\":[\"Links should read clearly without glowing against the page.\"],\"0ieXE7\":[\"Highest rated\"],\"11h9eK\":[\"Includes\"],\"15++NM\":[\"Inline emphasis\"],\"1DBGsz\":[\"Notes\"],\"1NeeWI\":[\"Square assets for avatars, apps, browsers, and shared links\"],\"1THMr2\":[\"Brand pack\"],\"1njn7W\":[\"Light\"],\"2B7HLH\":[\"New post\"],\"2C7mSG\":[\"Collection link\"],\"2ETv7R\":[\"Tune color in a real reading context\"],\"2HbvFp\":[\"Real post components\"],\"2MXb5X\":[\"Field notes on quiet design\"],\"2koDOQ\":[\"Thread accents\"],\"2lKpcz\":[\"Write your first post to get started.\"],\"2q/Q7x\":[\"Visibility\"],\"2sCqzD\":[\"Use this for websites, docs, articles, and other light or neutral surfaces.\"],\"33DClx\":[\"Link to your latest posts. If it comes before Featured, the homepage opens here.\"],\"3Cw1AI\":[\"Add Collection\"],\"3lJk5u\":[\"Keep the artwork unchanged.\"],\"3mdteM\":[\"before deciding whether the accent is carrying too much product energy.\"],\"3neqtf\":[\"Thread accent\"],\"3qkggm\":[\"Fullscreen\"],\"3vMdv3\":[\"This link is reserved. Choose something else.\"],\"3wKq0C\":[\"Couldn't save. Try again in a moment.\"],\"3xi01/\":[\"Look at the footer metadata last, to make sure the accent is not fighting the typography.\"],\"47iMgt\":[\"Editorial interfaces worth borrowing from\"],\"4D09NB\":[\"Link to your collections page\"],\"4HLTdq\":[\"without media\"],\"4J/OYU\":[\"Collection created.\"],\"4eiXo+\":[\"Leave blank to generate one automatically.\"],\"4pV0kE\":[\"Avatar-ready\"],\"51EYZX\":[\"without title\"],\"5dcjwM\":[\"Choose today or an earlier date, or leave it blank to publish now.\"],\"5pAjd8\":[\"Accent should feel present, not loud.\"],\"5sEkBi\":[\"Open raw asset\"],\"6UTABI\":[\"Collection order updated.\"],\"6WAK+2\":[\"Use current date\"],\"6Y4BBO\":[\"An abstract editorial layout in warm paper colors\"],\"6cjUDB\":[\"Brand assets\"],\"6lGV3K\":[\"Show less\"],\"6p0JeQ\":[\"to make sure both still feel like they belong to the same product.\"],\"6sVyMq\":[\"Add a URL before posting this link.\"],\"6yCv8j\":[\"Save these changes to the text attachment, discard them, or keep editing.\"],\"74kJNs\":[\"View earlier notes in this thread\"],\"7DvUqV\":[\"Read the page from top to bottom without looking at the swatches.\"],\"7aris6\":[\"March 15\"],\"7d1a0d\":[\"Public\"],\"7hYXO0\":[\"Use this on dark backgrounds, image-backed surfaces, and any placement where the green logo would lose contrast.\"],\"7kMW54\":[\"Open raw SVG\"],\"7nGhhM\":[\"What's on your mind?\"],\"7vhWI8\":[\"New Password\"],\"87a/t/\":[\"Label\"],\"8Btgys\":[\"Draft deleted.\"],\"8WX0J+\":[\"Your thoughts (optional)\"],\"8ZsakT\":[\"Password\"],\"8bpHix\":[\"Couldn't create your account. Check the details and try again.\"],\"8eC78s\":[\"A calmer accent makes\"],\"8tM8+a\":[\"Save as draft\"],\"90IRF2\":[\"This article is here to answer a specific question: does the default accent still feel calm once it has to carry a full reading experience?\"],\"9SHZas\":[\"Shows 'Settings' when logged in, 'Sign in' when logged out\"],\"9aloPG\":[\"References\"],\"9dr9Nh\":[\"Open external link\"],\"9qWoxS\":[\"Feed\"],\"A1D8Yt\":[\"What the accent should do\"],\"A1taO8\":[\"Search\"],\"A2Vg/u\":[\"Navigation and reading states\"],\"AjHkcv\":[\"Default preview image for social shares and link unfurls.\"],\"AyHO4m\":[\"What's this collection about?\"],\"B1FFMj\":[\"Download Brand Pack\"],\"B495Gs\":[\"Archive\"],\"BdjLtf\":[\"thread\"],\"Bmaby2\":[\"All formats\"],\"C+9df9\":[\"Quoted or highlighted passages should feel like annotations, not warnings.\"],\"C0/57J\":[\"This is the last part of the collection link.\"],\"C4TjpG\":[\"Read less\"],\"CAh1km\":[\"collections\"],\"CH3bgf\":[\"RSS feed for this view\"],\"CT7H2e\":[\"Link to your featured posts. If it comes before Latest, the homepage opens here.\"],\"CmBCXY\":[\"Link updated.\"],\"D4em/+\":[\"Logos\"],\"DHhJ7s\":[\"Previous\"],\"DJLY+/\":[\" and try again.\"],\"DOx286\":[\"Draft restored.\"],\"DPfwMq\":[\"Done\"],\"DSJXZM\":[\"Enter a valid date.\"],\"DYlMYF\":[\"Built-in background\"],\"DoJzLz\":[\"Collections\"],\"Du2B9f\":[\"The default accent should support reading first. Start by comparing it against the\"],\"DxwUcG\":[\"Read the palette as content first\"],\"E3NcGH\":[\"Square logo PNG\"],\"EEYbdt\":[\"Publish\"],\"EGwzOK\":[\"Complete Setup\"],\"EHWwm1\":[\"The default accent should feel written, not branded.\"],\"EO3I6h\":[\"Upload didn't go through. Try again in a moment.\"],\"EQNPYo\":[\"Featured on \",[\"date\"],\" at \",[\"time\"]],\"EQtz4D\":[\"Open a few links and check whether they still feel native to the page.\"],\"EU3tBD\":[\"Link removed.\"],\"EetoJL\":[\"Guide the eye without taking over the layout.\"],\"Eiv3bO\":[\"Buttons can stay steady, but links, thread markers, and subtle emphasis should feel closer to ink on paper than dashboard chrome.\"],\"ElTnWL\":[\"Published on\"],\"EmQw8O\":[\"If this article still feels like a page you want to keep reading, the palette is probably close.\"],\"EsJdRp\":[\"Save theme\"],\"FESYvt\":[\"Describe this for people with visual impairments...\"],\"FEr96N\":[\"Theme\"],\"FGySZL\":[\"The default accent works best when it reads like a fountain-pen underline. Compare it against the\"],\"FM+KeU\":[\"No drafts yet. Save a draft to find it here.\"],\"Fdv5k7\":[\"What to look for while tuning it\"],\"FkMol5\":[\"Featured\"],\"FqCHF/\":[\"Threads\"],\"Fxf4jq\":[\"Description (optional)\"],\"G2u/aQ\":[\"Download official Jant logos, icons, and preview assets.\"],\"GBJzTZ\":[\"Archive\"],\"GX2VMa\":[\"Create your admin account.\"],\"GY/1J4\":[\"Jant fallback canary string\"],\"GbIOhd\":[\"Reply quietly\"],\"GiRWtR\":[\"Why the default accent should feel written, not branded\"],\"GkpIs2\":[\"Remove this link from Collections? The destination won't change.\"],\"GorKul\":[\"Welcome to Jant\"],\"GxkJXS\":[\"Uploading...\"],\"H29JXm\":[\"+ ALT\"],\"H4lgRd\":[\"Authentication isn't set up. Check your server config.\"],\"HFPGej\":[\"No threads match these filters. Try adjusting your selection or clear all filters.\"],\"HG79RB\":[\"Post as Private\"],\"HNEHJP\":[\"Demo credentials are pre-filled — hit Sign In to continue.\"],\"HbAIQc\":[\"A reference link for checking whether the accent feels editorial instead of promotional.\"],\"HrC0ab\":[\"All posts\"],\"Ht1V3q\":[\"For the same reason, inline code should stay neutral. Something like theme.siteAccent = soften(green, 12%) should not suddenly become the loudest thing on the page.\"],\"I22eN0\":[\"Shared links\"],\"I6zLrz\":[\"Use these when you need a transparent square logo, a shaped tile with a built-in background, a browser icon, or a default preview image.\"],\"ICsA6P\":[\"You have unsaved changes\"],\"IUX7p+\":[\"White logo on the Jant green rounded tile for app icon mockups, touch icons, directory listings, and other square placements that should feel softer.\"],\"IagCbF\":[\"URL\"],\"IjnQHI\":[\"with title\"],\"ImOQa9\":[\"Reply\"],\"IsI3kE\":[\"Nothing here yet. Add posts to one of these collections to fill this view.\"],\"J+2Rls\":[\"Leave blank to publish now. Use an earlier date when importing older posts.\"],\"J4tAHl\":[\"Headings should keep their hierarchy even when the accent gets softer.\"],\"JYj5R2\":[\"Browse files\"],\"JcD7qf\":[\"More actions\"],\"JqJ5Xv\":[\"Latest\"],\"JuN5GC\":[\"No file selected. Choose a file to upload.\"],\"JwLPQ/\":[\"This sign-in link has expired. Return to \"],\"KOqvXP\":[\"Do not recolor, stretch, rotate, outline, or add effects to the logo.\"],\"KbS2K9\":[\"Reset Password\"],\"KdSsVl\":[\"Author (optional)\"],\"Khu3PV\":[\"Publish settings\"],\"KiJn9B\":[\"Note\"],\"KlZ+t+\":[\"%name% + %count% more\"],\"KsvRin\":[\"Hide from Latest\"],\"KzmC5L\":[\"Controls\"],\"L7svJg\":[\"Reading\"],\"Lbkbwy\":[\"A quote card for judging accent color against softer, citation-heavy content.\"],\"LcvzvX\":[\"Tap to retry\"],\"LkA8jz\":[\"Add alt text\"],\"LxRg6f\":[\"live theme controls\"],\"M4tzVU\":[\"Latest posts\"],\"M8kJqa\":[\"Drafts\"],\"MHrjPM\":[\"Title\"],\"MILa7n\":[\"Square tile\"],\"MSc/Yq\":[\"Do you want to publish your changes or discard them?\"],\"Mc7+6G\":[\"Enter a valid URL starting with http://, https://, or mailto:.\"],\"MdMyne\":[\"Source link (optional)\"],\"MiMY3Q\":[\"Apple touch icon\"],\"MiyoI7\":[\"default note sample\"],\"MqghUt\":[\"Search posts...\"],\"Myqkib\":[\"Create a collection to get started.\"],\"N8UzTV\":[\"Replies\"],\"NAFbuE\":[\"Search snippet\"],\"NH9Z1R\":[\"Start here\"],\"NqsRbb\":[\"Jant logo\"],\"NvXuWk\":[\"Won't move the thread to the top of latest.\"],\"O1367B\":[\"All collections\"],\"O3oNi5\":[\"Email\"],\"OEdMhi\":[\"The best default color is the one you notice only after reading for a while.\"],\"OEt/to\":[\"Guidelines\"],\"OJxdgi\":[\"Keep this link under 200 characters.\"],\"OaoJcz\":[\"Social preview\"],\"OmfDbR\":[\"Site accent\"],\"Ovks1h\":[\"A softer blue feels more like ink than product chrome.\"],\"P/sHNL\":[\"Use this page to judge buttons, links, cards, forms, thread accents, and quiet surfaces before changing a theme globally.\"],\"Q/uoSA\":[\"Quiet here for now.\"],\"Q2mGA7\":[\"Clear filter\"],\"QBqVyM\":[\"Home screen icon for iPhone and iPad shortcuts.\"],\"QebAts\":[\"Link added.\"],\"Qgbxdw\":[\"Designing a calmer default accent for Jant\"],\"Qn9Ao8\":[\"Circle tile\"],\"Qoq+GP\":[\"Read more\"],\"QyDt3L\":[\"File uploaded.\"],\"R5CMuK\":[\"Jant looks best when the accent feels editorial. Buttons can stay sturdy, but inline emphasis should feel like a pen mark, not a dashboard highlight.\"],\"R8AthW\":[\"Divider\"],\"R9Khdg\":[\"Auto\"],\"RAv3u7\":[\"Compare it against the theme controls\"],\"ROa4Ti\":[\"Interfaces for reading should guide the eye, not keep asking for attention.\"],\"RZOWDv\":[\"Add a custom shortcut to any page or site.\"],\"RdmNnl\":[\"Browser tab\"],\"RfGczC\":[\"Square logo\"],\"Rj01Fz\":[\"Links\"],\"S37om9\":[\"Included assets\"],\"S8NCfs\":[\"Save to drafts to edit and post at a later time.\"],\"SJGVAw\":[\"Feel editorial and slightly quieter.\"],\"SJmfuf\":[\"Site Name\"],\"SaNhJE\":[\"feel deliberate instead of washed out.\"],\"SpTWH3\":[\"Download SVG\"],\"SvRuJt\":[\"Field Notes on Interface Tone\"],\"T/R+Qz\":[\"Primary\"],\"TNZKpI\":[\"Danger\"],\"TvaTxw\":[\"Doesn't appear in Latest. Still appears in collections you add it to.\"],\"UIMXHD\":[\"Remove Divider\"],\"UaZwcz\":[\"More options are available after you create it.\"],\"Uc5y7o\":[\"Choose the standard logo for websites, docs, directories, and editorial layouts.\"],\"V18SVO\":[\"Use the logo on light backgrounds.\"],\"V4WsyL\":[\"Add Link\"],\"VCA6B2\":[\"These are actual feed components with real footers, summaries, and inline links. Use this section to judge whether the theme still feels calm once it is applied to realistic content.\"],\"VNqFYa\":[\"Loading post...\"],\"WCOanD\":[\"This reference is useful because it treats links and citations as part of the reading rhythm. Keep that in mind while tuning the\"],\"WbIbzR\":[\"Checking link...\"],\"WcWS//\":[\"Download file\"],\"WhsN3P\":[\"A good default accent in Jant should feel like editorial structure, not product branding. That means links, emphasis, and thread cues can be visible without turning the page into UI chrome.\"],\"Wn+/rH\":[\"Transparent square\"],\"XU7b+L\":[\"Primary logo files\"],\"XV1mAn\":[\"Only visible when signed in.\"],\"XrnWzN\":[\"Published!\"],\"YIix5Y\":[\"Search...\"],\"YUglt2\":[\"Generating a link...\"],\"YXiA6e\":[\"Primary button\"],\"Ygx3Yl\":[\"Small browser icon used in tabs and bookmarks.\"],\"Z6NwTi\":[\"Save as Draft\"],\"ZGs2so\":[\"Delete this collection permanently? Posts inside won't be removed.\"],\"ZV5ykW\":[\"Download PNG\"],\"ZhhOwV\":[\"Quote\"],\"ZmSeP+\":[\"Save to drafts?\"],\"ZxFuun\":[[\"count\",\"plural\",{\"one\":[\"Found \",\"#\",\" result\"],\"other\":[\"Found \",\"#\",\" results\"]}]],\"a5j82I\":[\"No collections match that search. Try a different name.\"],\"aHTB7P\":[\"Supplementary content attached to your post\"],\"aMEyv0\":[\"Stay sturdy and readable.\"],\"aN6wx0\":[\"Nothing in Featured yet. Mark a post as featured to show it here.\"],\"aYpXKS\":[\"and checking whether the accent is guiding attention or pulling too hard.\"],\"aaGV/9\":[\"New Link\"],\"af+9p6\":[\"Quiet metadata\"],\"an5hVd\":[\"Images\"],\"ao77hr\":[[\"count\",\"plural\",{\"one\":[\"#\",\" hidden post\"],\"other\":[\"#\",\" hidden posts\"]}]],\"auFlOr\":[\"Icons and previews\"],\"avuFKG\":[\"threads\"],\"bFpC86\":[\"Everything in one download\"],\"bGtMpA\":[\"Add a label and URL.\"],\"bHOiy1\":[\"Password changes are off in demo mode. Sign in with the shared demo credentials.\"],\"bbdNeX\":[\"Sign in\"],\"bfCbdi\":[\"Current post\"],\"bkBJmZ\":[\"This is useful as a color check because it puts the accent next to quotation styling, metadata, and a quieter explanatory paragraph. Compare it back to the\"],\"bzSI52\":[\"Discard\"],\"c2JRUS\":[\"Generate automatically\"],\"cIoW7X\":[\"Inline link\"],\"cTUByn\":[\"Newest first\"],\"cb7FR8\":[\"White logo on the Jant green square tile for platforms and layouts that expect a true edge-to-edge square.\"],\"cgmi4V\":[\"Delete Draft\"],\"cnGeoo\":[\"Delete\"],\"d+F4pf\":[\"The image should sit quietly inside the article instead of feeling like a card preview.\"],\"d/o/BH\":[\"Couldn't publish. Saved as draft.\"],\"dD7NPy\":[\"Outline\"],\"dEgA5A\":[\"Cancel\"],\"dHko2w\":[\"single posts\"],\"dUsGbd\":[\"The right accent should disappear into the writing until you need it.\"],\"dXoieq\":[\"Summary\"],\"dYKrp3\":[\"Hidden from Latest\"],\"dbUuAj\":[\"Appears in Latest.\"],\"df4a/r\":[\"Couldn't load this post. Try again.\"],\"ePK91l\":[\"Edit\"],\"eWLklq\":[\"Quotes\"],\"f4MAoA\":[\"Some uploads failed. Saved as draft.\"],\"f5s9EI\":[\"Press N to write\"],\"f6Hub0\":[\"Sort\"],\"f8fH8W\":[\"Design\"],\"fD+f7T\":[\"RSS feed\"],\"fKrDxS\":[\"Brand tile\"],\"fMPkxb\":[\"Show more\"],\"fqDzSu\":[\"Rate\"],\"fttd2R\":[\"My Collection\"],\"gCcxP/\":[\"Threads can include up to \",[\"count\"],\" posts.\"],\"gFdWl+\":[\"A long-form article sample for checking the default palette in a true reading context.\"],\"gNKz6Z\":[\"Collection deleted.\"],\"gXH9r/\":[\"Open raw PNG\"],\"gj52YE\":[\"This collection is empty. Add posts from the editor.\"],\"gpaPhA\":[\"Helps screen readers describe the image\"],\"h5RcXU\":[\"Post hidden\"],\"hLlWo5\":[\"A few simple rules.\"],\"hWpUeY\":[\"Auto link\"],\"hXzOVo\":[\"Next\"],\"heSQoS\":[\"Paste a URL...\"],\"hqeXKW\":[\"Single posts\"],\"hrkGms\":[\"Search\"],\"i0vDGK\":[\"Sort Order\"],\"i5+Y7d\":[\"Download the official Jant logo, icons, and preview files.\"],\"i6kro6\":[\"Edit custom link\"],\"i6nDCI\":[\"Choose a new password.\"],\"iG7KNr\":[\"Logo\"],\"iH8pgl\":[\"Back\"],\"ilSmIt\":[\"Hard edge\"],\"iu7tUI\":[\"Breadcrumb\"],\"jAXE5p\":[\"Reverse logo\"],\"jAqB/k\":[\"Post privately\"],\"jQflRT\":[\"This uses the real single-post detail rendering with a longer article, inline image, tables, lists, quotes, and code. The content column stays at the same width as the live site.\"],\"jd+8Mm\":[\"Social preview image\"],\"jdJOV1\":[\"Settings\"],\"ji7oVU\":[\"Edit post\"],\"jpctdh\":[\"View\"],\"jrsUoG\":[\"Type / for commands\"],\"jvyYZG\":[\"What's on your mind...\"],\"k3Iw35\":[\"Switch to the white logo when the standard green version would lose contrast.\"],\"kPMIr+\":[\"Give it a title...\"],\"kj6ppi\":[\"entry\"],\"kr39oD\":[\"No collections yet. Start one to organize posts by topic.\"],\"kzvWob\":[\"Link to the post archive\"],\"laT1IJ\":[\"iOS home screen\"],\"lb+Xwx\":[\"Custom link\"],\"m16xKo\":[\"Add\"],\"mKT7g0\":[\"Text attachment\"],\"mc/vLq\":[\"This link is already in use. Choose something else.\"],\"muKqfV\":[\"Featured\"],\"n1ekoW\":[\"Sign In\"],\"n3ReIn\":[\"Collections\"],\"n6QD94\":[\"Oldest first\"],\"nFukaP\":[\"Wrong email or password. Check your credentials and try again.\"],\"nV6twc\":[\"Organize\"],\"nd8Puv\":[\"White logo on the Jant green circle for profile images, badges, and other round placements where you want a ready-made asset.\"],\"ndrEYW\":[\"When the accent is slightly warmer and less literal, the whole page feels more like a writing space and less like product UI.\"],\"o21Y+P\":[\"entries\"],\"oO0hKx\":[[\"count\",\"plural\",{\"one\":[\"#\",\" more post\"],\"other\":[\"#\",\" more posts\"]}]],\"oTu7Wt\":[\"Combined Collections\"],\"ode0+L\":[\"Theme sample\"],\"ogssnn\":[\"with media\"],\"ovBPCi\":[\"Default\"],\"p1Z67P\":[\"When primary is too rigid, the whole page starts reading like product UI instead of writing space.\"],\"p2/GCq\":[\"Confirm Password\"],\"pB0OKE\":[\"New Divider\"],\"pBHx39\":[\"Dark backgrounds\"],\"pVrU5x\":[\"If this page feels too branded, the first place to soften is the default theme’s site accent, not the border or body text.\"],\"pvnfJD\":[\"Dark\"],\"q+hNag\":[\"Collection\"],\"q5YRzz\":[\"Color check\"],\"q8RviX\":[\"Titled\"],\"qcawwg\":[\"Publish now\"],\"qiN9NB\":[\"Surface\"],\"qt89I8\":[\"Draft saved.\"],\"quvfGs\":[\"instead of judging it as an isolated swatch.\"],\"r7kcaA\":[\"Drag collections, links, and dividers into the order you want.\"],\"rA2TFI\":[\"Switch the palette and mode without opening settings or changing the active site theme.\"],\"rV8ZnP\":[\"Edit publish date\"],\"rdUucN\":[\"Preview\"],\"s8G5Or\":[\"This upload would exceed your shared hosted media limit. Remove files or upgrade storage to continue.\"],\"s9gHf5\":[\"your-post-link\"],\"sER+bs\":[\"Files\"],\"sQpDn6\":[\"Exit fullscreen\"],\"sgr2wQ\":[\"collection\"],\"slujBW\":[\"Use lowercase letters, numbers, and hyphens only.\"],\"syiAKf\":[\"note treatment\"],\"t42hIC\":[\"Everything most people need is in one ZIP.\"],\"tCctex\":[\"The brand pack includes SVG logos, a transparent square PNG, rounded, square, and circle tiles, plus favicon, Apple touch icon, and the default social preview image.\"],\"tKlWWY\":[\"Emoji\"],\"tSWVu5\":[\"Published on \",[\"date\"],\" at \",[\"time\"]],\"tfDRzk\":[\"Save\"],\"tg5MRw\":[\"Sign in to start writing.\"],\"tgSBSE\":[\"Remove Link\"],\"uowbPn\":[\"Remove attachment\"],\"v3E8iS\":[\"A practical checklist\"],\"vSJd18\":[\"Video\"],\"vSYKYI\":[\"Main feed\"],\"vXCC6J\":[\"Something doesn't look right. Check the form and try again.\"],\"vcpc5o\":[\"Close menu\"],\"vdFnYM\":[\"Reset link\"],\"vdvpU5\":[\"/archive?format=quote or https://example.com\"],\"vgpfCi\":[\"Save draft\"],\"vpSPA1\":[\"Auth secret is missing. Check your environment variables.\"],\"vzU4k9\":[\"New Collection\"],\"w0Emel\":[\"Suggested link\"],\"w6mlns\":[\"Article detail page\"],\"wJ+GRy\":[\"All visibility\"],\"wL3cK8\":[\"Latest\"],\"wja8aL\":[\"Untitled\"],\"wlnK1t\":[\"A single ZIP with the main logo, reverse logo, square PNG, rounded, square, and circle tiles, plus favicon, Apple touch icon, and social preview image.\"],\"wm3Zlr\":[\"All years\"],\"xCWek4\":[\"File storage isn't set up. Check your server config.\"],\"xVrkxi\":[\"quiet design\"],\"xVvw1i\":[\"This reset link is no longer valid. Request a new one to continue.\"],\"xYilR2\":[\"Media\"],\"xeiujy\":[\"Text\"],\"xhTx3y\":[\"Choose the standard logo for most placements and the reverse logo when you need more contrast.\"],\"xjHB3/\":[\"Continue →\"],\"y28hnO\":[\"Post\"],\"y2o/Y0\":[\"This Link Has Expired\"],\"yGZVl1\":[\"More\"],\"yQ2kGp\":[\"Load more\"],\"yUtAh2\":[\"New Thread\"],\"ycM1Xg\":[\"No results. Try different keywords.\"],\"ynMAhG\":[\"Default logo\"],\"yzF66j\":[\"Link\"],\"zBFr9G\":[\"Paste a long article, AI response, or any text...\\n\\nMarkdown formatting will be preserved.\"],\"zJDAbh\":[\"Don't save\"],\"zcDmsG\":[\"Featured posts\"],\"zoK+eO\":[\"Add a title before posting this link.\"],\"zucql+\":[\"Menu\"],\"zwBp5t\":[\"Private\"]}")as Messages;
|
|
1
|
+
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"+4u2g6\":[\"A ready-made 1:1 PNG for decks, mockups, directories, and other square placements.\"],\"+DPYOZ\":[\"Add a link to your main RSS feed. Change what /feed returns in General.\"],\"+G8qqW\":[\"Collection saved.\"],\"+IJm1Z\":[\"Muted\"],\"+Irvp3\":[\"Everything on this page is ready to use for articles, launch posts, directories, and product coverage.\"],\"+Qaboy\":[\"Favicon\"],\"+fWu2O\":[\"A calmer, warmer accent makes the default theme feel quieter and more intentional.\"],\"+nHhRH\":[\"Use \",[\"brandColorName\"]],\"+siMqD\":[\"Journal\"],\"/DFKdU\":[\"Type the quote...\"],\"/PfPLc\":[\"Label (optional)\"],\"/PoNoq\":[\"Edit link\"],\"/Ui2OV\":[\"Use the reverse logo on dark backgrounds.\"],\"/Ybds4\":[\"Primary Jant logo for websites, docs, press coverage, and editorial layouts.\"],\"/rTz0M\":[\"Audio\"],\"0EcUWz\":[\"Discard changes?\"],\"0Lj7or\":[\"Save text attachment?\"],\"0XDp7X\":[\"Links should read clearly without glowing against the page.\"],\"0ieXE7\":[\"Highest rated\"],\"11h9eK\":[\"Includes\"],\"15++NM\":[\"Inline emphasis\"],\"1DBGsz\":[\"Notes\"],\"1NeeWI\":[\"Square assets for avatars, apps, browsers, and shared links\"],\"1THMr2\":[\"Brand pack\"],\"1njn7W\":[\"Light\"],\"2B7HLH\":[\"New post\"],\"2C7mSG\":[\"Collection link\"],\"2ETv7R\":[\"Tune color in a real reading context\"],\"2HbvFp\":[\"Real post components\"],\"2MXb5X\":[\"Field notes on quiet design\"],\"2koDOQ\":[\"Thread accents\"],\"2lKpcz\":[\"Write your first post to get started.\"],\"2q/Q7x\":[\"Visibility\"],\"2sCqzD\":[\"Use this for websites, docs, articles, and other light or neutral surfaces.\"],\"33DClx\":[\"Link to your latest posts. If it comes before Featured, the homepage opens here.\"],\"3Cw1AI\":[\"Add Collection\"],\"3lJk5u\":[\"Keep the artwork unchanged.\"],\"3mdteM\":[\"before deciding whether the accent is carrying too much product energy.\"],\"3neqtf\":[\"Thread accent\"],\"3qkggm\":[\"Fullscreen\"],\"3vMdv3\":[\"This link is reserved. Choose something else.\"],\"3wKq0C\":[\"Couldn't save. Try again in a moment.\"],\"3xi01/\":[\"Look at the footer metadata last, to make sure the accent is not fighting the typography.\"],\"47iMgt\":[\"Editorial interfaces worth borrowing from\"],\"4D09NB\":[\"Link to your collections page\"],\"4HLTdq\":[\"without media\"],\"4J/OYU\":[\"Collection created.\"],\"4eiXo+\":[\"Leave blank to generate one automatically.\"],\"4pV0kE\":[\"Avatar-ready\"],\"51EYZX\":[\"without title\"],\"5dcjwM\":[\"Choose today or an earlier date, or leave it blank to publish now.\"],\"5pAjd8\":[\"Accent should feel present, not loud.\"],\"5sEkBi\":[\"Open raw asset\"],\"6UTABI\":[\"Collection order updated.\"],\"6WAK+2\":[\"Use current date\"],\"6Y4BBO\":[\"An abstract editorial layout in warm paper colors\"],\"6cjUDB\":[\"Brand assets\"],\"6lGV3K\":[\"Show less\"],\"6p0JeQ\":[\"to make sure both still feel like they belong to the same product.\"],\"6sVyMq\":[\"Add a URL before posting this link.\"],\"6yCv8j\":[\"Save these changes to the text attachment, discard them, or keep editing.\"],\"74kJNs\":[\"View earlier notes in this thread\"],\"7DvUqV\":[\"Read the page from top to bottom without looking at the swatches.\"],\"7aris6\":[\"March 15\"],\"7d1a0d\":[\"Public\"],\"7hYXO0\":[\"Use this on dark backgrounds, image-backed surfaces, and any placement where the green logo would lose contrast.\"],\"7kMW54\":[\"Open raw SVG\"],\"7nGhhM\":[\"What's on your mind?\"],\"7vhWI8\":[\"New Password\"],\"87a/t/\":[\"Label\"],\"8Btgys\":[\"Draft deleted.\"],\"8WX0J+\":[\"Your thoughts (optional)\"],\"8ZsakT\":[\"Password\"],\"8bpHix\":[\"Couldn't create your account. Check the details and try again.\"],\"8eC78s\":[\"A calmer accent makes\"],\"8tM8+a\":[\"Save as draft\"],\"90IRF2\":[\"This article is here to answer a specific question: does the default accent still feel calm once it has to carry a full reading experience?\"],\"9SHZas\":[\"Shows 'Settings' when logged in, 'Sign in' when logged out\"],\"9aloPG\":[\"References\"],\"9dr9Nh\":[\"Open external link\"],\"9qWoxS\":[\"Feed\"],\"A1D8Yt\":[\"What the accent should do\"],\"A1taO8\":[\"Search\"],\"A2Vg/u\":[\"Navigation and reading states\"],\"AjHkcv\":[\"Default preview image for social shares and link unfurls.\"],\"AyHO4m\":[\"What's this collection about?\"],\"B1FFMj\":[\"Download Brand Pack\"],\"B495Gs\":[\"Archive\"],\"BdjLtf\":[\"thread\"],\"Bmaby2\":[\"All formats\"],\"C+9df9\":[\"Quoted or highlighted passages should feel like annotations, not warnings.\"],\"C0/57J\":[\"This is the last part of the collection link.\"],\"C4TjpG\":[\"Read less\"],\"CAh1km\":[\"collections\"],\"CH3bgf\":[\"RSS feed for this view\"],\"CT7H2e\":[\"Link to your featured posts. If it comes before Latest, the homepage opens here.\"],\"CmBCXY\":[\"Link updated.\"],\"D4em/+\":[\"Logos\"],\"DHhJ7s\":[\"Previous\"],\"DJLY+/\":[\" and try again.\"],\"DOx286\":[\"Draft restored.\"],\"DPfwMq\":[\"Done\"],\"DSJXZM\":[\"Enter a valid date.\"],\"DYlMYF\":[\"Built-in background\"],\"DoJzLz\":[\"Collections\"],\"Du2B9f\":[\"The default accent should support reading first. Start by comparing it against the\"],\"DxwUcG\":[\"Read the palette as content first\"],\"E3NcGH\":[\"Square logo PNG\"],\"EEYbdt\":[\"Publish\"],\"EGwzOK\":[\"Complete Setup\"],\"EHWwm1\":[\"The default accent should feel written, not branded.\"],\"EO3I6h\":[\"Upload didn't go through. Try again in a moment.\"],\"EQNPYo\":[\"Featured on \",[\"date\"],\" at \",[\"time\"]],\"EQtz4D\":[\"Open a few links and check whether they still feel native to the page.\"],\"EU3tBD\":[\"Link removed.\"],\"EetoJL\":[\"Guide the eye without taking over the layout.\"],\"Eiv3bO\":[\"Buttons can stay steady, but links, thread markers, and subtle emphasis should feel closer to ink on paper than dashboard chrome.\"],\"ElTnWL\":[\"Published on\"],\"EmQw8O\":[\"If this article still feels like a page you want to keep reading, the palette is probably close.\"],\"EsJdRp\":[\"Save theme\"],\"FESYvt\":[\"Describe this for people with visual impairments...\"],\"FEr96N\":[\"Theme\"],\"FGySZL\":[\"The default accent works best when it reads like a fountain-pen underline. Compare it against the\"],\"FM+KeU\":[\"No drafts yet. Save a draft to find it here.\"],\"Fdv5k7\":[\"What to look for while tuning it\"],\"FkMol5\":[\"Featured\"],\"FqCHF/\":[\"Threads\"],\"Fxf4jq\":[\"Description (optional)\"],\"G2u/aQ\":[\"Download official Jant logos, icons, and preview assets.\"],\"GBJzTZ\":[\"Archive\"],\"GX2VMa\":[\"Create your admin account.\"],\"GY/1J4\":[\"Jant fallback canary string\"],\"GbIOhd\":[\"Reply quietly\"],\"GiRWtR\":[\"Why the default accent should feel written, not branded\"],\"GkpIs2\":[\"Remove this link from Collections? The destination won't change.\"],\"GorKul\":[\"Welcome to Jant\"],\"GxkJXS\":[\"Uploading...\"],\"H29JXm\":[\"+ ALT\"],\"H4lgRd\":[\"Authentication isn't set up. Check your server config.\"],\"HFPGej\":[\"No threads match these filters. Try adjusting your selection or clear all filters.\"],\"HG79RB\":[\"Post as Private\"],\"HNEHJP\":[\"Demo credentials are pre-filled — hit Sign In to continue.\"],\"HbAIQc\":[\"A reference link for checking whether the accent feels editorial instead of promotional.\"],\"HrC0ab\":[\"All posts\"],\"Ht1V3q\":[\"For the same reason, inline code should stay neutral. Something like theme.siteAccent = soften(green, 12%) should not suddenly become the loudest thing on the page.\"],\"I22eN0\":[\"Shared links\"],\"I6zLrz\":[\"Use these when you need a transparent square logo, a shaped tile with a built-in background, a browser icon, or a default preview image.\"],\"ICsA6P\":[\"You have unsaved changes\"],\"IUX7p+\":[\"White logo on the Jant green rounded tile for app icon mockups, touch icons, directory listings, and other square placements that should feel softer.\"],\"IagCbF\":[\"URL\"],\"IjnQHI\":[\"with title\"],\"ImOQa9\":[\"Reply\"],\"IsI3kE\":[\"Nothing here yet. Add posts to one of these collections to fill this view.\"],\"J+2Rls\":[\"Leave blank to publish now. Use an earlier date when importing older posts.\"],\"J4tAHl\":[\"Headings should keep their hierarchy even when the accent gets softer.\"],\"JYj5R2\":[\"Browse files\"],\"JcD7qf\":[\"More actions\"],\"JqJ5Xv\":[\"Latest\"],\"JuN5GC\":[\"No file selected. Choose a file to upload.\"],\"JwLPQ/\":[\"This sign-in link has expired. Return to \"],\"KOqvXP\":[\"Do not recolor, stretch, rotate, outline, or add effects to the logo.\"],\"KbS2K9\":[\"Reset Password\"],\"KdSsVl\":[\"Author (optional)\"],\"Khu3PV\":[\"Publish settings\"],\"KiJn9B\":[\"Note\"],\"KlZ+t+\":[\"%name% + %count% more\"],\"KsvRin\":[\"Hide from Latest\"],\"KzmC5L\":[\"Controls\"],\"L7svJg\":[\"Reading\"],\"Lbkbwy\":[\"A quote card for judging accent color against softer, citation-heavy content.\"],\"LcvzvX\":[\"Tap to retry\"],\"LkA8jz\":[\"Add alt text\"],\"LxRg6f\":[\"live theme controls\"],\"M4tzVU\":[\"Latest posts\"],\"M8kJqa\":[\"Drafts\"],\"MHrjPM\":[\"Title\"],\"MILa7n\":[\"Square tile\"],\"MSc/Yq\":[\"Do you want to publish your changes or discard them?\"],\"Mc7+6G\":[\"Enter a valid URL starting with http://, https://, or mailto:.\"],\"MdMyne\":[\"Source link (optional)\"],\"MiMY3Q\":[\"Apple touch icon\"],\"MiyoI7\":[\"default note sample\"],\"MqghUt\":[\"Search posts...\"],\"Myqkib\":[\"Create a collection to get started.\"],\"N8UzTV\":[\"Replies\"],\"NAFbuE\":[\"Search snippet\"],\"NH9Z1R\":[\"Start here\"],\"NqsRbb\":[\"Jant logo\"],\"NvXuWk\":[\"Won't move the thread to the top of latest.\"],\"O1367B\":[\"All collections\"],\"O3oNi5\":[\"Email\"],\"OEdMhi\":[\"The best default color is the one you notice only after reading for a while.\"],\"OEt/to\":[\"Guidelines\"],\"OJxdgi\":[\"Keep this link under 200 characters.\"],\"OaoJcz\":[\"Social preview\"],\"OmfDbR\":[\"Site accent\"],\"Ovks1h\":[\"A softer blue feels more like ink than product chrome.\"],\"P/sHNL\":[\"Use this page to judge buttons, links, cards, forms, thread accents, and quiet surfaces before changing a theme globally.\"],\"Q/uoSA\":[\"Quiet here for now.\"],\"Q2mGA7\":[\"Clear filter\"],\"QBqVyM\":[\"Home screen icon for iPhone and iPad shortcuts.\"],\"QebAts\":[\"Link added.\"],\"Qgbxdw\":[\"Designing a calmer default accent for Jant\"],\"Qn9Ao8\":[\"Circle tile\"],\"Qoq+GP\":[\"Read more\"],\"QyDt3L\":[\"File uploaded.\"],\"R5CMuK\":[\"Jant looks best when the accent feels editorial. Buttons can stay sturdy, but inline emphasis should feel like a pen mark, not a dashboard highlight.\"],\"R8AthW\":[\"Divider\"],\"R9Khdg\":[\"Auto\"],\"RAv3u7\":[\"Compare it against the theme controls\"],\"ROa4Ti\":[\"Interfaces for reading should guide the eye, not keep asking for attention.\"],\"RZOWDv\":[\"Add a custom shortcut to any page or site.\"],\"RdmNnl\":[\"Browser tab\"],\"RfGczC\":[\"Square logo\"],\"Rj01Fz\":[\"Links\"],\"S37om9\":[\"Included assets\"],\"S8NCfs\":[\"Save to drafts to edit and post at a later time.\"],\"SJGVAw\":[\"Feel editorial and slightly quieter.\"],\"SJmfuf\":[\"Site Name\"],\"SaNhJE\":[\"feel deliberate instead of washed out.\"],\"SpTWH3\":[\"Download SVG\"],\"SvRuJt\":[\"Field Notes on Interface Tone\"],\"T/R+Qz\":[\"Primary\"],\"TNZKpI\":[\"Danger\"],\"TvaTxw\":[\"Doesn't appear in Latest. Still appears in collections you add it to.\"],\"UIMXHD\":[\"Remove Divider\"],\"UaZwcz\":[\"More options are available after you create it.\"],\"Uc5y7o\":[\"Choose the standard logo for websites, docs, directories, and editorial layouts.\"],\"V18SVO\":[\"Use the logo on light backgrounds.\"],\"V4WsyL\":[\"Add Link\"],\"VCA6B2\":[\"These are actual feed components with real footers, summaries, and inline links. Use this section to judge whether the theme still feels calm once it is applied to realistic content.\"],\"VNqFYa\":[\"Loading post...\"],\"WCOanD\":[\"This reference is useful because it treats links and citations as part of the reading rhythm. Keep that in mind while tuning the\"],\"WbIbzR\":[\"Checking link...\"],\"WcWS//\":[\"Download file\"],\"WhsN3P\":[\"A good default accent in Jant should feel like editorial structure, not product branding. That means links, emphasis, and thread cues can be visible without turning the page into UI chrome.\"],\"Wn+/rH\":[\"Transparent square\"],\"XU7b+L\":[\"Primary logo files\"],\"XV1mAn\":[\"Only visible when signed in.\"],\"XrnWzN\":[\"Published!\"],\"Y7WAtz\":[\"An image couldn't be saved to your library — its original link was kept.\"],\"YIix5Y\":[\"Search...\"],\"YUglt2\":[\"Generating a link...\"],\"YXiA6e\":[\"Primary button\"],\"Ygx3Yl\":[\"Small browser icon used in tabs and bookmarks.\"],\"Z6NwTi\":[\"Save as Draft\"],\"ZGs2so\":[\"Delete this collection permanently? Posts inside won't be removed.\"],\"ZV5ykW\":[\"Download PNG\"],\"ZhhOwV\":[\"Quote\"],\"ZmSeP+\":[\"Save to drafts?\"],\"ZxFuun\":[[\"count\",\"plural\",{\"one\":[\"Found \",\"#\",\" result\"],\"other\":[\"Found \",\"#\",\" results\"]}]],\"a5j82I\":[\"No collections match that search. Try a different name.\"],\"aHTB7P\":[\"Supplementary content attached to your post\"],\"aMEyv0\":[\"Stay sturdy and readable.\"],\"aN6wx0\":[\"Nothing in Featured yet. Mark a post as featured to show it here.\"],\"aYpXKS\":[\"and checking whether the accent is guiding attention or pulling too hard.\"],\"aaGV/9\":[\"New Link\"],\"af+9p6\":[\"Quiet metadata\"],\"an5hVd\":[\"Images\"],\"ao77hr\":[[\"count\",\"plural\",{\"one\":[\"#\",\" hidden post\"],\"other\":[\"#\",\" hidden posts\"]}]],\"auFlOr\":[\"Icons and previews\"],\"avuFKG\":[\"threads\"],\"bFpC86\":[\"Everything in one download\"],\"bGtMpA\":[\"Add a label and URL.\"],\"bHOiy1\":[\"Password changes are off in demo mode. Sign in with the shared demo credentials.\"],\"bbdNeX\":[\"Sign in\"],\"bfCbdi\":[\"Current post\"],\"bkBJmZ\":[\"This is useful as a color check because it puts the accent next to quotation styling, metadata, and a quieter explanatory paragraph. Compare it back to the\"],\"bzSI52\":[\"Discard\"],\"c2JRUS\":[\"Generate automatically\"],\"cIoW7X\":[\"Inline link\"],\"cTUByn\":[\"Newest first\"],\"cb7FR8\":[\"White logo on the Jant green square tile for platforms and layouts that expect a true edge-to-edge square.\"],\"cgmi4V\":[\"Delete Draft\"],\"cnGeoo\":[\"Delete\"],\"d+F4pf\":[\"The image should sit quietly inside the article instead of feeling like a card preview.\"],\"d/o/BH\":[\"Couldn't publish. Saved as draft.\"],\"dD7NPy\":[\"Outline\"],\"dEgA5A\":[\"Cancel\"],\"dHko2w\":[\"single posts\"],\"dUsGbd\":[\"The right accent should disappear into the writing until you need it.\"],\"dXoieq\":[\"Summary\"],\"dYKrp3\":[\"Hidden from Latest\"],\"dbUuAj\":[\"Appears in Latest.\"],\"df4a/r\":[\"Couldn't load this post. Try again.\"],\"ePK91l\":[\"Edit\"],\"eWLklq\":[\"Quotes\"],\"f4MAoA\":[\"Some uploads failed. Saved as draft.\"],\"f5s9EI\":[\"Press N to write\"],\"f6Hub0\":[\"Sort\"],\"f8fH8W\":[\"Design\"],\"fD+f7T\":[\"RSS feed\"],\"fKrDxS\":[\"Brand tile\"],\"fMPkxb\":[\"Show more\"],\"fqDzSu\":[\"Rate\"],\"fttd2R\":[\"My Collection\"],\"gCcxP/\":[\"Threads can include up to \",[\"count\"],\" posts.\"],\"gFdWl+\":[\"A long-form article sample for checking the default palette in a true reading context.\"],\"gNKz6Z\":[\"Collection deleted.\"],\"gXH9r/\":[\"Open raw PNG\"],\"gj52YE\":[\"This collection is empty. Add posts from the editor.\"],\"gpaPhA\":[\"Helps screen readers describe the image\"],\"h5RcXU\":[\"Post hidden\"],\"hLlWo5\":[\"A few simple rules.\"],\"hWpUeY\":[\"Auto link\"],\"hXzOVo\":[\"Next\"],\"heSQoS\":[\"Paste a URL...\"],\"hqeXKW\":[\"Single posts\"],\"hrkGms\":[\"Search\"],\"i0vDGK\":[\"Sort Order\"],\"i5+Y7d\":[\"Download the official Jant logo, icons, and preview files.\"],\"i6kro6\":[\"Edit custom link\"],\"i6nDCI\":[\"Choose a new password.\"],\"iG7KNr\":[\"Logo\"],\"iH8pgl\":[\"Back\"],\"ilSmIt\":[\"Hard edge\"],\"iu7tUI\":[\"Breadcrumb\"],\"jAXE5p\":[\"Reverse logo\"],\"jAqB/k\":[\"Post privately\"],\"jQflRT\":[\"This uses the real single-post detail rendering with a longer article, inline image, tables, lists, quotes, and code. The content column stays at the same width as the live site.\"],\"jd+8Mm\":[\"Social preview image\"],\"jdJOV1\":[\"Settings\"],\"ji7oVU\":[\"Edit post\"],\"jpctdh\":[\"View\"],\"jrsUoG\":[\"Type / for commands\"],\"jvyYZG\":[\"What's on your mind...\"],\"k3Iw35\":[\"Switch to the white logo when the standard green version would lose contrast.\"],\"kPMIr+\":[\"Give it a title...\"],\"kj6ppi\":[\"entry\"],\"kr39oD\":[\"No collections yet. Start one to organize posts by topic.\"],\"kzvWob\":[\"Link to the post archive\"],\"laT1IJ\":[\"iOS home screen\"],\"lb+Xwx\":[\"Custom link\"],\"m16xKo\":[\"Add\"],\"mKT7g0\":[\"Text attachment\"],\"mc/vLq\":[\"This link is already in use. Choose something else.\"],\"muKqfV\":[\"Featured\"],\"n1ekoW\":[\"Sign In\"],\"n3ReIn\":[\"Collections\"],\"n6QD94\":[\"Oldest first\"],\"nFukaP\":[\"Wrong email or password. Check your credentials and try again.\"],\"nJ4U1U\":[[\"count\"],\" images couldn't be saved to your library — their original links were kept.\"],\"nV6twc\":[\"Organize\"],\"nd8Puv\":[\"White logo on the Jant green circle for profile images, badges, and other round placements where you want a ready-made asset.\"],\"ndrEYW\":[\"When the accent is slightly warmer and less literal, the whole page feels more like a writing space and less like product UI.\"],\"o21Y+P\":[\"entries\"],\"oO0hKx\":[[\"count\",\"plural\",{\"one\":[\"#\",\" more post\"],\"other\":[\"#\",\" more posts\"]}]],\"oTu7Wt\":[\"Combined Collections\"],\"ode0+L\":[\"Theme sample\"],\"ogssnn\":[\"with media\"],\"ovBPCi\":[\"Default\"],\"p1Z67P\":[\"When primary is too rigid, the whole page starts reading like product UI instead of writing space.\"],\"p2/GCq\":[\"Confirm Password\"],\"pB0OKE\":[\"New Divider\"],\"pBHx39\":[\"Dark backgrounds\"],\"pVrU5x\":[\"If this page feels too branded, the first place to soften is the default theme’s site accent, not the border or body text.\"],\"pvnfJD\":[\"Dark\"],\"q+hNag\":[\"Collection\"],\"q5YRzz\":[\"Color check\"],\"q8RviX\":[\"Titled\"],\"qcawwg\":[\"Publish now\"],\"qiN9NB\":[\"Surface\"],\"qt89I8\":[\"Draft saved.\"],\"quvfGs\":[\"instead of judging it as an isolated swatch.\"],\"r7kcaA\":[\"Drag collections, links, and dividers into the order you want.\"],\"rA2TFI\":[\"Switch the palette and mode without opening settings or changing the active site theme.\"],\"rV8ZnP\":[\"Edit publish date\"],\"rdUucN\":[\"Preview\"],\"s8G5Or\":[\"This upload would exceed your shared hosted media limit. Remove files or upgrade storage to continue.\"],\"s9gHf5\":[\"your-post-link\"],\"sER+bs\":[\"Files\"],\"sQpDn6\":[\"Exit fullscreen\"],\"sgr2wQ\":[\"collection\"],\"slujBW\":[\"Use lowercase letters, numbers, and hyphens only.\"],\"syiAKf\":[\"note treatment\"],\"t42hIC\":[\"Everything most people need is in one ZIP.\"],\"tCctex\":[\"The brand pack includes SVG logos, a transparent square PNG, rounded, square, and circle tiles, plus favicon, Apple touch icon, and the default social preview image.\"],\"tKlWWY\":[\"Emoji\"],\"tSWVu5\":[\"Published on \",[\"date\"],\" at \",[\"time\"]],\"tfDRzk\":[\"Save\"],\"tg5MRw\":[\"Sign in to start writing.\"],\"tgSBSE\":[\"Remove Link\"],\"uowbPn\":[\"Remove attachment\"],\"v3E8iS\":[\"A practical checklist\"],\"vSJd18\":[\"Video\"],\"vSYKYI\":[\"Main feed\"],\"vXCC6J\":[\"Something doesn't look right. Check the form and try again.\"],\"vcpc5o\":[\"Close menu\"],\"vdFnYM\":[\"Reset link\"],\"vdvpU5\":[\"/archive?format=quote or https://example.com\"],\"vgpfCi\":[\"Save draft\"],\"vpSPA1\":[\"Auth secret is missing. Check your environment variables.\"],\"vzU4k9\":[\"New Collection\"],\"w0Emel\":[\"Suggested link\"],\"w6mlns\":[\"Article detail page\"],\"wJ+GRy\":[\"All visibility\"],\"wL3cK8\":[\"Latest\"],\"wja8aL\":[\"Untitled\"],\"wlnK1t\":[\"A single ZIP with the main logo, reverse logo, square PNG, rounded, square, and circle tiles, plus favicon, Apple touch icon, and social preview image.\"],\"wm3Zlr\":[\"All years\"],\"xCWek4\":[\"File storage isn't set up. Check your server config.\"],\"xVrkxi\":[\"quiet design\"],\"xVvw1i\":[\"This reset link is no longer valid. Request a new one to continue.\"],\"xYilR2\":[\"Media\"],\"xeiujy\":[\"Text\"],\"xhTx3y\":[\"Choose the standard logo for most placements and the reverse logo when you need more contrast.\"],\"xjHB3/\":[\"Continue →\"],\"y28hnO\":[\"Post\"],\"y2o/Y0\":[\"This Link Has Expired\"],\"yGZVl1\":[\"More\"],\"yQ2kGp\":[\"Load more\"],\"yUtAh2\":[\"New Thread\"],\"ycM1Xg\":[\"No results. Try different keywords.\"],\"ynMAhG\":[\"Default logo\"],\"yzF66j\":[\"Link\"],\"zBFr9G\":[\"Paste a long article, AI response, or any text...\\n\\nMarkdown formatting will be preserved.\"],\"zJDAbh\":[\"Don't save\"],\"zcDmsG\":[\"Featured posts\"],\"zoK+eO\":[\"Add a title before posting this link.\"],\"zucql+\":[\"Menu\"],\"zwBp5t\":[\"Private\"]}")as Messages;
|
|
@@ -33,6 +33,11 @@ msgstr ""
|
|
|
33
33
|
msgid "{count, plural, one {Found # result} other {Found # results}}"
|
|
34
34
|
msgstr ""
|
|
35
35
|
|
|
36
|
+
#. @context: Toast when several pasted remote images couldn't be rehosted; {count} is the number of images
|
|
37
|
+
#: src/ui/compose/ComposeDialog.tsx
|
|
38
|
+
msgid "{count} images couldn't be saved to your library — their original links were kept."
|
|
39
|
+
msgstr ""
|
|
40
|
+
|
|
36
41
|
#. @context: Placeholder for the custom collections link URL
|
|
37
42
|
#: src/ui/shared/collection-management-labels.ts
|
|
38
43
|
msgid "/archive?format=quote or https://example.com"
|
|
@@ -193,6 +198,11 @@ msgstr ""
|
|
|
193
198
|
msgid "An abstract editorial layout in warm paper colors"
|
|
194
199
|
msgstr ""
|
|
195
200
|
|
|
201
|
+
#. @context: Toast when a single pasted remote image couldn't be rehosted (e.g. blocked by the source's hotlink protection)
|
|
202
|
+
#: src/ui/compose/ComposeDialog.tsx
|
|
203
|
+
msgid "An image couldn't be saved to your library — its original link was kept."
|
|
204
|
+
msgstr ""
|
|
205
|
+
|
|
196
206
|
#. @context: Sample link post body suffix on the theme sample page
|
|
197
207
|
#: src/ui/pages/ThemeSamplePage.tsx
|
|
198
208
|
msgid "and checking whether the accent is guiding attention or pulling too hard."
|