@jant/core 0.6.8 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/commands/uploads/cleanup.js +1 -0
- package/dist/{app-9P4rVCe2.js → app-CGHkOdme.js} +3450 -3121
- package/dist/app-D24n0DoH.js +6 -0
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/{client-CXnEhyyv.js → client-DYrWuaIk.js} +1 -1
- package/dist/client/_assets/{client-auth-CSItbyU8.js → client-auth-B5Re0uCd.js} +187 -167
- package/dist/client/_assets/client-xWDl78yi.css +2 -0
- package/dist/{export-Be082J0n.js → export-DY1v5Iqu.js} +2 -2
- package/dist/{github-sync-D1Cw8mOY.js → github-sync-2_T7nbOv.js} +1 -1
- package/dist/{github-sync-_kPWM4m9.js → github-sync-LefaslGJ.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/node.js +4 -4
- package/package.json +1 -1
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +8 -2
- package/src/client/components/__tests__/jant-settings-general.test.ts +64 -12
- package/src/client/components/jant-compose-dialog.ts +12 -0
- package/src/client/components/jant-settings-general.ts +74 -21
- package/src/client/components/settings-types.ts +13 -0
- package/src/client/settings-bridge.ts +3 -0
- package/src/client/tiptap/__tests__/link-toolbar.test.ts +41 -0
- package/src/client/tiptap/__tests__/mark-exit.test.ts +99 -0
- package/src/client/tiptap/bubble-menu.ts +37 -4
- package/src/client/tiptap/link-toolbar.ts +63 -1
- package/src/db/migrations/0026_absent_rhodey.sql +14 -0
- package/src/db/migrations/meta/0026_snapshot.json +2511 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/migrations/pg/0024_high_violations.sql +14 -0
- package/src/db/migrations/pg/meta/0024_snapshot.json +3204 -0
- package/src/db/migrations/pg/meta/_journal.json +7 -0
- package/src/db/pg/schema.ts +36 -0
- package/src/db/schema.ts +36 -0
- package/src/i18n/__tests__/middleware.test.ts +46 -0
- package/src/i18n/locales/settings/en.po +282 -27
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +282 -27
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +282 -27
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +17 -8
- package/src/i18n/supported-locales.ts +5 -4
- package/src/lib/__tests__/feed.test.ts +5 -1
- package/src/lib/feed.ts +6 -3
- package/src/lib/ids.ts +1 -0
- package/src/lib/resolve-config.ts +1 -0
- package/src/lib/upload.ts +14 -0
- package/src/routes/api/__tests__/settings.test.ts +1 -4
- package/src/routes/api/__tests__/upload.test.ts +2 -0
- package/src/routes/api/internal/__tests__/uploads.test.ts +19 -1
- package/src/routes/api/settings.ts +2 -1
- package/src/routes/auth/__tests__/setup.test.ts +14 -0
- package/src/routes/dash/__tests__/settings-avatar.test.ts +35 -17
- package/src/routes/dash/settings.tsx +22 -4
- package/src/routes/feed/__tests__/feed.test.ts +58 -19
- package/src/routes/feed/feed.ts +37 -28
- package/src/routes/pages/featured.tsx +17 -0
- package/src/routes/pages/latest.tsx +25 -0
- package/src/services/__tests__/media.test.ts +191 -30
- package/src/services/__tests__/settings.test.ts +55 -0
- package/src/services/bootstrap.ts +7 -0
- package/src/services/export-theme/layouts/_default/baseof.html +2 -1
- package/src/services/media.ts +169 -42
- package/src/services/post.ts +1 -1
- package/src/services/settings.ts +49 -15
- package/src/services/upload-session.ts +13 -3
- package/src/styles/tokens.css +21 -4
- package/src/styles/ui.css +44 -1
- package/src/types/bindings.ts +1 -0
- package/src/types/config.ts +13 -0
- package/src/ui/__tests__/color-themes.test.ts +2 -2
- package/src/ui/color-themes.ts +32 -0
- package/src/ui/dash/appearance/ColorThemeContent.tsx +264 -29
- package/src/ui/dash/settings/GeneralContent.tsx +54 -4
- package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +3 -2
- package/src/ui/layouts/BaseLayout.tsx +3 -2
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +17 -4
- package/dist/app-DaxS_Cz-.js +0 -6
- package/dist/client/_assets/client-C6peCkkD.css +0 -2
|
@@ -35,6 +35,8 @@ export interface SettingsLabels {
|
|
|
35
35
|
mainFeedUrl: string;
|
|
36
36
|
latestFeedUrl: string;
|
|
37
37
|
featuredFeedUrl: string;
|
|
38
|
+
archiveFeedUrl: string;
|
|
39
|
+
archiveFeedUrlHelp: string;
|
|
38
40
|
latestFeedOption: string;
|
|
39
41
|
latestFeedOptionDescription: string;
|
|
40
42
|
featuredFeedOption: string;
|
|
@@ -47,6 +49,10 @@ export interface SettingsLabels {
|
|
|
47
49
|
siteLanguageSearchPlaceholder: string;
|
|
48
50
|
/** Empty-state message when the search filters out every option. */
|
|
49
51
|
siteLanguageNoMatches: string;
|
|
52
|
+
/** Lead text before the live `<html lang>` preview. */
|
|
53
|
+
contentLanguagePreview: string;
|
|
54
|
+
dashboardLanguage: string;
|
|
55
|
+
dashboardLanguageHelp: string;
|
|
50
56
|
cjkFont: string;
|
|
51
57
|
cjkFontHelp: string;
|
|
52
58
|
timeZone: string;
|
|
@@ -75,10 +81,17 @@ export interface SettingsCjkFont {
|
|
|
75
81
|
label: string;
|
|
76
82
|
}
|
|
77
83
|
|
|
84
|
+
/** Dashboard UI language option for the select dropdown */
|
|
85
|
+
export interface SettingsDashboardLanguage {
|
|
86
|
+
value: string;
|
|
87
|
+
label: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
78
90
|
export interface SettingsInitialData {
|
|
79
91
|
siteName: string;
|
|
80
92
|
siteDescription: string;
|
|
81
93
|
siteLanguage: string;
|
|
94
|
+
dashboardLanguage: string;
|
|
82
95
|
cjkSerifFont: string;
|
|
83
96
|
mainRssFeed: string;
|
|
84
97
|
timeZone: string;
|
|
@@ -20,6 +20,8 @@ function parseSettingsInitialData(data: unknown): SettingsInitialData | null {
|
|
|
20
20
|
const siteName = getJsonString(data, "siteName");
|
|
21
21
|
const siteDescription = getJsonString(data, "siteDescription");
|
|
22
22
|
const siteLanguage = getJsonString(data, "siteLanguage");
|
|
23
|
+
// Tolerate older payloads without the key: empty = follow content language.
|
|
24
|
+
const dashboardLanguage = getJsonString(data, "dashboardLanguage") ?? "";
|
|
23
25
|
const cjkSerifFont = getJsonString(data, "cjkSerifFont");
|
|
24
26
|
const mainRssFeed = getJsonString(data, "mainRssFeed");
|
|
25
27
|
const timeZone = getJsonString(data, "timeZone");
|
|
@@ -45,6 +47,7 @@ function parseSettingsInitialData(data: unknown): SettingsInitialData | null {
|
|
|
45
47
|
siteName,
|
|
46
48
|
siteDescription,
|
|
47
49
|
siteLanguage,
|
|
50
|
+
dashboardLanguage,
|
|
48
51
|
cjkSerifFont,
|
|
49
52
|
mainRssFeed,
|
|
50
53
|
timeZone,
|
|
@@ -168,6 +168,47 @@ describe("LinkToolbar", () => {
|
|
|
168
168
|
expect(linkMark).toBeUndefined();
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
+
it("opens the popover when a link is clicked (tap on mobile)", async () => {
|
|
172
|
+
const editor = createEditor();
|
|
173
|
+
|
|
174
|
+
// Create a link, then move the caret off it and dismiss the popover.
|
|
175
|
+
editor.commands.setTextSelection({ from: 1, to: 6 });
|
|
176
|
+
editor.view.dom.dispatchEvent(new CustomEvent("tiptap:open-link-input"));
|
|
177
|
+
const urlInput = requireElement(
|
|
178
|
+
document.querySelector<HTMLInputElement>(".tiptap-link-input-field"),
|
|
179
|
+
"expected url field",
|
|
180
|
+
);
|
|
181
|
+
urlInput.value = "https://example.com";
|
|
182
|
+
urlInput.dispatchEvent(
|
|
183
|
+
new globalThis.KeyboardEvent("keydown", { key: "Enter", bubbles: true }),
|
|
184
|
+
);
|
|
185
|
+
await Promise.resolve();
|
|
186
|
+
|
|
187
|
+
editor.commands.setTextSelection(10);
|
|
188
|
+
await Promise.resolve();
|
|
189
|
+
editor.commands.blur();
|
|
190
|
+
const popup = requireElement(
|
|
191
|
+
document.querySelector<HTMLElement>(".tiptap-link-input"),
|
|
192
|
+
"expected link popup",
|
|
193
|
+
);
|
|
194
|
+
popup.style.display = "none";
|
|
195
|
+
|
|
196
|
+
// Clicking the rendered link re-opens the popover without a prior caret
|
|
197
|
+
// move — this is the path that was broken on touch devices.
|
|
198
|
+
const anchor = requireElement(
|
|
199
|
+
editor.view.dom.querySelector<HTMLAnchorElement>("a"),
|
|
200
|
+
"expected rendered link",
|
|
201
|
+
);
|
|
202
|
+
anchor.dispatchEvent(
|
|
203
|
+
new globalThis.MouseEvent("click", { bubbles: true, button: 0 }),
|
|
204
|
+
);
|
|
205
|
+
await Promise.resolve();
|
|
206
|
+
|
|
207
|
+
expect(popup.style.display).toBe("flex");
|
|
208
|
+
expect(urlInput.value).toBe("https://example.com");
|
|
209
|
+
expect(editor.state.selection.empty).toBe(true);
|
|
210
|
+
});
|
|
211
|
+
|
|
171
212
|
it("replaces the link text when the text field is edited", async () => {
|
|
172
213
|
const editor = createEditor();
|
|
173
214
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
4
|
+
import { Editor } from "@tiptap/core";
|
|
5
|
+
import { createMarkdownContentExtensions } from "../../../lib/markdown-manager.js";
|
|
6
|
+
import { ExitableMarks } from "../exitable-marks.js";
|
|
7
|
+
import { toggleMarkAndExit } from "../bubble-menu.js";
|
|
8
|
+
|
|
9
|
+
const editors: Editor[] = [];
|
|
10
|
+
|
|
11
|
+
function createEditor(content: string): Editor {
|
|
12
|
+
const element = document.createElement("div");
|
|
13
|
+
document.body.appendChild(element);
|
|
14
|
+
const editor = new Editor({
|
|
15
|
+
element,
|
|
16
|
+
extensions: [...createMarkdownContentExtensions(), ExitableMarks],
|
|
17
|
+
content,
|
|
18
|
+
});
|
|
19
|
+
editor.view.dispatch(editor.state.tr);
|
|
20
|
+
editors.push(editor);
|
|
21
|
+
return editor;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Mimic real ProseMirror typed input: each char is offered to handleTextInput
|
|
25
|
+
// (used by mark input rules); if unhandled, insert it normally.
|
|
26
|
+
function type(editor: Editor, text: string): void {
|
|
27
|
+
const view = editor.view;
|
|
28
|
+
for (const ch of text) {
|
|
29
|
+
const { from, to } = view.state.selection;
|
|
30
|
+
const handled = view.someProp("handleTextInput", (f) =>
|
|
31
|
+
f(view, from, to, ch, () => view.state.tr.insertText(ch, from, to)),
|
|
32
|
+
);
|
|
33
|
+
if (!handled) view.dispatch(view.state.tr.insertText(ch, from, to));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Marks on the text node containing the last character of the doc. */
|
|
38
|
+
function marksOfLastText(editor: Editor): string[] {
|
|
39
|
+
const json = editor.getJSON();
|
|
40
|
+
const para = json.content?.[0];
|
|
41
|
+
const last = para?.content?.[para.content.length - 1];
|
|
42
|
+
return (last?.marks ?? []).map((m: { type: string }) => m.type);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
while (editors.length) editors.pop()?.destroy();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("toggleMarkAndExit", () => {
|
|
50
|
+
it("formats the selection, then continued typing is plain", () => {
|
|
51
|
+
const editor = createEditor("<p>hello</p>");
|
|
52
|
+
editor.chain().setTextSelection({ from: 1, to: 6 }).run();
|
|
53
|
+
|
|
54
|
+
toggleMarkAndExit(editor, "strike");
|
|
55
|
+
type(editor, "Z");
|
|
56
|
+
|
|
57
|
+
// "hello" struck, "Z" plain — cursor exited the inclusive mark.
|
|
58
|
+
expect(editor.getJSON().content?.[0]).toEqual({
|
|
59
|
+
type: "paragraph",
|
|
60
|
+
content: [
|
|
61
|
+
{ type: "text", marks: [{ type: "strike" }], text: "hello" },
|
|
62
|
+
{ type: "text", text: "Z" },
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("works for bold the same way", () => {
|
|
68
|
+
const editor = createEditor("<p>word</p>");
|
|
69
|
+
editor.chain().setTextSelection({ from: 1, to: 5 }).run();
|
|
70
|
+
|
|
71
|
+
toggleMarkAndExit(editor, "bold");
|
|
72
|
+
type(editor, "!");
|
|
73
|
+
|
|
74
|
+
expect(marksOfLastText(editor)).toEqual([]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("toggling a mark off leaves the cursor plain", () => {
|
|
78
|
+
const editor = createEditor("<p><strong>bold</strong></p>");
|
|
79
|
+
editor.chain().setTextSelection({ from: 1, to: 5 }).run();
|
|
80
|
+
|
|
81
|
+
toggleMarkAndExit(editor, "bold");
|
|
82
|
+
type(editor, "x");
|
|
83
|
+
|
|
84
|
+
// Mark removed from the selection and from the trailing cursor.
|
|
85
|
+
expect(editor.isActive("bold")).toBe(false);
|
|
86
|
+
expect(marksOfLastText(editor)).toEqual([]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("with an empty selection it acts as a plain mode toggle (stays on)", () => {
|
|
90
|
+
const editor = createEditor("<p></p>");
|
|
91
|
+
editor.chain().setTextSelection(1).run();
|
|
92
|
+
|
|
93
|
+
toggleMarkAndExit(editor, "bold");
|
|
94
|
+
type(editor, "ab");
|
|
95
|
+
|
|
96
|
+
// No selection → mode toggle: typed text carries the mark.
|
|
97
|
+
expect(marksOfLastText(editor)).toEqual(["bold"]);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -37,6 +37,39 @@ interface BubbleBtn {
|
|
|
37
37
|
isActive: (view: EditorView) => boolean;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Toggle an inline mark on the current selection, then drop out of it.
|
|
42
|
+
*
|
|
43
|
+
* The toolbar mark buttons (bold, italic) format the *selection* — the same
|
|
44
|
+
* intent as the `**x**` / `~~x~~` markdown shortcuts, which auto-exit once you
|
|
45
|
+
* type the closing delimiter. These marks are inclusive, so a collapsed cursor
|
|
46
|
+
* sitting at the end of the formatted word stays "inside" the mark and keeps
|
|
47
|
+
* extending as the user types, with no obvious way to stop (the bubble menu is
|
|
48
|
+
* hidden once the selection collapses). After toggling, we collapse the cursor
|
|
49
|
+
* to the end of the selection and remove the just-applied mark from the stored
|
|
50
|
+
* set so the next character is plain. Use the keyboard shortcuts (Mod-B /
|
|
51
|
+
* Mod-I) for mode-style "keep typing in this format".
|
|
52
|
+
*/
|
|
53
|
+
export function toggleMarkAndExit(editor: Editor, markName: string): void {
|
|
54
|
+
const { to, empty } = editor.state.selection;
|
|
55
|
+
if (empty) {
|
|
56
|
+
// No selection (e.g. shortcut-driven): behave as a plain mode toggle.
|
|
57
|
+
editor.chain().focus().toggleMark(markName).run();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const markType = editor.schema.marks[markName];
|
|
61
|
+
editor
|
|
62
|
+
.chain()
|
|
63
|
+
.focus()
|
|
64
|
+
.toggleMark(markName)
|
|
65
|
+
.setTextSelection(to)
|
|
66
|
+
.command(({ tr }) => {
|
|
67
|
+
if (markType) tr.removeStoredMark(markType);
|
|
68
|
+
return true;
|
|
69
|
+
})
|
|
70
|
+
.run();
|
|
71
|
+
}
|
|
72
|
+
|
|
40
73
|
function getButtons(
|
|
41
74
|
editor: Editor,
|
|
42
75
|
toolbarMode: FormattingToolbarMode,
|
|
@@ -47,14 +80,14 @@ function getButtons(
|
|
|
47
80
|
key: "bold",
|
|
48
81
|
icon: ICONS.bold,
|
|
49
82
|
title: "Bold",
|
|
50
|
-
action: () => editor
|
|
83
|
+
action: () => toggleMarkAndExit(editor, "bold"),
|
|
51
84
|
isActive: () => editor.isActive("bold"),
|
|
52
85
|
},
|
|
53
86
|
{
|
|
54
87
|
key: "italic",
|
|
55
88
|
icon: ICONS.italic,
|
|
56
89
|
title: "Italic",
|
|
57
|
-
action: () => editor
|
|
90
|
+
action: () => toggleMarkAndExit(editor, "italic"),
|
|
58
91
|
isActive: () => editor.isActive("italic"),
|
|
59
92
|
},
|
|
60
93
|
{
|
|
@@ -101,14 +134,14 @@ function getButtons(
|
|
|
101
134
|
key: "bold",
|
|
102
135
|
icon: ICONS.bold,
|
|
103
136
|
title: "Bold",
|
|
104
|
-
action: () => editor
|
|
137
|
+
action: () => toggleMarkAndExit(editor, "bold"),
|
|
105
138
|
isActive: () => editor.isActive("bold"),
|
|
106
139
|
},
|
|
107
140
|
{
|
|
108
141
|
key: "italic",
|
|
109
142
|
icon: ICONS.italic,
|
|
110
143
|
title: "Italic",
|
|
111
|
-
action: () => editor
|
|
144
|
+
action: () => toggleMarkAndExit(editor, "italic"),
|
|
112
145
|
isActive: () => editor.isActive("italic"),
|
|
113
146
|
},
|
|
114
147
|
{
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { Extension } from "@tiptap/core";
|
|
13
|
-
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
13
|
+
import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
14
14
|
import type { EditorState } from "@tiptap/pm/state";
|
|
15
15
|
import type { EditorView } from "@tiptap/pm/view";
|
|
16
16
|
import {
|
|
@@ -331,9 +331,71 @@ export const LinkToolbar = Extension.create({
|
|
|
331
331
|
suppressNextUpdate = true;
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Open the passive link popover when a link is clicked or tapped.
|
|
336
|
+
*
|
|
337
|
+
* The popover is otherwise shown only as a side effect of a collapsed
|
|
338
|
+
* caret landing inside a link (see the plugin `update` below). A mouse
|
|
339
|
+
* click reliably drops that caret, but a touch tap often does not — it may
|
|
340
|
+
* leave the selection unchanged or select a word — so on mobile the popover
|
|
341
|
+
* never appeared. Here we read the tapped position directly and force a
|
|
342
|
+
* collapsed caret inside the link, which works for both pointer types.
|
|
343
|
+
*/
|
|
344
|
+
function handleLinkClick(view: EditorView, event: MouseEvent) {
|
|
345
|
+
if (event.button !== 0) return;
|
|
346
|
+
const target = event.target as HTMLElement | null;
|
|
347
|
+
const anchor = target?.closest("a");
|
|
348
|
+
if (!anchor || !view.dom.contains(anchor)) return;
|
|
349
|
+
|
|
350
|
+
const linkType = view.state.schema.marks.link;
|
|
351
|
+
if (!linkType) return;
|
|
352
|
+
|
|
353
|
+
// Don't disturb an active range selection (e.g. drag-selecting link
|
|
354
|
+
// text) — that case is owned by the bubble menu.
|
|
355
|
+
if (!view.state.selection.empty) return;
|
|
356
|
+
|
|
357
|
+
// When the click already dropped a caret inside a link (the desktop
|
|
358
|
+
// path), keep it where the user clicked. On touch the tap often doesn't
|
|
359
|
+
// move the caret into the link, so derive a position from the anchor
|
|
360
|
+
// element itself — targeting this exact link regardless of where the
|
|
361
|
+
// user tapped.
|
|
362
|
+
const cur = view.state.selection.from;
|
|
363
|
+
const hasLink = (p: number) =>
|
|
364
|
+
view.state.doc
|
|
365
|
+
.resolve(p)
|
|
366
|
+
.marks()
|
|
367
|
+
.some((m) => m.type === linkType);
|
|
368
|
+
|
|
369
|
+
let pos = cur;
|
|
370
|
+
if (!hasLink(cur)) {
|
|
371
|
+
try {
|
|
372
|
+
pos = view.posAtDOM(anchor, 0) + 1;
|
|
373
|
+
} catch {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (pos < 0 || pos > view.state.doc.content.size) return;
|
|
377
|
+
if (!hasLink(pos)) return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Re-show even if the user previously dismissed it, then (re)place a
|
|
381
|
+
// collapsed caret in the link so the passive popover flow picks it up.
|
|
382
|
+
suppressAutoShow = false;
|
|
383
|
+
view.dispatch(
|
|
384
|
+
view.state.tr.setSelection(TextSelection.create(view.state.doc, pos)),
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
334
388
|
return [
|
|
335
389
|
new Plugin({
|
|
336
390
|
key: linkToolbarKey,
|
|
391
|
+
props: {
|
|
392
|
+
handleDOMEvents: {
|
|
393
|
+
click: (view, event) => {
|
|
394
|
+
handleLinkClick(view, event as MouseEvent);
|
|
395
|
+
return false;
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
},
|
|
337
399
|
view(editorView) {
|
|
338
400
|
createElements();
|
|
339
401
|
const dialog = editorView.dom.closest("dialog");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
CREATE TABLE `storage_purge` (
|
|
2
|
+
`id` text PRIMARY KEY NOT NULL,
|
|
3
|
+
`site_id` text NOT NULL,
|
|
4
|
+
`provider` text NOT NULL,
|
|
5
|
+
`storage_key` text NOT NULL,
|
|
6
|
+
`original_key` text NOT NULL,
|
|
7
|
+
`reason` text,
|
|
8
|
+
`purge_after` integer NOT NULL,
|
|
9
|
+
`created_at` integer NOT NULL,
|
|
10
|
+
FOREIGN KEY (`site_id`) REFERENCES `site`(`id`) ON UPDATE no action ON DELETE cascade
|
|
11
|
+
);
|
|
12
|
+
--> statement-breakpoint
|
|
13
|
+
CREATE UNIQUE INDEX `uq_storage_purge_provider_key` ON `storage_purge` (`provider`,`storage_key`);--> statement-breakpoint
|
|
14
|
+
CREATE INDEX `idx_storage_purge_site_provider_due` ON `storage_purge` (`site_id`,`provider`,`purge_after`);
|