@jant/core 0.6.13 → 0.6.14
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-HnzmsaqU.js → app-2i7-WQHg.js} +26 -22
- package/dist/app-DZDlmh7C.js +6 -0
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/{client-3_OaxhTs.js → client-BvID5ucz.js} +1 -1
- package/dist/client/_assets/client-Cevfpm8H.css +2 -0
- package/dist/client/_assets/{client-auth-TsYq90xm.js → client-auth-v3yvhbgZ.js} +175 -172
- package/dist/{export-Dfbq9aot.js → export-DWn149b7.js} +14 -6
- package/dist/{github-sync-BGTLx8Mf.js → github-sync-C0zyF6XS.js} +2 -2
- package/dist/{github-sync--PSoE-Ne.js → github-sync-Ckk0sJxK.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-command-palette.test.ts +137 -10
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +3 -0
- package/src/client/components/jant-command-palette.ts +100 -70
- package/src/client/components/jant-compose-dialog.ts +1 -1
- package/src/client/tiptap/__tests__/footnotes.test.ts +284 -0
- package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +148 -1
- package/src/client/tiptap/bubble-menu.ts +3 -3
- package/src/client/tiptap/footnotes.ts +156 -0
- package/src/client/tiptap/markdown-clipboard.ts +7 -2
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +4 -0
- package/src/lib/markdown-manager.ts +14 -5
- package/src/styles/ui.css +116 -28
- package/src/ui/feed/__tests__/timeline-cards.test.ts +8 -3
- package/src/ui/layouts/BaseLayout.tsx +1 -1
- package/src/ui/layouts/SiteLayout.tsx +19 -13
- package/src/ui/layouts/__tests__/SiteLayout.test.tsx +48 -0
- package/dist/app-CWJFPjuR.js +0 -6
- package/dist/client/_assets/client-DCrcHVJl.css +0 -2
|
@@ -2994,7 +2994,8 @@ function createMarkdownContentExtensions(options = {}) {
|
|
|
2994
2994
|
openOnClick: false,
|
|
2995
2995
|
autolink: false
|
|
2996
2996
|
},
|
|
2997
|
-
codeBlock: false
|
|
2997
|
+
codeBlock: false,
|
|
2998
|
+
trailingNode: { notAfter: ["footnoteDefinition"] }
|
|
2998
2999
|
}),
|
|
2999
3000
|
MarkdownCodeBlock,
|
|
3000
3001
|
Table.configure({
|
|
@@ -3013,9 +3014,16 @@ function createMarkdownContentExtensions(options = {}) {
|
|
|
3013
3014
|
MarkdownFootnoteDefinition
|
|
3014
3015
|
];
|
|
3015
3016
|
}
|
|
3016
|
-
|
|
3017
|
+
/**
|
|
3018
|
+
* Normalizes Markdown parser output before it enters the editor schema.
|
|
3019
|
+
*
|
|
3020
|
+
* @param node - Parsed Tiptap document or descendant node
|
|
3021
|
+
* @returns A normalized copy safe to load into an editor
|
|
3022
|
+
* @example
|
|
3023
|
+
* const normalized = normalizeMarkdownDocument(markdownManager.parse(source));
|
|
3024
|
+
*/ function normalizeMarkdownDocument(node) {
|
|
3017
3025
|
const normalized = { ...node };
|
|
3018
|
-
if (normalized.content) normalized.content = normalized.content.map(
|
|
3026
|
+
if (normalized.content) normalized.content = normalized.content.map(normalizeMarkdownDocument);
|
|
3019
3027
|
if (normalized.marks) normalized.marks = normalized.marks.map((mark) => {
|
|
3020
3028
|
if (!mark || typeof mark !== "object") return mark;
|
|
3021
3029
|
const nextMark = {
|
|
@@ -3040,8 +3048,8 @@ function normalizeMarkdownDoc(node) {
|
|
|
3040
3048
|
for (let index = 0; index < normalized.content.length; index += 1) {
|
|
3041
3049
|
const child = normalized.content[index];
|
|
3042
3050
|
const nextChild = normalized.content[index + 1];
|
|
3043
|
-
if (child?.type === "text" && typeof child.text === "string" && nextChild?.type === "footnoteReference" && child.text
|
|
3044
|
-
const trimmedText = child.text.replace(/\n
|
|
3051
|
+
if (child?.type === "text" && typeof child.text === "string" && nextChild?.type === "footnoteReference" && /\n[ \t]*$/.test(child.text)) {
|
|
3052
|
+
const trimmedText = child.text.replace(/\n[ \t]*$/, "");
|
|
3045
3053
|
if (trimmedText) nextContent.push({
|
|
3046
3054
|
...child,
|
|
3047
3055
|
text: trimmedText
|
|
@@ -3069,7 +3077,7 @@ function getMarkdownManager() {
|
|
|
3069
3077
|
return sharedMarkdownManager;
|
|
3070
3078
|
}
|
|
3071
3079
|
function parseMarkdownDocument(markdown) {
|
|
3072
|
-
return
|
|
3080
|
+
return normalizeMarkdownDocument(getMarkdownManager().parse(markdown));
|
|
3073
3081
|
}
|
|
3074
3082
|
function serializeMarkdownDocument(doc) {
|
|
3075
3083
|
return expandCodeBlockFences(getMarkdownManager().serialize(normalizeFootnoteArtifacts(doc)));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import "./url-BMYO-Zlt.js";
|
|
2
|
-
import "./export-
|
|
3
|
-
import { i as classifyRepoForSync, o as createGitHubSyncService } from "./github-sync
|
|
2
|
+
import "./export-DWn149b7.js";
|
|
3
|
+
import { i as classifyRepoForSync, o as createGitHubSyncService } from "./github-sync-Ckk0sJxK.js";
|
|
4
4
|
export { classifyRepoForSync, createGitHubSyncService };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as parseMarkdownDocument, r as parseFrontMatter, t as createExportService } from "./export-
|
|
1
|
+
import { c as parseMarkdownDocument, r as parseFrontMatter, t as createExportService } from "./export-DWn149b7.js";
|
|
2
2
|
import { r as getInstallationToken } from "./github-app-BbklkFmU.js";
|
|
3
3
|
import { r as parseRepoSlug, t as createGitHubClient } from "./github-api-BgSiE71w.js";
|
|
4
4
|
//#region src/lib/markdown-to-tiptap.ts
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { y as url_exports } from "./url-BMYO-Zlt.js";
|
|
2
|
-
import { A as MAX_MEDIA_ATTACHMENTS, C as toMediaView, D as toPostViews, E as toPostView, F as STATUSES, I as TEXT_ATTACHMENT_CONTENT_FORMATS, M as MEDIA_KINDS, N as NAV_ITEM_TYPES, O as toSearchResultView, P as SORT_ORDERS, S as toArchiveGroupsWithMedia, T as toNavItemViews, b as createMediaContext, j as MAX_PINNED_POSTS, k as FORMATS, m as defaultFeedRenderer, t as createApp, w as toNavItemView, x as toArchiveGroups } from "./app-
|
|
3
|
-
import { S as time_exports, a as markdown_exports } from "./export-
|
|
2
|
+
import { A as MAX_MEDIA_ATTACHMENTS, C as toMediaView, D as toPostViews, E as toPostView, F as STATUSES, I as TEXT_ATTACHMENT_CONTENT_FORMATS, M as MEDIA_KINDS, N as NAV_ITEM_TYPES, O as toSearchResultView, P as SORT_ORDERS, S as toArchiveGroupsWithMedia, T as toNavItemViews, b as createMediaContext, j as MAX_PINNED_POSTS, k as FORMATS, m as defaultFeedRenderer, t as createApp, w as toNavItemView, x as toArchiveGroups } from "./app-2i7-WQHg.js";
|
|
3
|
+
import { S as time_exports, a as markdown_exports } from "./export-DWn149b7.js";
|
|
4
4
|
import "./env-OHRKGcMj.js";
|
|
5
|
-
import "./github-sync
|
|
5
|
+
import "./github-sync-Ckk0sJxK.js";
|
|
6
6
|
export { FORMATS, MAX_MEDIA_ATTACHMENTS, MAX_PINNED_POSTS, MEDIA_KINDS, NAV_ITEM_TYPES, SORT_ORDERS, STATUSES, TEXT_ATTACHMENT_CONTENT_FORMATS, createApp, createMediaContext, defaultFeedRenderer, markdown_exports as markdown, time_exports as time, toArchiveGroups, toArchiveGroupsWithMedia, toMediaView, toNavItemView, toNavItemViews, toPostView, toPostViews, toSearchResultView, url_exports as url };
|
package/dist/node.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "./url-BMYO-Zlt.js";
|
|
2
|
-
import { B as isAssetPath, L as buildThemeStyle, R as BUILTIN_COLOR_THEMES, _ as sqliteSchemaBundle, a as resolveDatabaseDialect, c as getWebhookUrl, d as BUILTIN_FONT_THEMES, f as getCjkSerifCssVariables, g as pgSchemaBundle, h as createStorageDriver, i as createSiteService, l as setMyCommands, n as createNodeCliRuntime, o as getHostBasedStartupConfigurationIssues, p as getFontThemeCssVariables, r as createNodeRequestRuntime, s as resolveConfig, t as createApp, u as setWebhook, v as createNodeDatabase, y as schema_exports, z as getPublicAssetBasePath } from "./app-
|
|
3
|
-
import { t as createExportService } from "./export-
|
|
2
|
+
import { B as isAssetPath, L as buildThemeStyle, R as BUILTIN_COLOR_THEMES, _ as sqliteSchemaBundle, a as resolveDatabaseDialect, c as getWebhookUrl, d as BUILTIN_FONT_THEMES, f as getCjkSerifCssVariables, g as pgSchemaBundle, h as createStorageDriver, i as createSiteService, l as setMyCommands, n as createNodeCliRuntime, o as getHostBasedStartupConfigurationIssues, p as getFontThemeCssVariables, r as createNodeRequestRuntime, s as resolveConfig, t as createApp, u as setWebhook, v as createNodeDatabase, y as schema_exports, z as getPublicAssetBasePath } from "./app-2i7-WQHg.js";
|
|
3
|
+
import { t as createExportService } from "./export-DWn149b7.js";
|
|
4
4
|
import { C as shouldTrustProxy, S as getTelegramWebhookSecret, b as getSiteResolutionMode, d as getHostedControlPlaneBaseUrl, i as getConfiguredSingleSitePathPrefix, l as getEnvString, r as getConfiguredSingleSiteOrigin, x as getTelegramBotPool, y as getPort } from "./env-OHRKGcMj.js";
|
|
5
|
-
import "./github-sync
|
|
5
|
+
import "./github-sync-Ckk0sJxK.js";
|
|
6
6
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
7
7
|
import { serve } from "@hono/node-server";
|
|
8
8
|
import Database from "better-sqlite3";
|
|
@@ -529,7 +529,7 @@ async function createNodeRequestHandler(options) {
|
|
|
529
529
|
async function start(env = process.env, app) {
|
|
530
530
|
const handler = await createNodeRequestHandler({
|
|
531
531
|
env,
|
|
532
|
-
app: async () => app ?? (await import("./app-
|
|
532
|
+
app: async () => app ?? (await import("./app-DZDlmh7C.js")).createApp()
|
|
533
533
|
});
|
|
534
534
|
const hostname = resolveHost(env);
|
|
535
535
|
const port = resolvePort(env);
|
package/package.json
CHANGED
|
@@ -7,23 +7,29 @@ import {
|
|
|
7
7
|
} from "../jant-command-palette.js";
|
|
8
8
|
import type { JantCommandPalette } from "../jant-command-palette.js";
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
interface MockPaletteItem {
|
|
11
|
+
title: string;
|
|
12
|
+
path: string;
|
|
13
|
+
type: "post" | "collection" | "system";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function mockPaletteApi(items: MockPaletteItem[] = []) {
|
|
17
|
+
vi.spyOn(globalThis, "fetch").mockImplementation(
|
|
18
|
+
async () =>
|
|
19
|
+
new Response(JSON.stringify({ items }), {
|
|
20
|
+
headers: { "Content-Type": "application/json" },
|
|
21
|
+
}),
|
|
15
22
|
);
|
|
16
23
|
}
|
|
17
24
|
|
|
18
|
-
async function renderPalette(query: string) {
|
|
19
|
-
mockPaletteApi();
|
|
25
|
+
async function renderPalette(query: string, items: MockPaletteItem[] = []) {
|
|
26
|
+
mockPaletteApi(items);
|
|
20
27
|
const el = document.createElement(
|
|
21
28
|
"jant-command-palette",
|
|
22
29
|
) as JantCommandPalette;
|
|
23
|
-
el._open = true;
|
|
24
|
-
el._query = query;
|
|
25
30
|
document.body.appendChild(el);
|
|
26
|
-
await
|
|
31
|
+
await el.open();
|
|
32
|
+
el._query = query;
|
|
27
33
|
await el.updateComplete;
|
|
28
34
|
return el;
|
|
29
35
|
}
|
|
@@ -39,6 +45,7 @@ describe("JantCommandPalette slash path mode", () => {
|
|
|
39
45
|
vi.restoreAllMocks();
|
|
40
46
|
document.body.innerHTML = "";
|
|
41
47
|
globalThis.localStorage.clear();
|
|
48
|
+
globalThis.history.replaceState({}, "", "/");
|
|
42
49
|
});
|
|
43
50
|
|
|
44
51
|
it("shows go-to-path first and search second for slash queries", async () => {
|
|
@@ -61,3 +68,123 @@ describe("JantCommandPalette slash path mode", () => {
|
|
|
61
68
|
expect(resultTitles(el)).toEqual(["Go to /notes", 'Search for "notes"']);
|
|
62
69
|
});
|
|
63
70
|
});
|
|
71
|
+
|
|
72
|
+
describe("JantCommandPalette persistent search action", () => {
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
vi.restoreAllMocks();
|
|
75
|
+
document.body.innerHTML = "";
|
|
76
|
+
globalThis.localStorage.clear();
|
|
77
|
+
globalThis.history.replaceState({}, "", "/");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const matchingItems: MockPaletteItem[] = Array.from(
|
|
81
|
+
{ length: 12 },
|
|
82
|
+
(_, index) => ({
|
|
83
|
+
title: `Match ${index + 1}`,
|
|
84
|
+
path: `match-${index + 1}`,
|
|
85
|
+
type: "post",
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
it("keeps search visible below all navigation matches", async () => {
|
|
90
|
+
const el = await renderPalette("match", matchingItems);
|
|
91
|
+
|
|
92
|
+
expect(
|
|
93
|
+
el.querySelectorAll(".command-palette-results > .command-palette-result"),
|
|
94
|
+
).toHaveLength(12);
|
|
95
|
+
expect(resultTitles(el)).toEqual([
|
|
96
|
+
"Match 1",
|
|
97
|
+
"Match 2",
|
|
98
|
+
"Match 3",
|
|
99
|
+
"Match 4",
|
|
100
|
+
"Match 5",
|
|
101
|
+
"Match 6",
|
|
102
|
+
"Match 7",
|
|
103
|
+
"Match 8",
|
|
104
|
+
"Match 9",
|
|
105
|
+
"Match 10",
|
|
106
|
+
"Match 11",
|
|
107
|
+
"Match 12",
|
|
108
|
+
'Search all content for "match"',
|
|
109
|
+
]);
|
|
110
|
+
expect(
|
|
111
|
+
el
|
|
112
|
+
.querySelector(".command-palette-results-container")
|
|
113
|
+
?.lastElementChild?.classList.contains("command-palette-search-action"),
|
|
114
|
+
).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("uses plain Enter without a shortcut hint when search is the only result", async () => {
|
|
118
|
+
const el = await renderPalette("nothing", matchingItems);
|
|
119
|
+
const dialog = el.querySelector<HTMLDialogElement>(".command-palette");
|
|
120
|
+
|
|
121
|
+
expect(resultTitles(el)).toEqual(['Search all content for "nothing"']);
|
|
122
|
+
expect(el.querySelector(".command-palette-result-shortcut")).toBeNull();
|
|
123
|
+
|
|
124
|
+
dialog?.dispatchEvent(
|
|
125
|
+
new globalThis.KeyboardEvent("keydown", {
|
|
126
|
+
key: "Enter",
|
|
127
|
+
bubbles: true,
|
|
128
|
+
cancelable: true,
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(globalThis.location.pathname).toBe("/search");
|
|
133
|
+
expect(globalThis.location.search).toBe("?q=nothing");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("wraps ArrowUp from the best match to the search action", async () => {
|
|
137
|
+
const el = await renderPalette("match", matchingItems);
|
|
138
|
+
const dialog = el.querySelector<HTMLDialogElement>(".command-palette");
|
|
139
|
+
|
|
140
|
+
dialog?.dispatchEvent(
|
|
141
|
+
new globalThis.KeyboardEvent("keydown", {
|
|
142
|
+
key: "ArrowUp",
|
|
143
|
+
bubbles: true,
|
|
144
|
+
cancelable: true,
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
147
|
+
await el.updateComplete;
|
|
148
|
+
|
|
149
|
+
expect(
|
|
150
|
+
el.querySelector(
|
|
151
|
+
".command-palette-result-selected .command-palette-result-title",
|
|
152
|
+
)?.textContent,
|
|
153
|
+
).toBe('Search all content for "match"');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it.each(["metaKey", "ctrlKey"] as const)(
|
|
157
|
+
"runs full-text search with the %s Enter shortcut",
|
|
158
|
+
async (modifier) => {
|
|
159
|
+
const el = await renderPalette("match", matchingItems);
|
|
160
|
+
const dialog = el.querySelector<HTMLDialogElement>(".command-palette");
|
|
161
|
+
|
|
162
|
+
dialog?.dispatchEvent(
|
|
163
|
+
new globalThis.KeyboardEvent("keydown", {
|
|
164
|
+
key: "Enter",
|
|
165
|
+
[modifier]: true,
|
|
166
|
+
bubbles: true,
|
|
167
|
+
cancelable: true,
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
expect(globalThis.location.pathname).toBe("/search");
|
|
172
|
+
expect(globalThis.location.search).toBe("?q=match");
|
|
173
|
+
},
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
it("keeps plain Enter on the best navigation match", async () => {
|
|
177
|
+
const el = await renderPalette("match", matchingItems);
|
|
178
|
+
const dialog = el.querySelector<HTMLDialogElement>(".command-palette");
|
|
179
|
+
|
|
180
|
+
dialog?.dispatchEvent(
|
|
181
|
+
new globalThis.KeyboardEvent("keydown", {
|
|
182
|
+
key: "Enter",
|
|
183
|
+
bubbles: true,
|
|
184
|
+
cancelable: true,
|
|
185
|
+
}),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
expect(globalThis.location.pathname).toBe("/match-1");
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -3077,6 +3077,9 @@ describe("JantComposeDialog", () => {
|
|
|
3077
3077
|
expect(css).toMatch(
|
|
3078
3078
|
/\.compose-reply-compose-layout\s+\.compose-thread-post-header\s*\+\s*\.compose-body\s*\{[\s\S]*padding-top:\s*12px;/,
|
|
3079
3079
|
);
|
|
3080
|
+
expect(css).toMatch(
|
|
3081
|
+
/@media \(max-width:\s*760px\),\s*\(hover:\s*none\) and \(pointer:\s*coarse\)\s*\{[\s\S]*\.compose-thread-layout\s*\{[\s\S]*padding-top:\s*0\.75rem;[\s\S]*\.compose-thread-layout::before\s*\{[\s\S]*top:\s*0\.75rem;/,
|
|
3082
|
+
);
|
|
3080
3083
|
expect(css).toMatch(
|
|
3081
3084
|
/\.compose-dialog,[\s\S]*\.compose-page-shell\s*>\s*jant-compose-dialog\s*\{[\s\S]*--compose-quote-input-size:\s*var\(--type-content-subtitle\);[\s\S]*--compose-quote-input-leading:\s*1\.32;/,
|
|
3082
3085
|
);
|
|
@@ -30,6 +30,19 @@ interface CommandItem {
|
|
|
30
30
|
action: () => void;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
interface DisplayItem {
|
|
34
|
+
label: string;
|
|
35
|
+
secondary?: string;
|
|
36
|
+
icon: string;
|
|
37
|
+
/** For search-mode items: the query to execute */
|
|
38
|
+
searchQuery?: string;
|
|
39
|
+
/** For path-mode items: the local path to open */
|
|
40
|
+
pathTarget?: string;
|
|
41
|
+
/** Keep cross-mode actions visible below the scrollable result list. */
|
|
42
|
+
placement?: "results" | "footer";
|
|
43
|
+
shortcut?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
// ---------------------------------------------------------------------------
|
|
34
47
|
// SVG icon paths (reuse existing Lucide-style icons from the codebase)
|
|
35
48
|
// ---------------------------------------------------------------------------
|
|
@@ -280,15 +293,7 @@ export class JantCommandPalette extends LitElement {
|
|
|
280
293
|
return COMMANDS.filter((c) => !q || c.label.toLowerCase().includes(q));
|
|
281
294
|
}
|
|
282
295
|
|
|
283
|
-
get #displayItems():
|
|
284
|
-
label: string;
|
|
285
|
-
secondary?: string;
|
|
286
|
-
icon: string;
|
|
287
|
-
/** For search-mode items: the query to execute */
|
|
288
|
-
searchQuery?: string;
|
|
289
|
-
/** For path-mode items: the local path to open */
|
|
290
|
-
pathTarget?: string;
|
|
291
|
-
}> {
|
|
296
|
+
get #displayItems(): DisplayItem[] {
|
|
292
297
|
const mode = this.#mode;
|
|
293
298
|
|
|
294
299
|
if (mode === "command") {
|
|
@@ -320,13 +325,7 @@ export class JantCommandPalette extends LitElement {
|
|
|
320
325
|
if (mode === "path") {
|
|
321
326
|
const target = getPathCommandTarget(this._query);
|
|
322
327
|
const searchQuery = getPathCommandSearchQuery(this._query);
|
|
323
|
-
const items:
|
|
324
|
-
label: string;
|
|
325
|
-
secondary?: string;
|
|
326
|
-
icon: string;
|
|
327
|
-
searchQuery?: string;
|
|
328
|
-
pathTarget?: string;
|
|
329
|
-
}> = [
|
|
328
|
+
const items: DisplayItem[] = [
|
|
330
329
|
{
|
|
331
330
|
label: `Go to ${target}`,
|
|
332
331
|
icon: ICONS.path,
|
|
@@ -346,25 +345,25 @@ export class JantCommandPalette extends LitElement {
|
|
|
346
345
|
}
|
|
347
346
|
|
|
348
347
|
// Navigate mode — show all items when no query (autocomplete)
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
searchQuery?: string;
|
|
354
|
-
pathTarget?: string;
|
|
355
|
-
}> = this.#navigateItems.map((item) => ({
|
|
348
|
+
// Rank direct destinations and let the scrollable result list handle
|
|
349
|
+
// overflow without hiding valid matches.
|
|
350
|
+
const q = normalizeSearch(this._query);
|
|
351
|
+
const navItems: DisplayItem[] = this.#navigateItems.map((item) => ({
|
|
356
352
|
label: item.title,
|
|
357
|
-
secondary: item.
|
|
353
|
+
secondary: item.path,
|
|
358
354
|
icon: ICONS[item.type],
|
|
359
355
|
}));
|
|
360
356
|
|
|
361
|
-
//
|
|
362
|
-
|
|
363
|
-
|
|
357
|
+
// Full-text search is a persistent action, not another navigation result.
|
|
358
|
+
// Keep it outside the scrollable list while retaining it as the final
|
|
359
|
+
// keyboard option.
|
|
360
|
+
if (q) {
|
|
364
361
|
navItems.push({
|
|
365
|
-
label: `Search for "${this._query.trim()}"`,
|
|
362
|
+
label: `Search all content for "${this._query.trim()}"`,
|
|
366
363
|
icon: ICONS.search,
|
|
367
364
|
searchQuery: this._query.trim(),
|
|
365
|
+
placement: "footer",
|
|
366
|
+
...(navItems.length > 0 ? { shortcut: "⌘/Ctrl ↵" } : {}),
|
|
368
367
|
});
|
|
369
368
|
}
|
|
370
369
|
|
|
@@ -375,6 +374,12 @@ export class JantCommandPalette extends LitElement {
|
|
|
375
374
|
// Actions
|
|
376
375
|
// -----------------------------------------------------------------------
|
|
377
376
|
|
|
377
|
+
#executeSearch(query: string) {
|
|
378
|
+
saveHistory(SEARCH_HISTORY_KEY, query);
|
|
379
|
+
this.close();
|
|
380
|
+
window.location.href = `/search?q=${encodeURIComponent(query)}`;
|
|
381
|
+
}
|
|
382
|
+
|
|
378
383
|
#executeItem(index: number) {
|
|
379
384
|
const mode = this.#mode;
|
|
380
385
|
|
|
@@ -391,9 +396,7 @@ export class JantCommandPalette extends LitElement {
|
|
|
391
396
|
const displayItem = this.#displayItems[index];
|
|
392
397
|
const q = displayItem?.searchQuery;
|
|
393
398
|
if (q) {
|
|
394
|
-
|
|
395
|
-
this.close();
|
|
396
|
-
window.location.href = `/search?q=${encodeURIComponent(q)}`;
|
|
399
|
+
this.#executeSearch(q);
|
|
397
400
|
}
|
|
398
401
|
return;
|
|
399
402
|
}
|
|
@@ -408,10 +411,7 @@ export class JantCommandPalette extends LitElement {
|
|
|
408
411
|
}
|
|
409
412
|
|
|
410
413
|
if (displayItem?.searchQuery) {
|
|
411
|
-
|
|
412
|
-
saveHistory(SEARCH_HISTORY_KEY, q);
|
|
413
|
-
this.close();
|
|
414
|
-
window.location.href = `/search?q=${encodeURIComponent(q)}`;
|
|
414
|
+
this.#executeSearch(displayItem.searchQuery);
|
|
415
415
|
}
|
|
416
416
|
return;
|
|
417
417
|
}
|
|
@@ -419,10 +419,7 @@ export class JantCommandPalette extends LitElement {
|
|
|
419
419
|
// Navigate mode — check if the selected item is a search fallback
|
|
420
420
|
const displayItem = this.#displayItems[index];
|
|
421
421
|
if (displayItem?.searchQuery) {
|
|
422
|
-
|
|
423
|
-
saveHistory(SEARCH_HISTORY_KEY, q);
|
|
424
|
-
this.close();
|
|
425
|
-
window.location.href = `/search?q=${encodeURIComponent(q)}`;
|
|
422
|
+
this.#executeSearch(displayItem.searchQuery);
|
|
426
423
|
return;
|
|
427
424
|
}
|
|
428
425
|
|
|
@@ -475,6 +472,19 @@ export class JantCommandPalette extends LitElement {
|
|
|
475
472
|
return;
|
|
476
473
|
}
|
|
477
474
|
|
|
475
|
+
if (
|
|
476
|
+
event.key === "Enter" &&
|
|
477
|
+
(event.metaKey || event.ctrlKey) &&
|
|
478
|
+
this.#mode === "navigate"
|
|
479
|
+
) {
|
|
480
|
+
const query = this._query.trim();
|
|
481
|
+
if (query) {
|
|
482
|
+
event.preventDefault();
|
|
483
|
+
this.#executeSearch(query);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
478
488
|
if (event.key === "Enter") {
|
|
479
489
|
event.preventDefault();
|
|
480
490
|
this.#executeItem(this._selectedIndex);
|
|
@@ -521,10 +531,49 @@ export class JantCommandPalette extends LitElement {
|
|
|
521
531
|
// Render
|
|
522
532
|
// -----------------------------------------------------------------------
|
|
523
533
|
|
|
534
|
+
#renderItem(item: DisplayItem, index: number, footer = false) {
|
|
535
|
+
return html`
|
|
536
|
+
<div
|
|
537
|
+
id="command-palette-item-${index}"
|
|
538
|
+
class=${classMap({
|
|
539
|
+
"command-palette-result": true,
|
|
540
|
+
"command-palette-result-selected": index === this._selectedIndex,
|
|
541
|
+
"command-palette-search-action": footer,
|
|
542
|
+
})}
|
|
543
|
+
role="option"
|
|
544
|
+
aria-selected=${index === this._selectedIndex}
|
|
545
|
+
@click=${this.#handleItemClick(index)}
|
|
546
|
+
>
|
|
547
|
+
<span class="command-palette-result-icon">${unsafeSVG(item.icon)}</span>
|
|
548
|
+
<span class="command-palette-result-body">
|
|
549
|
+
<span class="command-palette-result-title">${item.label}</span>
|
|
550
|
+
${item.secondary
|
|
551
|
+
? html`<span class="command-palette-result-path"
|
|
552
|
+
>${item.secondary}</span
|
|
553
|
+
>`
|
|
554
|
+
: nothing}
|
|
555
|
+
</span>
|
|
556
|
+
${item.shortcut
|
|
557
|
+
? html`<kbd
|
|
558
|
+
class="command-palette-result-shortcut"
|
|
559
|
+
aria-label="Command or Control plus Enter"
|
|
560
|
+
>${item.shortcut}</kbd
|
|
561
|
+
>`
|
|
562
|
+
: nothing}
|
|
563
|
+
</div>
|
|
564
|
+
`;
|
|
565
|
+
}
|
|
566
|
+
|
|
524
567
|
render() {
|
|
525
568
|
if (!this._open) return nothing;
|
|
526
569
|
|
|
527
570
|
const items = this.#displayItems;
|
|
571
|
+
const resultItems = items
|
|
572
|
+
.map((item, index) => ({ item, index }))
|
|
573
|
+
.filter(({ item }) => item.placement !== "footer");
|
|
574
|
+
const footerItems = items
|
|
575
|
+
.map((item, index) => ({ item, index }))
|
|
576
|
+
.filter(({ item }) => item.placement === "footer");
|
|
528
577
|
|
|
529
578
|
return html`
|
|
530
579
|
<dialog
|
|
@@ -563,41 +612,22 @@ export class JantCommandPalette extends LitElement {
|
|
|
563
612
|
|
|
564
613
|
${items.length > 0
|
|
565
614
|
? html`
|
|
566
|
-
<
|
|
615
|
+
<div
|
|
567
616
|
id="command-palette-results"
|
|
568
|
-
class="command-palette-results"
|
|
617
|
+
class="command-palette-results-container"
|
|
569
618
|
role="listbox"
|
|
570
619
|
>
|
|
571
|
-
${
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
role="option"
|
|
581
|
-
aria-selected=${i === this._selectedIndex}
|
|
582
|
-
@click=${this.#handleItemClick(i)}
|
|
583
|
-
>
|
|
584
|
-
<span class="command-palette-result-icon"
|
|
585
|
-
>${unsafeSVG(item.icon)}</span
|
|
586
|
-
>
|
|
587
|
-
<span class="command-palette-result-body">
|
|
588
|
-
<span class="command-palette-result-title"
|
|
589
|
-
>${item.label}</span
|
|
590
|
-
>
|
|
591
|
-
${item.secondary
|
|
592
|
-
? html`<span class="command-palette-result-path"
|
|
593
|
-
>${item.secondary}</span
|
|
594
|
-
>`
|
|
595
|
-
: nothing}
|
|
596
|
-
</span>
|
|
597
|
-
</li>
|
|
598
|
-
`,
|
|
620
|
+
${resultItems.length > 0
|
|
621
|
+
? html`<div class="command-palette-results">
|
|
622
|
+
${resultItems.map(({ item, index }) =>
|
|
623
|
+
this.#renderItem(item, index),
|
|
624
|
+
)}
|
|
625
|
+
</div>`
|
|
626
|
+
: nothing}
|
|
627
|
+
${footerItems.map(({ item, index }) =>
|
|
628
|
+
this.#renderItem(item, index, true),
|
|
599
629
|
)}
|
|
600
|
-
</
|
|
630
|
+
</div>
|
|
601
631
|
`
|
|
602
632
|
: this._query.trim() && !this._loading
|
|
603
633
|
? html`<div class="command-palette-empty">No results</div>`
|
|
@@ -4033,7 +4033,7 @@ export class JantComposeDialog extends LitElement {
|
|
|
4033
4033
|
<span class="compose-alt-title">${this.labels.attachedText}</span>
|
|
4034
4034
|
<button
|
|
4035
4035
|
type="button"
|
|
4036
|
-
class="compose-
|
|
4036
|
+
class="compose-attached-done"
|
|
4037
4037
|
@click=${() => this._doneAttachedPanel()}
|
|
4038
4038
|
>
|
|
4039
4039
|
${this.labels.done}
|