@rigel-cms/rigel 1.3.0 → 1.4.0-rc.2
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/docs/content-locales.md +18 -1
- package/package.json +1 -1
- package/src/config.ts +5 -0
- package/src/editor/App.svelte +74 -27
- package/src/editor/i18n/de/shell.json +9 -1
- package/src/editor/i18n/en/shell.json +9 -1
- package/src/editor/i18n/es/shell.json +9 -1
- package/src/editor/i18n/fr/shell.json +9 -1
- package/src/editor/i18n/it/shell.json +9 -1
- package/src/editor/i18n/ja/shell.json +9 -1
- package/src/editor/i18n/nl/shell.json +9 -1
- package/src/editor/i18n/ru/shell.json +9 -1
- package/src/editor/i18n/zh/shell.json +9 -1
- package/src/editor/lib/ActionDialog.svelte +23 -3
- package/src/editor/lib/MarkdownEditor.svelte +7 -5
- package/src/editor/lib/Settings.svelte +37 -30
- package/src/editor/lib/TopBar.svelte +13 -49
- package/src/editor/lib/VisualEditor.svelte +6 -4
- package/src/editor/lib/settings.ts +2 -0
- package/src/editor/styles.css +14 -0
- package/src/index.ts +1 -1
- package/src/manifest/navigation.ts +2 -2
- package/src/manifest/pages.ts +13 -0
- package/src/manifest/search.ts +41 -0
- package/src/manifest/types.ts +1 -0
- package/src/vite/manifest-plugin.ts +2 -1
package/docs/content-locales.md
CHANGED
|
@@ -4,6 +4,7 @@ Content languages are optional. Configure them separately from `languages`, whic
|
|
|
4
4
|
|
|
5
5
|
```js
|
|
6
6
|
rigel({
|
|
7
|
+
search: { scope: "all" },
|
|
7
8
|
contentLocales: {
|
|
8
9
|
defaultLocale: "en",
|
|
9
10
|
locales: [
|
|
@@ -44,10 +45,26 @@ defineDocument({
|
|
|
44
45
|
|
|
45
46
|
Each type can have different fields, allowed sections and initial content. You can also annotate an existing `defineCopy` page with `locale`. Rigel reads the annotation, not the folder name.
|
|
46
47
|
|
|
47
|
-
With multiple configured locales, the editor shows a Content language dropdown. It filters navigation, page lists
|
|
48
|
+
With multiple configured locales, the editor shows a Content language dropdown. It filters navigation, page lists and collections. Switching away from unsaved changes offers the existing Save, Discard and Keep editing choices. New sessions start with `defaultLocale`.
|
|
49
|
+
|
|
50
|
+
## Global search
|
|
51
|
+
|
|
52
|
+
Sidebar search (⌘/Ctrl + K) searches **all content languages** by default. Set `search: { scope: "currentLocale" }` in your Astro integration to default to the selected content language plus shared resources instead. Missing or invalid scope values fall back to `"all"`. This setting does not change the Pages table's language filter or its live search.
|
|
53
|
+
|
|
54
|
+
In **Settings → Content → Search content languages**, editors can choose **Website default**, **All languages**, or **Current content language**, alongside the article editing preferences. The override applies immediately and persists in `rigelcms.settings.userPref.searchScope` in this browser. Website default removes the override; Erase All Data resets it with the other browser preferences. It is independent of the editor interface language, which lives in **Settings → Interface** after Theme and Accessibility. Current content language is disabled when there are fewer than two configured content languages.
|
|
55
|
+
|
|
56
|
+
Results include legacy pages, native documents, collections, posts and menus. Shared resources appear once. Localized results show two-character language codes when multiple content languages exist, with the full configured label available on hover and to screen readers. Opening another language's result switches content language and opens that exact resource through the existing unsaved-changes guard; Keep editing leaves the language and draft untouched. Search still shows at most 12 results.
|
|
48
57
|
|
|
49
58
|
Content without a `locale` remains visible in every language. Use this for common authors, media and other shared records. A shared document type can also have a locale when its records are language-specific. The language filter is an editing convenience, not an access-control boundary: the manifest and publishing batch still contain the site's complete content.
|
|
50
59
|
|
|
51
60
|
Without `contentLocales`, the editor keeps its existing navigation and shows no content-language dropdown. A single configured locale also needs no dropdown.
|
|
52
61
|
|
|
53
62
|
Your Astro routes and templates still own website localization, including public language links, HTML `lang`, translated navigation and fallback behavior. Rigel does not translate text, pair translated entries or generate localized routes. Keep each type's URL mapping consistent with the website routes, and prepare its preview bindings as usual.
|
|
63
|
+
|
|
64
|
+
## Page categories and templates
|
|
65
|
+
|
|
66
|
+
Set `category` on a nonshared document definition to group its pages, for example `category: "Articles"`. This is schema metadata, not an editable field in stored documents. Uncategorised native documents and legacy copy pages remain in **Core** without a migration. Pages initially shows **All pages**. When categories exist, the table offers Core, All pages and each category visible in the selected content language. The live search matches title/name, slug and category substrings, ignores case and surrounding whitespace, and combines with the selected category. Its clear button restores input focus. Back preserves both the query and originating table/filter, not a filter inferred from the opened page. Content-language switches retain the query and reset unavailable categories to All pages.
|
|
67
|
+
|
|
68
|
+
Add new asks for a page name and a category from the locale-eligible, creatable document types. When a category contains multiple types, the editor also requires a Template choice using each definition's title. Shared, singleton and `creatable: false` types are not creation choices. A single uncategorised type keeps the name-only dialog. Creation uses the selected type's identity, initial values and section template; nothing is written until Save.
|
|
69
|
+
|
|
70
|
+
A visual variant can be another document definition with its own category, defaults and template—for example a Resources category with a dark-theme initial value. Its native type, folder, preview mapping and host renderer must be configured normally. A category does not change the editor theme, rewrite existing document types or generate website routes.
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -9,12 +9,15 @@ import { DEFAULT_MAX_FILE_SIZE } from "./media-types";
|
|
|
9
9
|
import { systemMessage } from "./editor/i18n/format";
|
|
10
10
|
import { localeNames, type Locale } from "./editor/i18n/locales";
|
|
11
11
|
import { validateContentLocales, validateContentLocale } from "./manifest/content-locales";
|
|
12
|
+
import { effectiveSearchScope } from "./manifest/search";
|
|
12
13
|
|
|
13
14
|
export interface ContentLocalesConfig {
|
|
14
15
|
defaultLocale: string;
|
|
15
16
|
locales: { code: string; label: string }[];
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
export type SearchScope = "all" | "currentLocale";
|
|
20
|
+
|
|
18
21
|
export interface RigelConfig {
|
|
19
22
|
copyDir: string;
|
|
20
23
|
mediaDir?: string;
|
|
@@ -25,6 +28,7 @@ export interface RigelConfig {
|
|
|
25
28
|
demo?: boolean;
|
|
26
29
|
languages?: Locale[];
|
|
27
30
|
contentLocales?: ContentLocalesConfig;
|
|
31
|
+
search?: { scope: SearchScope };
|
|
28
32
|
extensions?: string[];
|
|
29
33
|
collections?: CollectionConfig[];
|
|
30
34
|
menus?: { folder: string; maxDepth?: number };
|
|
@@ -56,6 +60,7 @@ const DEFAULTS: Pick<RigelConfig, "copyDir" | "backend"> = {
|
|
|
56
60
|
|
|
57
61
|
export function parseConfig(options: Partial<RigelConfig> = {}): RigelConfig {
|
|
58
62
|
const cfg = { ...DEFAULTS, ...options };
|
|
63
|
+
cfg.search = { scope: effectiveSearchScope(cfg.search, undefined) };
|
|
59
64
|
if (cfg.contentLocales !== undefined) {
|
|
60
65
|
validateContentLocales(cfg.contentLocales);
|
|
61
66
|
cfg.contentLocales = { ...cfg.contentLocales, locales: cfg.contentLocales.locales.map(locale => ({ ...locale })) };
|
package/src/editor/App.svelte
CHANGED
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
import SitePreview from "./lib/SitePreview.svelte";
|
|
13
13
|
import ImagePicker from "./lib/ImagePicker.svelte";
|
|
14
14
|
import Settings from "./lib/Settings.svelte";
|
|
15
|
+
import { effectiveSearchScope } from "../manifest/search";
|
|
15
16
|
import TopBar from "./lib/TopBar.svelte";
|
|
16
17
|
import Select from "./lib/Select.svelte";
|
|
17
18
|
import { contentForLocale, selectionInContent } from "../manifest/content-locales";
|
|
19
|
+
import { pageCategories, pageTemplates, inPageCategory } from "../manifest/pages";
|
|
18
20
|
import { validateDocumentSlugs } from "../slugs";
|
|
19
21
|
import Shortcuts from "./lib/Shortcuts.svelte";
|
|
20
22
|
import CollectionPanel from "./lib/CollectionPanel.svelte";
|
|
@@ -38,7 +40,7 @@
|
|
|
38
40
|
import VisualEditor from "./lib/VisualEditor.svelte";
|
|
39
41
|
import { newDocumentFromTemplate } from "../schema/paths";
|
|
40
42
|
import { previewUrl, previewPath } from "../preview";
|
|
41
|
-
import type { ContentDocument, Schema } from "../schema/types";
|
|
43
|
+
import type { ContentDocument, DocumentDef, Schema } from "../schema/types";
|
|
42
44
|
import { buildSidebar, navigationKey, type Selection } from "../manifest/navigation";
|
|
43
45
|
import { startGitHubLogin, checkGitHubAccess, type GitHubAccess } from "./lib/github-auth";
|
|
44
46
|
import { loadManifestRemote, saveRemoteSelection, deleteRemotePost, reviewRemotePublish, confirmRemotePublish, PublishReviewChangedError, type GitContext, type PublishReview } from "./lib/github-backend";
|
|
@@ -142,7 +144,7 @@
|
|
|
142
144
|
}
|
|
143
145
|
let pendingNavigation = $state<(() => void) | null>(null);
|
|
144
146
|
let pendingNavigationCancel: (() => void) | null = null;
|
|
145
|
-
let actionDialog = $state<{ title: string; description: string; confirmLabel: string; fieldLabel?: string; danger?: boolean; onconfirm: (value: string) => void | Promise<void> } | null>(null);
|
|
147
|
+
let actionDialog = $state<{ title: string; description: string; confirmLabel: string; fieldLabel?: string; danger?: boolean; templates?: DocumentDef[]; templateCategory?: string; onconfirm: (value: string, type?: string) => void | Promise<void> } | null>(null);
|
|
146
148
|
let newEntry = $state<{ collection: string; post: CollectionPost } | null>(null);
|
|
147
149
|
let newDocument = $state<{ type: string; id: string; slug: string } | null>(null);
|
|
148
150
|
let docBase = $state<Record<string, unknown> | null>(null);
|
|
@@ -159,6 +161,7 @@
|
|
|
159
161
|
if (!Array.isArray(data.pages) || (data.backend === "github" && !data.github)) throw new Error($t("shell.configurationIncomplete"));
|
|
160
162
|
blueprint = data;
|
|
161
163
|
manifest = data;
|
|
164
|
+
pagesFilter = null;
|
|
162
165
|
ready = true;
|
|
163
166
|
if (data.backend === "github" && data.github) {
|
|
164
167
|
const session = readSession(data.github);
|
|
@@ -478,11 +481,20 @@
|
|
|
478
481
|
});
|
|
479
482
|
|
|
480
483
|
const sidebar = $derived(buildSidebar(visibleContent, $t));
|
|
481
|
-
|
|
484
|
+
let pagesFilter = $state<string | null>(null);
|
|
485
|
+
let pagesQuery = $state("");
|
|
486
|
+
let pagesSearch: HTMLInputElement;
|
|
487
|
+
let entryList = $state<Sel | null>(null);
|
|
488
|
+
const categories = $derived(pageCategories(visibleContent));
|
|
489
|
+
const pageCategory = $derived(sel?.kind === "pages" && sel.category !== undefined ? sel.category : pagesFilter);
|
|
482
490
|
const schema = $derived<Schema | undefined>(manifest.schema);
|
|
483
491
|
const docTypes = $derived(visibleContent.schema?.documents ?? []);
|
|
484
492
|
|
|
485
493
|
const pageDocType = $derived(docTypes.find((type) => !type.shared && !type.singleton));
|
|
494
|
+
const templates = $derived(pageTemplates(visibleContent));
|
|
495
|
+
const pageBack = $derived<Extract<Sel, { kind: "pages" }>>(entryList?.kind === "pages" ? entryList : { kind: "pages" });
|
|
496
|
+
const documentBack = $derived<Sel | null>(entryList?.kind === "documents" && entryList.type === activeDocType?.name ? entryList : !activeDocType?.shared ? pageBack : activeDocType.singleton ? null : { kind: "documents", type: activeDocType.name });
|
|
497
|
+
const pageBackLabel = $derived((pageBack.category ?? pagesFilter) || (pagesFilter === "" && categories.length ? $t("shell.coreCategory") : $t("shell.pages")));
|
|
486
498
|
const pageDocuments = $derived((visibleContent.documents ?? []).filter((doc) => docTypes.some(type => type.name === doc.type && !type.shared)));
|
|
487
499
|
function docTypeName(s: Sel | null): string | null {
|
|
488
500
|
return s && (s.kind === "document" || s.kind === "documents") ? s.type : null;
|
|
@@ -509,14 +521,15 @@
|
|
|
509
521
|
|
|
510
522
|
const sortedPages = $derived.by(() => {
|
|
511
523
|
const rows = [
|
|
512
|
-
...visibleContent.pages.filter((p) => !p.shared && (
|
|
524
|
+
...visibleContent.pages.filter((p) => !p.shared && inPageCategory(p.category, pageCategory))
|
|
513
525
|
.map((p) => ({ slug: p.slug, path: previewPath(manifest.preview ?? {}, { kind: "page", slug: p.slug }), title: p.label, category: p.category, draft: false, target: { kind: "page", slug: p.slug } as Sel })),
|
|
514
526
|
...pageDocuments
|
|
515
|
-
.map((doc) => ({ slug: effectiveSlug(doc.data, doc.slug), path: previewPath(manifest.preview ?? {}, { kind: "document", type: doc.type, slug: effectiveSlug(doc.data, doc.slug) }), title: String(doc.data.title ?? doc.slug), category: docTypes.find(type => type.name === doc.type)?.category, draft: doc.data.draft === true, target: { kind: "document", type: doc.type, id: doc.id } as Sel }))
|
|
516
|
-
.filter(doc =>
|
|
527
|
+
.map((doc) => ({ slug: effectiveSlug(doc.data, doc.slug), path: previewPath(manifest.preview ?? {}, { kind: "document", type: doc.type, slug: effectiveSlug(doc.data, doc.slug) }), title: String(doc.data.title ?? doc.data.name ?? doc.slug), category: docTypes.find(type => type.name === doc.type)?.category, draft: doc.data.draft === true, target: { kind: "document", type: doc.type, id: doc.id } as Sel }))
|
|
528
|
+
.filter(doc => inPageCategory(doc.category, pageCategory)),
|
|
517
529
|
];
|
|
518
530
|
const key = sortBy === "slug" || sortBy === "category" || sortBy === "path" ? sortBy : "title";
|
|
519
|
-
|
|
531
|
+
const query = pagesQuery.trim().toLocaleLowerCase();
|
|
532
|
+
return rows.filter(row => !query || [row.title, row.slug, row.category ?? (categories.length ? $t("shell.coreCategory") : "")].some(value => value.toLocaleLowerCase().includes(query))).sort((a, b) => String(a[key] ?? "").localeCompare(String(b[key] ?? "")) * sortDir);
|
|
520
533
|
});
|
|
521
534
|
function documentCell(doc: ContentDocument, key: string): string {
|
|
522
535
|
return String(key === "title" ? doc.data.title ?? doc.data.name ?? doc.slug : key === "slug" ? activeDocType?.shared ? doc.slug : effectiveSlug(doc.data, doc.slug) : doc.data[key] ?? "");
|
|
@@ -526,13 +539,13 @@
|
|
|
526
539
|
|
|
527
540
|
const menu = $derived.by(() => { const slug = sel?.kind === "menu" ? sel.slug : null; return manifest.menus?.entries.find((m) => m.slug === slug); });
|
|
528
541
|
const sortedMenus = $derived([...(manifest.menus?.entries ?? [])].sort((a, b) => (sortBy === "slug" ? a.slug.localeCompare(b.slug) : a.title.localeCompare(b.title)) * sortDir));
|
|
529
|
-
const listTitle = $derived(sel?.kind === "menus" ? $t("shell.allMenus") : sel?.kind === "documents" ? activeDocType?.title ?? $t("shell.documents") : sel?.kind === "pages" ? pageCategory !==
|
|
542
|
+
const listTitle = $derived(sel?.kind === "menus" ? $t("shell.allMenus") : sel?.kind === "documents" ? activeDocType?.title ?? $t("shell.documents") : sel?.kind === "pages" ? pageCategory !== null && categories.length ? $t("shell.allNamedEntries", { name: pageCategory || $t("shell.coreCategory") }) : $t("shell.allPages") : coll?.listTitle?.trim() || (coll?.label !== undefined ? $t("shell.allNamedEntries", { name: coll.label }) : $t("shell.allEntries")));
|
|
530
543
|
const listRows = $derived(sel?.kind === "menus"
|
|
531
544
|
? sortedMenus.map((m) => ({ slug: m.slug, cells: { slug: m.slug, title: m.title } as Record<string, string>, draft: false, target: { kind: "menu", slug: m.slug } as Sel }))
|
|
532
545
|
: sel?.kind === "documents"
|
|
533
546
|
? typeDocuments.map((doc) => ({ slug: doc.slug, cells: Object.fromEntries(columns.map(column => [column.key, documentCell(doc, column.key)])), draft: doc.data.draft === true, target: { kind: "document", type: doc.type, id: doc.id } as Sel }))
|
|
534
547
|
: sel?.kind === "pages"
|
|
535
|
-
? sortedPages.map((p) => ({ slug: p.slug, cells: { label: p.title, slug: p.slug, path: p.path ?? "—", category: p.category ?? "—" } as Record<string, string>, draft: p.draft, target: p.target }))
|
|
548
|
+
? sortedPages.map((p) => ({ slug: p.slug, cells: { label: p.title, slug: p.slug, path: p.path ?? "—", category: p.category ?? (categories.length ? $t("shell.coreCategory") : "—") } as Record<string, string>, draft: p.draft, target: p.target }))
|
|
536
549
|
: sortedPosts.map((p) => ({ slug: p.slug, cells: Object.fromEntries(columns.map((c) => [c.key, cellValue(p, c.key)])), draft: Boolean(p.frontmatter.draft), target: { kind: "post", collection: coll!.name, slug: p.slug } as Sel })));
|
|
537
550
|
const titleColumn = $derived(sel?.kind === "pages" ? "label" : "title");
|
|
538
551
|
|
|
@@ -637,6 +650,11 @@
|
|
|
637
650
|
}
|
|
638
651
|
|
|
639
652
|
function openSelection(next: Sel | null) {
|
|
653
|
+
if (next?.kind === "page" || next?.kind === "document") {
|
|
654
|
+
if (sel?.kind === "pages" || sel?.kind === "documents") entryList = sel;
|
|
655
|
+
else if (sel?.kind !== "page" && sel?.kind !== "document") entryList = null;
|
|
656
|
+
}
|
|
657
|
+
if (next?.kind === "pages" && next.category !== undefined) pagesFilter = next.category;
|
|
640
658
|
sel = next;
|
|
641
659
|
saveError = "";
|
|
642
660
|
sortBy = "";
|
|
@@ -680,17 +698,25 @@
|
|
|
680
698
|
void tick().then(() => document.querySelector<HTMLButtonElement>(".content-language button[role=combobox]")?.focus({ preventScroll: true }));
|
|
681
699
|
}
|
|
682
700
|
|
|
683
|
-
function switchContentLocale(value: string | string[]) {
|
|
701
|
+
function switchContentLocale(value: string | string[], target?: Sel) {
|
|
684
702
|
localeControlRevision += 1;
|
|
685
703
|
if (typeof value !== "string" || value === contentLocale || !manifest.contentLocales?.locales.some(locale => locale.code === value)) return;
|
|
686
704
|
requestNavigation(() => {
|
|
687
705
|
const destination = contentForLocale(manifest, value);
|
|
688
706
|
selectedContentLocale = value;
|
|
707
|
+
if (pagesFilter && !pageCategories(destination).includes(pagesFilter)) pagesFilter = null;
|
|
708
|
+
if (!selectionInContent(destination, entryList)) entryList = null;
|
|
689
709
|
if (!selectionInContent(destination, sel)) openSelection(null);
|
|
710
|
+
if (target && selectionInContent(destination, target)) openSelection(target);
|
|
690
711
|
focusContentLocale();
|
|
691
712
|
}, focusContentLocale);
|
|
692
713
|
}
|
|
693
714
|
|
|
715
|
+
function selectSearch(next: Sel, locale?: string) {
|
|
716
|
+
if (locale && locale !== contentLocale && manifest.contentLocales?.locales.some(entry => entry.code === locale)) switchContentLocale(locale, next);
|
|
717
|
+
else select(next);
|
|
718
|
+
}
|
|
719
|
+
|
|
694
720
|
function sort(key: string) {
|
|
695
721
|
if (sortBy === key) sortDir = sortDir === 1 ? -1 : 1;
|
|
696
722
|
else {
|
|
@@ -726,21 +752,25 @@
|
|
|
726
752
|
return String(v);
|
|
727
753
|
}
|
|
728
754
|
|
|
729
|
-
function newPage(type = pageDocType) {
|
|
755
|
+
function newPage(type = pageDocType, chooseTemplate = false) {
|
|
756
|
+
if (chooseTemplate) type = templates.find(type => inPageCategory(type.category, pageCategory)) ?? templates[0];
|
|
730
757
|
if (!type || !schema || type.creatable === false || type.singleton) return;
|
|
731
758
|
actionDialog = {
|
|
732
|
-
title: $t("shell.addDocument", { name: type.title }), description: $t("shell.newDocumentDescription"), fieldLabel: type.shared ? $t("shell.name") : $t("shell.pageName"), confirmLabel: $t("shell.createDraft"),
|
|
733
|
-
|
|
759
|
+
title: $t("shell.addDocument", { name: chooseTemplate ? $t("shell.pages") : type.title }), description: $t("shell.newDocumentDescription"), fieldLabel: type.shared ? $t("shell.name") : $t("shell.pageName"), confirmLabel: $t("shell.createDraft"),
|
|
760
|
+
...(chooseTemplate ? { templates, templateCategory: type.category ?? "" } : {}),
|
|
761
|
+
onconfirm: (title, selectedType) => {
|
|
762
|
+
const selected = chooseTemplate ? templates.find(entry => entry.name === selectedType) : type;
|
|
763
|
+
if (!selected) throw new Error($t("shell.chooseTemplate"));
|
|
734
764
|
const slug = slugify(title);
|
|
735
765
|
if (!slug) throw new Error($t("shell.titleRequired"));
|
|
736
|
-
if (manifest.documents?.some(doc => doc.type ===
|
|
737
|
-
const id = `${
|
|
738
|
-
const data = newDocumentFromTemplate(schema,
|
|
739
|
-
validateDocumentSlugs(schema, [...(manifest.documents ?? []), { id, type:
|
|
740
|
-
newDocument = { type:
|
|
766
|
+
if (manifest.documents?.some(doc => doc.type === selected.name && doc.slug === slug)) throw new Error($t("shell.slugAlreadyUsed", { slug }));
|
|
767
|
+
const id = `${selected.name}:${slug}`;
|
|
768
|
+
const data = newDocumentFromTemplate(schema, selected.name, title, slug);
|
|
769
|
+
validateDocumentSlugs(schema, [...(manifest.documents ?? []), { id, type: selected.name, slug, data }], manifest.preview, $t);
|
|
770
|
+
newDocument = { type: selected.name, id, slug };
|
|
741
771
|
draft = JSON.parse(JSON.stringify(data));
|
|
742
772
|
docBase = null;
|
|
743
|
-
openSelection({ kind: "document", type:
|
|
773
|
+
openSelection({ kind: "document", type: selected.name, id });
|
|
744
774
|
},
|
|
745
775
|
};
|
|
746
776
|
}
|
|
@@ -857,7 +887,7 @@
|
|
|
857
887
|
<img src={logo} alt="" width="32" height="32" />
|
|
858
888
|
<span>Rigel <span class="brand-suffix">CMS</span></span>
|
|
859
889
|
</a>
|
|
860
|
-
<TopBar manifest
|
|
890
|
+
<TopBar {manifest} {sel} {contentLocale} scope={effectiveSearchScope(manifest.search, settings.userPref.searchScope)} onselect={selectSearch} />
|
|
861
891
|
{#if manifest.contentLocales && manifest.contentLocales.locales.length > 1}
|
|
862
892
|
<div class="content-language" data-locale={contentLocale}>
|
|
863
893
|
{#key localeControlRevision}
|
|
@@ -957,10 +987,9 @@
|
|
|
957
987
|
{#if sel.kind === "page" || sel.kind === "post" || sel.kind === "menu" || sel.kind === "document"}
|
|
958
988
|
<div class="editor-actions">
|
|
959
989
|
{#if sel.kind === "page" && page}
|
|
960
|
-
<p class="back"><button onclick={() => select(page.shared ? null :
|
|
990
|
+
<p class="back"><button onclick={() => select(page.shared ? null : pageBack)}>{$t("shell.backTo", { name: page.shared ? $t("shell.welcome") : pageBackLabel })}</button></p>
|
|
961
991
|
{:else if sel.kind === "document"}
|
|
962
|
-
{
|
|
963
|
-
<p class="back"><button onclick={() => select(backSel)}>{$t("shell.backTo", { name: activeDocType?.singleton ? $t("shell.welcome") : sel.type === pageDocType?.name ? $t("shell.pages") : activeDocType?.title ?? $t("shell.documents") })}</button></p>
|
|
992
|
+
<p class="back"><button onclick={() => select(documentBack)}>{$t("shell.backTo", { name: !documentBack ? $t("shell.welcome") : documentBack.kind === "pages" ? pageBackLabel : activeDocType?.title ?? $t("shell.documents") })}</button></p>
|
|
964
993
|
{:else if sel.kind === "post" && coll}
|
|
965
994
|
<p class="back"><button onclick={() => select({ kind: "collection", name: coll.name })}>{$t("shell.backTo", { name: coll.label })}</button></p>
|
|
966
995
|
{:else if sel.kind === "menu"}
|
|
@@ -976,8 +1005,25 @@
|
|
|
976
1005
|
{/if}
|
|
977
1006
|
<div class="titlebar">
|
|
978
1007
|
<h2>{sel.kind === "page" ? page?.label : sel.kind === "menu" ? String(draft.title || menu?.slug || "") : sel.kind === "post" ? String(draft.title || post?.slug || "") : sel.kind === "document" ? activeDocType?.singleton ? activeDocType.title : String(draft.title || draft.name || activeDocument?.slug || sel.id) : listTitle}</h2>
|
|
979
|
-
{#if
|
|
980
|
-
|
|
1008
|
+
{#if sel.kind === "pages"}
|
|
1009
|
+
{#if templates.length}
|
|
1010
|
+
<button class="primary" onclick={() => newPage(pageDocType, true)}>{$t("shell.addNew")}</button>
|
|
1011
|
+
{/if}
|
|
1012
|
+
<div class="pages-controls">
|
|
1013
|
+
{#if categories.length}
|
|
1014
|
+
<Select ariaLabel={$t("shell.category")} value={JSON.stringify(pageCategory)} options={[{ value: "null", label: $t("shell.allPages") }, { value: JSON.stringify(""), label: $t("shell.coreCategory") }, ...categories.map(category => ({ value: JSON.stringify(category), label: category }))]} onchange={(value) => { if (typeof value === "string") { pagesFilter = JSON.parse(value); select({ kind: "pages" }); } }} />
|
|
1015
|
+
{/if}
|
|
1016
|
+
<div class="pages-search">
|
|
1017
|
+
<Icon name="magnifier" size={18} />
|
|
1018
|
+
<input type="search" aria-label={$t("shell.searchPages")} placeholder={$t("shell.searchPages")} bind:value={pagesQuery} bind:this={pagesSearch} />
|
|
1019
|
+
{#if pagesQuery}
|
|
1020
|
+
<button type="button" class="icon-btn" aria-label={$t("fields.clear")} onclick={() => { pagesQuery = ""; pagesSearch.focus(); }}><Icon name="x" size={16} /></button>
|
|
1021
|
+
{/if}
|
|
1022
|
+
</div>
|
|
1023
|
+
</div>
|
|
1024
|
+
{/if}
|
|
1025
|
+
{#if (sel?.kind === "collection" && coll) || (sel?.kind === "documents" && activeDocType && activeDocType.creatable !== false && !activeDocType.singleton)}
|
|
1026
|
+
<button class="primary" onclick={() => (sel?.kind === "collection" ? newPost(coll!) : newPage(sel?.kind === "documents" ? activeDocType : pageDocType, sel?.kind === "pages"))}>{$t("shell.addNew")}</button>
|
|
981
1027
|
{/if}
|
|
982
1028
|
{#if sel?.kind === "post" && post}
|
|
983
1029
|
<button class="icon-btn" aria-label={$t("shell.deletePost")} data-tooltip={$t("shell.deletePost")} disabled={saveState === "saving"} onclick={deletePost}>
|
|
@@ -1003,6 +1049,7 @@
|
|
|
1003
1049
|
{:else if sel?.kind === "media"}
|
|
1004
1050
|
<ImagePicker inline />
|
|
1005
1051
|
{:else if sel?.kind === "pages" || sel?.kind === "documents" || sel?.kind === "menus" || (sel?.kind === "collection" && coll)}
|
|
1052
|
+
|
|
1006
1053
|
<div class="table-card">
|
|
1007
1054
|
<table>
|
|
1008
1055
|
<thead>
|
|
@@ -1039,7 +1086,7 @@
|
|
|
1039
1086
|
{/each}
|
|
1040
1087
|
</tr>
|
|
1041
1088
|
{:else}
|
|
1042
|
-
<tr><td colspan={columns.length}>{$t("shell.noEntries")}</td></tr>
|
|
1089
|
+
<tr><td colspan={columns.length}>{$t(sel?.kind === "pages" && pagesQuery.trim() ? "shell.noResults" : "shell.noEntries")}</td></tr>
|
|
1043
1090
|
{/each}
|
|
1044
1091
|
</tbody>
|
|
1045
1092
|
</table>
|
|
@@ -1144,7 +1191,7 @@
|
|
|
1144
1191
|
{/if}
|
|
1145
1192
|
|
|
1146
1193
|
{#if settingsOpen}
|
|
1147
|
-
<Settings bind:s={settings} github={githubMode ? manifest.github : undefined} user={githubAccess?.user} error={settingsError} saving={saveState === "saving"} onerase={eraseBrowserData} onclose={() => (settingsOpen = false)} />
|
|
1194
|
+
<Settings bind:s={settings} search={manifest.search} contentLocales={manifest.contentLocales} github={githubMode ? manifest.github : undefined} user={githubAccess?.user} error={settingsError} saving={saveState === "saving"} onerase={eraseBrowserData} onclose={() => (settingsOpen = false)} />
|
|
1148
1195
|
{/if}
|
|
1149
1196
|
{#if shortcutsOpen}
|
|
1150
1197
|
<Shortcuts onclose={() => (shortcutsOpen = false)} />
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
+
"shell.searchScope": "Inhaltssprachen durchsuchen",
|
|
3
|
+
"shell.searchScopeDefault": "Website-Standard ({scope})",
|
|
4
|
+
"shell.searchScopeAll": "Alle Sprachen",
|
|
5
|
+
"shell.searchScopeCurrent": "Aktuelle Inhaltssprache",
|
|
6
|
+
"shell.coreCategory": "Standard",
|
|
7
|
+
"shell.searchPages": "Seiten suchen",
|
|
8
|
+
"shell.template": "Vorlage",
|
|
9
|
+
"shell.chooseTemplate": "Vorlage auswählen",
|
|
2
10
|
"shell.search": "Suchen",
|
|
3
11
|
"shell.searchShortcut": "⌘/Ctrl + K",
|
|
4
12
|
"shell.closeDialog": "Dialog schließen",
|
|
@@ -21,7 +29,7 @@
|
|
|
21
29
|
"shell.searchNavigate": "navigieren",
|
|
22
30
|
"shell.searchOpen": "öffnen",
|
|
23
31
|
"shell.searchClose": "schließen",
|
|
24
|
-
"shell.
|
|
32
|
+
"shell.interface": "Benutzeroberfläche",
|
|
25
33
|
"shell.editor": "Editor",
|
|
26
34
|
"shell.connection": "Verbindung",
|
|
27
35
|
"shell.media": "Medien",
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
+
"shell.searchScope": "Search content languages",
|
|
3
|
+
"shell.searchScopeDefault": "Website default ({scope})",
|
|
4
|
+
"shell.searchScopeAll": "All languages",
|
|
5
|
+
"shell.searchScopeCurrent": "Current content language",
|
|
6
|
+
"shell.coreCategory": "Core",
|
|
7
|
+
"shell.searchPages": "Search pages",
|
|
8
|
+
"shell.template": "Template",
|
|
9
|
+
"shell.chooseTemplate": "Choose a template",
|
|
2
10
|
"shell.search": "Search",
|
|
3
11
|
"shell.searchShortcut": "⌘/Ctrl + K",
|
|
4
12
|
"shell.closeDialog": "Close dialog",
|
|
@@ -21,7 +29,7 @@
|
|
|
21
29
|
"shell.searchNavigate": "navigate",
|
|
22
30
|
"shell.searchOpen": "open",
|
|
23
31
|
"shell.searchClose": "close",
|
|
24
|
-
"shell.
|
|
32
|
+
"shell.interface": "Interface",
|
|
25
33
|
"shell.editor": "Editor",
|
|
26
34
|
"shell.connection": "Connection",
|
|
27
35
|
"shell.media": "Media",
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
+
"shell.searchScope": "Idiomas del contenido de búsqueda",
|
|
3
|
+
"shell.searchScopeDefault": "Predeterminado del sitio ({scope})",
|
|
4
|
+
"shell.searchScopeAll": "Todos los idiomas",
|
|
5
|
+
"shell.searchScopeCurrent": "Idioma actual del contenido",
|
|
6
|
+
"shell.coreCategory": "Principales",
|
|
7
|
+
"shell.searchPages": "Buscar páginas",
|
|
8
|
+
"shell.template": "Plantilla",
|
|
9
|
+
"shell.chooseTemplate": "Elige una plantilla",
|
|
2
10
|
"shell.search": "Buscar",
|
|
3
11
|
"shell.searchShortcut": "⌘/Ctrl + K",
|
|
4
12
|
"shell.closeDialog": "Cerrar diálogo",
|
|
@@ -21,7 +29,7 @@
|
|
|
21
29
|
"shell.searchNavigate": "navegar",
|
|
22
30
|
"shell.searchOpen": "abrir",
|
|
23
31
|
"shell.searchClose": "cerrar",
|
|
24
|
-
"shell.
|
|
32
|
+
"shell.interface": "Interfaz",
|
|
25
33
|
"shell.editor": "Editor",
|
|
26
34
|
"shell.connection": "Conexión",
|
|
27
35
|
"shell.media": "Archivos multimedia",
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
+
"shell.searchScope": "Langues du contenu recherché",
|
|
3
|
+
"shell.searchScopeDefault": "Par défaut du site ({scope})",
|
|
4
|
+
"shell.searchScopeAll": "Toutes les langues",
|
|
5
|
+
"shell.searchScopeCurrent": "Langue actuelle du contenu",
|
|
6
|
+
"shell.coreCategory": "Principales",
|
|
7
|
+
"shell.searchPages": "Rechercher des pages",
|
|
8
|
+
"shell.template": "Modèle",
|
|
9
|
+
"shell.chooseTemplate": "Choisir un modèle",
|
|
2
10
|
"shell.search": "Rechercher",
|
|
3
11
|
"shell.searchShortcut": "⌘/Ctrl + K",
|
|
4
12
|
"shell.closeDialog": "Fermer la boîte de dialogue",
|
|
@@ -21,7 +29,7 @@
|
|
|
21
29
|
"shell.searchNavigate": "parcourir",
|
|
22
30
|
"shell.searchOpen": "ouvrir",
|
|
23
31
|
"shell.searchClose": "fermer",
|
|
24
|
-
"shell.
|
|
32
|
+
"shell.interface": "Interface",
|
|
25
33
|
"shell.editor": "Éditeur",
|
|
26
34
|
"shell.connection": "Connexion",
|
|
27
35
|
"shell.media": "Médias",
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
+
"shell.searchScope": "Lingue dei contenuti da cercare",
|
|
3
|
+
"shell.searchScopeDefault": "Predefinito del sito ({scope})",
|
|
4
|
+
"shell.searchScopeAll": "Tutte le lingue",
|
|
5
|
+
"shell.searchScopeCurrent": "Lingua attuale del contenuto",
|
|
6
|
+
"shell.coreCategory": "Principali",
|
|
7
|
+
"shell.searchPages": "Cerca pagine",
|
|
8
|
+
"shell.template": "Modello",
|
|
9
|
+
"shell.chooseTemplate": "Scegli un modello",
|
|
2
10
|
"shell.search": "Cerca",
|
|
3
11
|
"shell.searchShortcut": "⌘/Ctrl + K",
|
|
4
12
|
"shell.closeDialog": "Chiudi finestra",
|
|
@@ -21,7 +29,7 @@
|
|
|
21
29
|
"shell.searchNavigate": "naviga",
|
|
22
30
|
"shell.searchOpen": "apri",
|
|
23
31
|
"shell.searchClose": "chiudi",
|
|
24
|
-
"shell.
|
|
32
|
+
"shell.interface": "Interfaccia",
|
|
25
33
|
"shell.editor": "Editor",
|
|
26
34
|
"shell.connection": "Connessione",
|
|
27
35
|
"shell.media": "Media",
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
+
"shell.searchScope": "検索するコンテンツの言語",
|
|
3
|
+
"shell.searchScopeDefault": "サイトの既定値({scope})",
|
|
4
|
+
"shell.searchScopeAll": "すべての言語",
|
|
5
|
+
"shell.searchScopeCurrent": "現在のコンテンツ言語",
|
|
6
|
+
"shell.coreCategory": "基本",
|
|
7
|
+
"shell.searchPages": "ページを検索",
|
|
8
|
+
"shell.template": "テンプレート",
|
|
9
|
+
"shell.chooseTemplate": "テンプレートを選択",
|
|
2
10
|
"shell.search": "検索",
|
|
3
11
|
"shell.searchShortcut": "⌘/Ctrl + K",
|
|
4
12
|
"shell.closeDialog": "ダイアログを閉じる",
|
|
@@ -21,7 +29,7 @@
|
|
|
21
29
|
"shell.searchNavigate": "移動",
|
|
22
30
|
"shell.searchOpen": "開く",
|
|
23
31
|
"shell.searchClose": "閉じる",
|
|
24
|
-
"shell.
|
|
32
|
+
"shell.interface": "インターフェース",
|
|
25
33
|
"shell.editor": "エディター",
|
|
26
34
|
"shell.connection": "接続",
|
|
27
35
|
"shell.media": "メディア",
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
+
"shell.searchScope": "Inhoudstalen doorzoeken",
|
|
3
|
+
"shell.searchScopeDefault": "Websitestandaard ({scope})",
|
|
4
|
+
"shell.searchScopeAll": "Alle talen",
|
|
5
|
+
"shell.searchScopeCurrent": "Huidige inhoudstaal",
|
|
6
|
+
"shell.coreCategory": "Standaard",
|
|
7
|
+
"shell.searchPages": "Pagina’s zoeken",
|
|
8
|
+
"shell.template": "Sjabloon",
|
|
9
|
+
"shell.chooseTemplate": "Kies een sjabloon",
|
|
2
10
|
"shell.search": "Zoeken",
|
|
3
11
|
"shell.searchShortcut": "⌘/Ctrl + K",
|
|
4
12
|
"shell.closeDialog": "Dialoogvenster sluiten",
|
|
@@ -21,7 +29,7 @@
|
|
|
21
29
|
"shell.searchNavigate": "navigeren",
|
|
22
30
|
"shell.searchOpen": "openen",
|
|
23
31
|
"shell.searchClose": "sluiten",
|
|
24
|
-
"shell.
|
|
32
|
+
"shell.interface": "Interface",
|
|
25
33
|
"shell.editor": "Editor",
|
|
26
34
|
"shell.connection": "Verbinding",
|
|
27
35
|
"shell.media": "Media",
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
+
"shell.searchScope": "Языки содержимого для поиска",
|
|
3
|
+
"shell.searchScopeDefault": "По умолчанию на сайте ({scope})",
|
|
4
|
+
"shell.searchScopeAll": "Все языки",
|
|
5
|
+
"shell.searchScopeCurrent": "Текущий язык содержимого",
|
|
6
|
+
"shell.coreCategory": "Основные",
|
|
7
|
+
"shell.searchPages": "Поиск страниц",
|
|
8
|
+
"shell.template": "Шаблон",
|
|
9
|
+
"shell.chooseTemplate": "Выберите шаблон",
|
|
2
10
|
"shell.search": "Поиск",
|
|
3
11
|
"shell.searchShortcut": "⌘/Ctrl + K",
|
|
4
12
|
"shell.closeDialog": "Закрыть диалог",
|
|
@@ -21,7 +29,7 @@
|
|
|
21
29
|
"shell.searchNavigate": "перемещение",
|
|
22
30
|
"shell.searchOpen": "открыть",
|
|
23
31
|
"shell.searchClose": "закрыть",
|
|
24
|
-
"shell.
|
|
32
|
+
"shell.interface": "Интерфейс",
|
|
25
33
|
"shell.editor": "Редактор",
|
|
26
34
|
"shell.connection": "Подключение",
|
|
27
35
|
"shell.media": "Медиафайлы",
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
+
"shell.searchScope": "搜索内容语言",
|
|
3
|
+
"shell.searchScopeDefault": "网站默认({scope})",
|
|
4
|
+
"shell.searchScopeAll": "所有语言",
|
|
5
|
+
"shell.searchScopeCurrent": "当前内容语言",
|
|
6
|
+
"shell.coreCategory": "基础",
|
|
7
|
+
"shell.searchPages": "搜索页面",
|
|
8
|
+
"shell.template": "模板",
|
|
9
|
+
"shell.chooseTemplate": "选择模板",
|
|
2
10
|
"shell.search": "搜索",
|
|
3
11
|
"shell.searchShortcut": "⌘/Ctrl + K",
|
|
4
12
|
"shell.closeDialog": "关闭对话框",
|
|
@@ -21,7 +29,7 @@
|
|
|
21
29
|
"shell.searchNavigate": "切换",
|
|
22
30
|
"shell.searchOpen": "打开",
|
|
23
31
|
"shell.searchClose": "关闭",
|
|
24
|
-
"shell.
|
|
32
|
+
"shell.interface": "界面",
|
|
25
33
|
"shell.editor": "编辑器",
|
|
26
34
|
"shell.connection": "连接",
|
|
27
35
|
"shell.media": "媒体",
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { t } from "../i18n";
|
|
3
3
|
import Icon from "./Icon.svelte";
|
|
4
|
-
|
|
4
|
+
import Select from "./Select.svelte";
|
|
5
|
+
import { untrack } from "svelte";
|
|
6
|
+
import type { DocumentDef } from "../../schema/types";
|
|
7
|
+
let { title, description, confirmLabel, fieldLabel, danger = false, templates, templateCategory, onconfirm, onclose }: {
|
|
5
8
|
title: string; description: string; confirmLabel: string; fieldLabel?: string; danger?: boolean;
|
|
6
|
-
|
|
9
|
+
templates?: DocumentDef[]; templateCategory?: string;
|
|
10
|
+
onconfirm: (value: string, type?: string) => void | Promise<void>; onclose: () => void;
|
|
7
11
|
} = $props();
|
|
8
12
|
let dialog: HTMLDialogElement;
|
|
9
13
|
let cancel: HTMLButtonElement;
|
|
@@ -11,6 +15,11 @@
|
|
|
11
15
|
let value = $state("");
|
|
12
16
|
let busy = $state(false);
|
|
13
17
|
let error = $state("");
|
|
18
|
+
let category = $state(untrack(() => templateCategory ?? ""));
|
|
19
|
+
let selectedTemplate = $state("");
|
|
20
|
+
const categories = $derived([...new Set((templates ?? []).map(type => type.category ?? ""))].sort());
|
|
21
|
+
const candidates = $derived((templates ?? []).filter(type => (type.category ?? "") === category));
|
|
22
|
+
const template = $derived(candidates.length === 1 ? candidates[0].name : selectedTemplate);
|
|
14
23
|
$effect(() => {
|
|
15
24
|
dialog.showModal();
|
|
16
25
|
(fieldLabel ? input : cancel).focus();
|
|
@@ -20,7 +29,10 @@
|
|
|
20
29
|
if (busy) return;
|
|
21
30
|
busy = true;
|
|
22
31
|
error = "";
|
|
23
|
-
try {
|
|
32
|
+
try {
|
|
33
|
+
if (templates && !candidates.some(type => type.name === template)) throw new Error($t("shell.chooseTemplate"));
|
|
34
|
+
await onconfirm(value.trim(), template); onclose();
|
|
35
|
+
}
|
|
24
36
|
catch (cause) { error = cause instanceof Error ? cause.message : $t("connection.actionFailed"); }
|
|
25
37
|
finally { busy = false; }
|
|
26
38
|
}
|
|
@@ -31,6 +43,14 @@
|
|
|
31
43
|
<div class="action-head"><h2 id="action-title">{title}</h2><button type="button" class="icon-btn" aria-label={$t("connection.cancel")} disabled={busy} onclick={onclose}><Icon name="x" size={16} /></button></div>
|
|
32
44
|
<p id="action-description">{description}</p>
|
|
33
45
|
{#if fieldLabel}<label><span>{fieldLabel}</span><input type="text" bind:this={input} bind:value required disabled={busy} /></label>{/if}
|
|
46
|
+
{#if templates}
|
|
47
|
+
{#if categories.some(category => category !== "")}
|
|
48
|
+
<Select label={$t("shell.category")} value={category} options={categories.map(value => ({ value, label: value || $t("shell.coreCategory") }))} disabled={busy} onchange={(value) => { if (typeof value === "string") { category = value; selectedTemplate = ""; error = ""; } }} />
|
|
49
|
+
{/if}
|
|
50
|
+
{#if candidates.length > 1}
|
|
51
|
+
<Select label={$t("shell.template")} value={selectedTemplate} options={[{ value: "", label: $t("shell.chooseTemplate"), disabled: true }, ...candidates.map(type => ({ value: type.name, label: type.title }))]} disabled={busy} onchange={(value) => { if (typeof value === "string") selectedTemplate = value; }} />
|
|
52
|
+
{/if}
|
|
53
|
+
{/if}
|
|
34
54
|
{#if error}<p class="error" role="alert">{error}</p>{/if}
|
|
35
55
|
<div class="action-actions">
|
|
36
56
|
<button type="button" bind:this={cancel} disabled={busy} onclick={onclose}>{$t("connection.cancel")}</button>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import type { SelectionBookmark } from "prosemirror-state";
|
|
15
15
|
import { parseVisualMarkdown, serializeMarkdown, safeMarkdownUrl, schema } from "./rich-markdown";
|
|
16
16
|
|
|
17
|
-
let { value, label, oninput }: { value: string; label: string; oninput: (value: string) => void } = $props();
|
|
17
|
+
let { value, label, oninput, editableRoot }: { value: string; label: string; oninput: (value: string) => void; editableRoot?: HTMLElement } = $props();
|
|
18
18
|
const id = $props.id();
|
|
19
19
|
let host: HTMLDivElement;
|
|
20
20
|
let mode = $state<"visual" | "source">("visual");
|
|
@@ -66,9 +66,10 @@
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
$effect(() => {
|
|
69
|
-
if (!host) return;
|
|
69
|
+
if (!editableRoot && !host) return;
|
|
70
70
|
if (!view) {
|
|
71
|
-
|
|
71
|
+
if (editableRoot) editableRoot.classList.add("rigel-markdown-content");
|
|
72
|
+
view = new EditorView(editableRoot ? { mount: editableRoot } : host, {
|
|
72
73
|
state: EditorState.create({ schema }),
|
|
73
74
|
attributes: { role: "textbox", "aria-label": label, "aria-multiline": "true" },
|
|
74
75
|
dispatchTransaction(transaction) {
|
|
@@ -92,6 +93,7 @@
|
|
|
92
93
|
load(value);
|
|
93
94
|
}
|
|
94
95
|
});
|
|
96
|
+
$effect(() => { const editable = mode === "visual"; view?.setProps({ editable: () => editable }); });
|
|
95
97
|
onDestroy(() => view?.destroy());
|
|
96
98
|
|
|
97
99
|
function activeMark(type: string) {
|
|
@@ -162,7 +164,7 @@
|
|
|
162
164
|
|
|
163
165
|
<svelte:window onpointerdown={event => { if (linkOpen && !document.getElementById(`${id}-link`)?.contains(event.target as Node) && !linkButton?.contains(event.target as Node)) linkOpen = false; }} />
|
|
164
166
|
|
|
165
|
-
<div class="markdown-editor body-editor">
|
|
167
|
+
<div class="markdown-editor body-editor" class:markdown-inline={!!editableRoot}>
|
|
166
168
|
<div class="markdown-heading">
|
|
167
169
|
<div class="field-label" id={`${id}-label`}>{label}</div>
|
|
168
170
|
<div class="markdown-modes" role="group" aria-label={$t("richtext.editingMode", { label })}>
|
|
@@ -199,7 +201,7 @@
|
|
|
199
201
|
{#if linkError}<p id={`${id}-error`} role="alert">{linkError}</p>{/if}
|
|
200
202
|
</div>
|
|
201
203
|
{/if}
|
|
202
|
-
<div class="md-preview rich-text" bind:this={host}></div>
|
|
204
|
+
{#if !editableRoot}<div class="md-preview rich-text" bind:this={host}></div>{/if}
|
|
203
205
|
</div>
|
|
204
206
|
{#if mode === "source"}
|
|
205
207
|
<textarea class="mono" rows="14" aria-label={$t("richtext.markdownSource", { label })} value={value} oninput={(event) => { warning = ""; emit(event.currentTarget.value); }}></textarea>
|
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
import type { Manifest } from "../../manifest/types";
|
|
6
6
|
import type { GitHubAccess } from "./github-auth";
|
|
7
7
|
import Icon from "./Icon.svelte";
|
|
8
|
+
import Select from "./Select.svelte";
|
|
9
|
+
import { effectiveSearchScope } from "../../manifest/search";
|
|
8
10
|
|
|
9
|
-
let { s = $bindable(), github, user, error, saving, onerase, onclose }: {
|
|
11
|
+
let { s = $bindable(), search, contentLocales, github, user, error, saving, onerase, onclose }: {
|
|
10
12
|
s: RigelSettings;
|
|
13
|
+
search?: Manifest["search"];
|
|
14
|
+
contentLocales?: Manifest["contentLocales"];
|
|
11
15
|
github?: Manifest["github"];
|
|
12
16
|
user?: GitHubAccess["user"];
|
|
13
17
|
error: string;
|
|
@@ -16,8 +20,8 @@
|
|
|
16
20
|
onclose: () => void;
|
|
17
21
|
} = $props();
|
|
18
22
|
|
|
19
|
-
type PaneId = "
|
|
20
|
-
let active: PaneId = $state("
|
|
23
|
+
type PaneId = "interface" | "content" | "connection" | "media" | "advanced";
|
|
24
|
+
let active: PaneId = $state("interface");
|
|
21
25
|
|
|
22
26
|
const providerIds = Object.keys(STOCK_PROVIDERS) as (keyof typeof STOCK_PROVIDERS)[];
|
|
23
27
|
let keys = $state(getKeys());
|
|
@@ -26,20 +30,20 @@
|
|
|
26
30
|
let eraseError = $state("");
|
|
27
31
|
|
|
28
32
|
const panes: { id: PaneId; label: string }[] = $derived([
|
|
29
|
-
{ id: "
|
|
30
|
-
{ id: "
|
|
33
|
+
{ id: "interface", label: $t("shell.interface") },
|
|
34
|
+
{ id: "content", label: $t("shell.content") },
|
|
31
35
|
{ id: "connection", label: $t("shell.connection") },
|
|
32
36
|
{ id: "media", label: $t("shell.media") },
|
|
33
|
-
|
|
37
|
+
|
|
34
38
|
{ id: "advanced", label: $t("shell.advanced") },
|
|
35
39
|
]);
|
|
36
40
|
|
|
37
41
|
const PANE_ICONS = {
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
interface: "palette",
|
|
43
|
+
content: "fileText",
|
|
40
44
|
connection: "user",
|
|
41
45
|
media: "image",
|
|
42
|
-
|
|
46
|
+
|
|
43
47
|
advanced: "wrench",
|
|
44
48
|
} as const;
|
|
45
49
|
|
|
@@ -71,6 +75,7 @@
|
|
|
71
75
|
{#each panes as p (p.id)}
|
|
72
76
|
<button
|
|
73
77
|
class:active={active === p.id}
|
|
78
|
+
aria-current={active === p.id ? "true" : undefined}
|
|
74
79
|
onclick={() => (active = p.id)}
|
|
75
80
|
>
|
|
76
81
|
<span class="pico"><Icon name={PANE_ICONS[p.id]} size={24} /></span>
|
|
@@ -79,9 +84,9 @@
|
|
|
79
84
|
{/each}
|
|
80
85
|
</nav>
|
|
81
86
|
|
|
82
|
-
<div class="settings-pane">
|
|
83
|
-
{#if active === "
|
|
84
|
-
|
|
87
|
+
<div class="settings-pane" role="region" aria-label={panes.find(p => p.id === active)?.label}>
|
|
88
|
+
{#if active === "interface"}
|
|
89
|
+
|
|
85
90
|
<fieldset>
|
|
86
91
|
<legend>{$t("shell.theme")}</legend>
|
|
87
92
|
<div class="theme-options">
|
|
@@ -101,17 +106,30 @@
|
|
|
101
106
|
</label>
|
|
102
107
|
<p class="hint" id="high-contrast-hint">{$t("shell.highContrastHint")}</p>
|
|
103
108
|
</fieldset>
|
|
104
|
-
|
|
105
|
-
{:else if active === "editor"}
|
|
106
|
-
<h3>{$t("shell.editorExperience")}</h3>
|
|
107
109
|
<fieldset>
|
|
108
|
-
<legend>{$t("shell.
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
<legend>{$t("shell.editorInterfaceLanguage")}</legend>
|
|
111
|
+
{#each availableLocales as lang}
|
|
112
|
+
<label class="radio">
|
|
113
|
+
<input type="radio" bind:group={s.language} value={lang} />
|
|
114
|
+
<span>{localeNames[lang]}</span>
|
|
115
|
+
</label>
|
|
116
|
+
{/each}
|
|
111
117
|
</fieldset>
|
|
118
|
+
|
|
119
|
+
{:else if active === "content"}
|
|
120
|
+
|
|
121
|
+
<Select label={$t("shell.articleBodyEditing")} value={s.userPref.editorExperience ?? "inline"} options={[
|
|
122
|
+
{ value: "sidebar", label: $t("shell.sidebar") },
|
|
123
|
+
{ value: "inline", label: $t("shell.inlinePreview") },
|
|
124
|
+
]} onchange={value => s.userPref.editorExperience = value === "sidebar" ? "sidebar" : "inline"} />
|
|
112
125
|
<p class="hint">{$t("shell.editorExperienceHint")}</p>
|
|
126
|
+
<Select label={$t("shell.searchScope")} value={s.userPref.searchScope ?? "inherit"} options={[
|
|
127
|
+
{ value: "inherit", label: $t("shell.searchScopeDefault", { scope: $t(effectiveSearchScope(search, undefined) === "all" ? "shell.searchScopeAll" : "shell.searchScopeCurrent") }) },
|
|
128
|
+
{ value: "all", label: $t("shell.searchScopeAll") },
|
|
129
|
+
{ value: "currentLocale", label: $t("shell.searchScopeCurrent"), disabled: !contentLocales || contentLocales.locales.length < 2 },
|
|
130
|
+
]} onchange={value => s.userPref.searchScope = value === "all" || value === "currentLocale" ? value : undefined} />
|
|
113
131
|
{:else if active === "connection"}
|
|
114
|
-
|
|
132
|
+
|
|
115
133
|
{#if github}
|
|
116
134
|
<dl class="connection-details">
|
|
117
135
|
<dt>{$t("shell.githubAccount")}</dt>
|
|
@@ -165,17 +183,6 @@
|
|
|
165
183
|
|
|
166
184
|
</section>
|
|
167
185
|
|
|
168
|
-
{:else if active === "language"}
|
|
169
|
-
<h3>{$t("shell.language")}</h3>
|
|
170
|
-
<fieldset>
|
|
171
|
-
<legend>{$t("shell.editorInterfaceLanguage")}</legend>
|
|
172
|
-
{#each availableLocales as lang}
|
|
173
|
-
<label class="radio">
|
|
174
|
-
<input type="radio" bind:group={s.language} value={lang} />
|
|
175
|
-
<span>{localeNames[lang]}</span>
|
|
176
|
-
</label>
|
|
177
|
-
{/each}
|
|
178
|
-
</fieldset>
|
|
179
186
|
|
|
180
187
|
{:else}
|
|
181
188
|
<h3>{$t("shell.developerMode")}</h3>
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import SidebarIcon from "./SidebarIcon.svelte";
|
|
5
5
|
import { tick } from "svelte";
|
|
6
6
|
import type { Manifest } from "../../manifest/types";
|
|
7
|
+
import { searchIndex, type SearchHit } from "../../manifest/search";
|
|
8
|
+
import type { SearchScope } from "../../config";
|
|
7
9
|
|
|
8
10
|
type Sel =
|
|
9
11
|
| { kind: "media" }
|
|
@@ -20,10 +22,14 @@
|
|
|
20
22
|
manifest,
|
|
21
23
|
sel,
|
|
22
24
|
onselect,
|
|
25
|
+
scope = "all",
|
|
26
|
+
contentLocale,
|
|
23
27
|
}: {
|
|
24
28
|
manifest: Manifest;
|
|
25
29
|
sel: Sel | null;
|
|
26
|
-
onselect: (s: Sel) => void;
|
|
30
|
+
onselect: (s: Sel, locale?: string) => void;
|
|
31
|
+
scope?: SearchScope;
|
|
32
|
+
contentLocale?: string;
|
|
27
33
|
} = $props();
|
|
28
34
|
|
|
29
35
|
let open = $state(false);
|
|
@@ -35,53 +41,11 @@
|
|
|
35
41
|
if (open) inputEl?.focus();
|
|
36
42
|
});
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
hint: string;
|
|
41
|
-
action: () => void;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const hits = $derived.by((): Hit[] => {
|
|
45
|
-
const q = query.trim().toLowerCase();
|
|
46
|
-
const all: Hit[] = [];
|
|
47
|
-
for (const p of manifest.pages) {
|
|
48
|
-
all.push({
|
|
49
|
-
label: p.label,
|
|
50
|
-
hint: $t("shell.page"),
|
|
51
|
-
action: () => onselect({ kind: "page", slug: p.slug }),
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
for (const doc of manifest.documents ?? []) {
|
|
55
|
-
all.push({
|
|
56
|
-
label: String(doc.data.title ?? doc.data.name ?? doc.slug),
|
|
57
|
-
hint: $t("shell.searchItemHint", { name: doc.type, slug: doc.slug }),
|
|
58
|
-
action: () => onselect({ kind: "document", type: doc.type, id: doc.id }),
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
for (const c of manifest.collections ?? []) {
|
|
62
|
-
all.push({
|
|
63
|
-
label: c.label,
|
|
64
|
-
hint: $t("shell.collection"),
|
|
65
|
-
action: () => onselect({ kind: "collection", name: c.name }),
|
|
66
|
-
});
|
|
67
|
-
for (const po of c.posts) {
|
|
68
|
-
all.push({
|
|
69
|
-
label: String(po.frontmatter.title ?? po.slug),
|
|
70
|
-
hint: $t("shell.searchItemHint", { name: c.label, slug: po.slug }),
|
|
71
|
-
action: () => onselect({ kind: "post", collection: c.name, slug: po.slug }),
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (manifest.menus) {
|
|
76
|
-
all.push({ label: $t("shell.menus"), hint: $t("shell.collection"), action: () => onselect({ kind: "menus" }) });
|
|
77
|
-
for (const m of manifest.menus.entries) all.push({ label: m.title, hint: $t("shell.searchMenuHint", { slug: m.slug }), action: () => onselect({ kind: "menu", slug: m.slug }) });
|
|
78
|
-
}
|
|
79
|
-
if (!q) return all.slice(0, 12);
|
|
80
|
-
return all.filter((h) => h.label.toLowerCase().includes(q) || h.hint.toLowerCase().includes(q)).slice(0, 12);
|
|
81
|
-
});
|
|
44
|
+
const index = $derived(searchIndex(manifest, scope, contentLocale, $t));
|
|
45
|
+
const hits = $derived(index.filter(hit => [hit.label, hit.hint, hit.language ?? ""].some(value => value.toLowerCase().includes(query.trim().toLowerCase()))).slice(0, 12));
|
|
82
46
|
|
|
83
|
-
function trigger(h:
|
|
84
|
-
h.
|
|
47
|
+
function trigger(h: SearchHit) {
|
|
48
|
+
onselect(h.selection, h.locale);
|
|
85
49
|
open = false;
|
|
86
50
|
query = "";
|
|
87
51
|
active = -1;
|
|
@@ -145,10 +109,10 @@
|
|
|
145
109
|
<kbd>{$t("shell.escapeKeyLowercase")}</kbd>
|
|
146
110
|
</div>
|
|
147
111
|
<ul id="rigel-search-results" role="listbox" aria-label={$t("shell.searchResults")}>
|
|
148
|
-
{#each hits as h, i (h.
|
|
112
|
+
{#each hits as h, i (h.id)}
|
|
149
113
|
<li role="presentation">
|
|
150
114
|
<button id={`rigel-search-hit-${i}`} role="option" aria-selected={active === i} onfocus={() => active = i} onclick={() => trigger(h)}>
|
|
151
|
-
<span>{h.label}</span>
|
|
115
|
+
<span>{h.label}{#if h.language}<span class="badge search-language" title={h.language} aria-label={h.language}>{h.locale?.slice(0, 2).toUpperCase()}</span>{/if}</span>
|
|
152
116
|
<span class="hint">{h.hint}</span>
|
|
153
117
|
</button>
|
|
154
118
|
</li>
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
let bodyScope: HTMLElement | null = null;
|
|
53
53
|
let removeTooltips: (() => void) | undefined;
|
|
54
54
|
let mode = $state<"browse" | "edit">("edit");
|
|
55
|
-
let panelOpen = $state(
|
|
55
|
+
let panelOpen = $state(false);
|
|
56
56
|
let editorWidth = $state(0);
|
|
57
57
|
let requestedWidth = $state(340);
|
|
58
58
|
let resizing = $state(false);
|
|
@@ -262,19 +262,21 @@
|
|
|
262
262
|
clearHover(); clearHighlight();
|
|
263
263
|
editingBody = element; bodyContents = [...element.childNodes];
|
|
264
264
|
const scope = doc.createElement("div");
|
|
265
|
+
bodyAttributes = ["class", "style", "role", "aria-label", "aria-multiline", "contenteditable", "tabindex", "translate"].map(name => [name, element.getAttribute(name)]);
|
|
265
266
|
const syncTheme = () => { scope.dataset.theme = document.documentElement.dataset.theme; scope.dataset.contrast = document.documentElement.dataset.contrast; };
|
|
266
267
|
syncTheme();
|
|
267
268
|
const host = doc.createElement("div"); host.className = "rigel rigel-inline";
|
|
268
|
-
scope.append(host);
|
|
269
|
+
scope.append(host); doc.body.append(scope); bodyScope = scope;
|
|
269
270
|
removeTooltips = tooltipRoot(host).destroy;
|
|
270
271
|
bodyEditor = mount(MarkdownEditor, { target: host, props: {
|
|
272
|
+
editableRoot: element,
|
|
271
273
|
label: def.labelKey ? $t(def.labelKey) : def.label,
|
|
272
274
|
get value() { return String(anchorValue(values, field.path).value ?? ""); },
|
|
273
275
|
oninput: (next: string) => onchange(replaceAt(values, field.path, next)),
|
|
274
276
|
} });
|
|
275
277
|
bodyThemeObserver = new MutationObserver(syncTheme);
|
|
276
278
|
bodyThemeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme", "data-contrast"] });
|
|
277
|
-
void tick().then(() => host.querySelector<
|
|
279
|
+
void tick().then(() => (element.isContentEditable ? element : host.querySelector<HTMLTextAreaElement>("textarea"))?.focus());
|
|
278
280
|
return true;
|
|
279
281
|
}
|
|
280
282
|
|
|
@@ -393,7 +395,7 @@
|
|
|
393
395
|
|
|
394
396
|
function setMode(next: "browse" | "edit") {
|
|
395
397
|
mode = next;
|
|
396
|
-
panelOpen =
|
|
398
|
+
panelOpen = false;
|
|
397
399
|
if (next === "browse") {
|
|
398
400
|
clearHighlight();
|
|
399
401
|
clearHover();
|
|
@@ -3,6 +3,7 @@ import type { StockKeys } from "./images";
|
|
|
3
3
|
import { availableLocales, setLocale, type Locale } from "../i18n";
|
|
4
4
|
|
|
5
5
|
export interface RigelUserPref {
|
|
6
|
+
searchScope?: "all" | "currentLocale";
|
|
6
7
|
sidebarCollapsed?: boolean;
|
|
7
8
|
editorExperience?: "sidebar" | "inline";
|
|
8
9
|
previewSplit: number;
|
|
@@ -46,6 +47,7 @@ export function getSettings(): RigelSettings {
|
|
|
46
47
|
highContrast: typeof saved?.highContrast === "boolean" ? saved.highContrast : DEFAULTS.highContrast,
|
|
47
48
|
apiKeys: mergedKeys,
|
|
48
49
|
userPref: {
|
|
50
|
+
searchScope: saved?.userPref?.searchScope === "all" || saved?.userPref?.searchScope === "currentLocale" ? saved.userPref.searchScope : undefined,
|
|
49
51
|
sidebarCollapsed: saved?.userPref?.sidebarCollapsed === true,
|
|
50
52
|
editorExperience: saved?.userPref?.editorExperience === "sidebar" ? "sidebar" : DEFAULTS.userPref.editorExperience,
|
|
51
53
|
previewSplit: typeof saved?.userPref?.previewSplit === "number" && Number.isFinite(saved.userPref.previewSplit) && saved.userPref.previewSplit >= 0.2 && saved.userPref.previewSplit <= 0.8
|
package/src/editor/styles.css
CHANGED
|
@@ -408,6 +408,15 @@
|
|
|
408
408
|
.rigel .titlebar { display: flex; align-items: center; flex-wrap: wrap; gap: 0.75rem 1.5rem; }
|
|
409
409
|
.rigel .titlebar h2 { min-width: 0; overflow-wrap: anywhere; }
|
|
410
410
|
.rigel .titlebar > button { flex-shrink: 0; }
|
|
411
|
+
.rigel .pages-controls button[role="combobox"], .rigel .pages-search input[type="search"] { padding-top: 0.375rem; padding-bottom: 0.375rem; font-size: 0.8125rem; line-height: normal; }
|
|
412
|
+
.rigel .pages-controls { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 0.75rem; margin-left: auto; min-width: 0; max-width: 100%; }
|
|
413
|
+
.rigel .pages-controls .custom-select { max-width: 100%; }
|
|
414
|
+
.rigel .pages-search { position: relative; width: 240px; max-width: 100%; color: var(--text-muted); }
|
|
415
|
+
.rigel .pages-search > svg { position: absolute; left: 0.75rem; top: 50%; transform: translateY(-50%); pointer-events: none; }
|
|
416
|
+
.rigel .pages-search input[type="search"] { padding-left: 2.25rem; padding-right: 2.75rem; }
|
|
417
|
+
.rigel .pages-search input::-webkit-search-cancel-button { appearance: none; }
|
|
418
|
+
.rigel .pages-search .icon-btn { position: absolute; right: 0; top: 0; height: 100%; width: 2.5rem; }
|
|
419
|
+
|
|
411
420
|
.rigel .editor-actions { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem; }
|
|
412
421
|
.rigel .editor-actions .back { display: flex; min-width: 0; margin: 0; }
|
|
413
422
|
.rigel .editor-actions .back button, .rigel .editor-actions .save-button { height: 2.25rem; padding: 0.5rem 0.75rem; border-width: 1px; border-style: solid; font-family: inherit; font-size: 0.8125rem; font-weight: 500; line-height: 1.25; }
|
|
@@ -567,6 +576,7 @@
|
|
|
567
576
|
.rigel .palette li button:hover,
|
|
568
577
|
.rigel .palette li button:focus-visible { background: var(--accent-soft); color: var(--on-accent-soft); }
|
|
569
578
|
.rigel .palette .hint { color: var(--text-muted); font-size: 0.75rem; font-weight: 400; }
|
|
579
|
+
.rigel .palette .search-language { margin-left: 0.5rem; }
|
|
570
580
|
.rigel .palette li button[aria-selected="true"] .hint,
|
|
571
581
|
.rigel .palette li button:hover .hint,
|
|
572
582
|
.rigel .palette li button:focus-visible .hint { color: var(--on-accent-soft); }
|
|
@@ -1183,6 +1193,9 @@
|
|
|
1183
1193
|
.rigel.rigel-inline .markdown-link, .rigel .block-editor .markdown-link { position: fixed; inset: auto; width: 340px; max-width: calc(100vw - 16px); z-index: 121; }
|
|
1184
1194
|
.rigel.rigel-inline .block-markdown-view { position: fixed; inset: 12px; max-height: calc(100vh - 24px); overflow: auto; padding: 0.5rem; border: 1px solid var(--border-control); background: var(--panel); }
|
|
1185
1195
|
.rigel-article-content:focus { outline: 0; }
|
|
1196
|
+
.rigel.rigel-inline .markdown-inline { position: fixed; top: 8px; left: 8px; width: max-content; max-width: calc(100vw - 16px); max-height: calc(100vh - 16px); overflow: auto; background: var(--panel); border: 1px solid var(--border-control); box-shadow: var(--shadow-lg); pointer-events: auto; }
|
|
1197
|
+
.rigel .markdown-editor [hidden] { display: none !important; }
|
|
1198
|
+
.rigel-markdown-content { white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
1186
1199
|
:is(.rigel .block-content, .rigel-article-content)::highlight(rigel-link-selection), :is(.rigel .block-content, .rigel-article-content) ::highlight(rigel-link-selection) { background-color: Highlight; color: HighlightText; }
|
|
1187
1200
|
.rigel-article-content:focus-visible > [data-rigel-active-block] { outline: 3px solid Highlight; outline-offset: 3px; }
|
|
1188
1201
|
.rigel-article-content .block-bold { font-weight: 700; }
|
|
@@ -1316,6 +1329,7 @@
|
|
|
1316
1329
|
.rigel .reorder-status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
1317
1330
|
.rigel .structure-list li.active { border-color: var(--accent); }
|
|
1318
1331
|
.rigel .structure-row { flex: 1; min-width: 0; display: flex; justify-content: space-between; gap: 0.5rem; align-items: center; border: 0; border-radius: 0; background: transparent; color: var(--text); padding: 0.5rem; text-align: left; cursor: pointer; font-size: 0.8125rem; }
|
|
1332
|
+
.rigel .structure-row > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1319
1333
|
.rigel .structure-actions, .rigel .schema-item-actions { display: flex; align-items: center; }
|
|
1320
1334
|
.rigel .structure-actions button, .rigel .schema-item-actions button { border: 0; border-left: 1px solid var(--border); border-radius: 0; background: transparent; color: var(--text-2); cursor: pointer; }
|
|
1321
1335
|
.rigel .structure-actions button:disabled, .rigel .schema-item-actions button:disabled { opacity: 0.35; cursor: default; }
|
package/src/index.ts
CHANGED
|
@@ -115,7 +115,7 @@ export { validateMenu } from "./manifest/menu";
|
|
|
115
115
|
export type { MenuItem, MenuDocument } from "./manifest/menu";
|
|
116
116
|
export { defineCopy } from "./manifest/types";
|
|
117
117
|
export type { CopyField, CopyGroup, CopyPage, Manifest, CollectionField } from "./manifest/types";
|
|
118
|
-
export type { RigelConfig, CollectionConfig, ContentLocalesConfig } from "./config";
|
|
118
|
+
export type { RigelConfig, CollectionConfig, ContentLocalesConfig, SearchScope } from "./config";
|
|
119
119
|
export type { JsonOutput } from "./json-output";
|
|
120
120
|
export type { ReferenceOption } from "./manifest/references";
|
|
121
121
|
export type { SidebarEntry, SidebarTarget, SidebarIconName } from "./manifest/navigation";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Manifest } from "./types";
|
|
2
|
+
import { pageCategories } from "./pages";
|
|
2
3
|
import { systemMessage, type Translator } from "../editor/i18n/format";
|
|
3
4
|
|
|
4
5
|
export type Selection =
|
|
@@ -31,8 +32,7 @@ export function navigationKey(target: Selection): string {
|
|
|
31
32
|
export function buildSidebar(manifest: Manifest, message: Translator = systemMessage): SidebarNode[] {
|
|
32
33
|
const link = (target: SidebarTarget, label: string): SidebarLink => ({ kind: "link", target, label });
|
|
33
34
|
const types = manifest.schema?.documents ?? [];
|
|
34
|
-
const
|
|
35
|
-
const categories = [...new Set([...pages, ...types.filter(type => !type.shared)].map(entry => entry.category).filter((category): category is string => !!category))].sort();
|
|
35
|
+
const categories = pageCategories(manifest);
|
|
36
36
|
const defaults: SidebarNode[] = [
|
|
37
37
|
{ kind: "heading", label: message("system.sidebar.website") },
|
|
38
38
|
link({ kind: "pages" }, message("system.sidebar.pages")),
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Manifest } from "./types";
|
|
2
|
+
|
|
3
|
+
export function pageCategories(manifest: Manifest): string[] {
|
|
4
|
+
return [...new Set([...manifest.pages, ...(manifest.schema?.documents ?? [])].filter(entry => !entry.shared).map(entry => entry.category).filter((category): category is string => !!category))].sort();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function pageTemplates(manifest: Manifest) {
|
|
8
|
+
return (manifest.schema?.documents ?? []).filter(type => !type.shared && !type.singleton && type.creatable !== false);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function inPageCategory(category: string | undefined, filter: string | null): boolean {
|
|
12
|
+
return filter === null || (category ?? "") === filter;
|
|
13
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { SearchScope } from "../config";
|
|
2
|
+
import type { Manifest } from "./types";
|
|
3
|
+
import type { Selection } from "./navigation";
|
|
4
|
+
import { contentForLocale } from "./content-locales";
|
|
5
|
+
|
|
6
|
+
export interface SearchHit {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
hint: string;
|
|
10
|
+
locale?: string;
|
|
11
|
+
language?: string;
|
|
12
|
+
selection: Selection;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function effectiveSearchScope(config: Manifest["search"], override: unknown): SearchScope {
|
|
16
|
+
return override === "all" || override === "currentLocale" ? override : config?.scope === "currentLocale" ? "currentLocale" : "all";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function searchIndex(manifest: Manifest, scope: SearchScope, locale: string | undefined, t: (key: string, params?: Record<string, string>) => string): SearchHit[] {
|
|
20
|
+
const content = scope === "currentLocale" ? contentForLocale(manifest, locale) : manifest;
|
|
21
|
+
const hits = new Map<string, SearchHit>();
|
|
22
|
+
function add(selection: Selection, label: string, hint: string, code?: string) {
|
|
23
|
+
const id = JSON.stringify(selection);
|
|
24
|
+
const language = manifest.contentLocales && manifest.contentLocales.locales.length > 1 ? manifest.contentLocales.locales.find(entry => entry.code === code)?.label : undefined;
|
|
25
|
+
if (!hits.has(id)) hits.set(id, { id, label, hint, selection, locale: code, language });
|
|
26
|
+
}
|
|
27
|
+
for (const page of content.pages) add({ kind: "page", slug: page.slug }, page.label, t("shell.page"), page.locale);
|
|
28
|
+
for (const doc of content.documents ?? []) {
|
|
29
|
+
const type = content.schema?.documents.find(type => type.name === doc.type);
|
|
30
|
+
add({ kind: "document", type: doc.type, id: doc.id }, String(doc.data.title ?? doc.data.name ?? doc.slug), t("shell.searchItemHint", { name: doc.type, slug: doc.slug }), type?.locale);
|
|
31
|
+
}
|
|
32
|
+
for (const collection of content.collections ?? []) {
|
|
33
|
+
add({ kind: "collection", name: collection.name }, collection.label, t("shell.collection"), collection.locale);
|
|
34
|
+
for (const post of collection.posts) add({ kind: "post", collection: collection.name, slug: post.slug }, String(post.frontmatter.title ?? post.slug), t("shell.searchItemHint", { name: collection.label, slug: post.slug }), collection.locale);
|
|
35
|
+
}
|
|
36
|
+
if (content.menus) {
|
|
37
|
+
add({ kind: "menus" }, t("shell.menus"), t("shell.collection"));
|
|
38
|
+
for (const menu of content.menus.entries) add({ kind: "menu", slug: menu.slug }, menu.title, t("shell.searchMenuHint", { slug: menu.slug }));
|
|
39
|
+
}
|
|
40
|
+
return [...hits.values()];
|
|
41
|
+
}
|
package/src/manifest/types.ts
CHANGED
|
@@ -38,6 +38,7 @@ export interface GitHubBackendConfig {
|
|
|
38
38
|
|
|
39
39
|
export interface Manifest {
|
|
40
40
|
contentLocales?: import("../config").ContentLocalesConfig;
|
|
41
|
+
search?: { scope: import("../config").SearchScope };
|
|
41
42
|
backend?: "github";
|
|
42
43
|
github?: GitHubBackendConfig;
|
|
43
44
|
sidebar?: import("./navigation").SidebarEntry[];
|
|
@@ -169,6 +169,7 @@ export const menus = {
|
|
|
169
169
|
demo: cfg.demo,
|
|
170
170
|
languages: cfg.languages,
|
|
171
171
|
contentLocales: cfg.contentLocales,
|
|
172
|
+
search: cfg.search,
|
|
172
173
|
publicDir: publicDir?.href,
|
|
173
174
|
mediaDir: cfg.mediaDir,
|
|
174
175
|
maxFileSize: cfg.maxFileSize,
|
|
@@ -218,7 +219,7 @@ ${collectionCode}
|
|
|
218
219
|
];
|
|
219
220
|
for (const collection of collections) __rigelAssertUniqueUrlSlugs(collection.posts.map(post => ({ slug: post.slug, values: post.frontmatter })));
|
|
220
221
|
${menuCode}
|
|
221
|
-
export default { pages, collections, documents${cfg.contentLocales ? ", contentLocales: " + JSON.stringify(cfg.contentLocales) : ""}${github ? ', backend: "github", github: ' + JSON.stringify(github) : ""}${cfg.menus ? ", menus" : ""}${cfg.schema ? ", schema: " + JSON.stringify(cfg.schema) : ""}${cfg.preview ? ", preview: " + JSON.stringify(cfg.preview) : ""}${cfg.sidebar ? ", sidebar: " + JSON.stringify(cfg.sidebar) : ""} };
|
|
222
|
+
export default { pages, collections, documents, search: ${JSON.stringify(cfg.search)}${cfg.contentLocales ? ", contentLocales: " + JSON.stringify(cfg.contentLocales) : ""}${github ? ', backend: "github", github: ' + JSON.stringify(github) : ""}${cfg.menus ? ", menus" : ""}${cfg.schema ? ", schema: " + JSON.stringify(cfg.schema) : ""}${cfg.preview ? ", preview: " + JSON.stringify(cfg.preview) : ""}${cfg.sidebar ? ", sidebar: " + JSON.stringify(cfg.sidebar) : ""} };
|
|
222
223
|
`;
|
|
223
224
|
},
|
|
224
225
|
};
|