@rigel-cms/rigel 0.0.0-stage → 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/LICENSE +203 -0
  2. package/README.md +8 -2
  3. package/package.json +78 -4
  4. package/src/article-body.ts +60 -0
  5. package/src/article-render.ts +87 -0
  6. package/src/config.ts +97 -0
  7. package/src/content.ts +5 -0
  8. package/src/editor/App.svelte +972 -0
  9. package/src/editor/assets/rigelcms-icon.png +0 -0
  10. package/src/editor/assets/sidebar/LICENSE +21 -0
  11. package/src/editor/assets/sidebar/book-open.svg +1 -0
  12. package/src/editor/assets/sidebar/calendar.svg +1 -0
  13. package/src/editor/assets/sidebar/chart-bar.svg +1 -0
  14. package/src/editor/assets/sidebar/file-text.svg +1 -0
  15. package/src/editor/assets/sidebar/files.svg +1 -0
  16. package/src/editor/assets/sidebar/folder.svg +1 -0
  17. package/src/editor/assets/sidebar/folders.svg +1 -0
  18. package/src/editor/assets/sidebar/gear.svg +1 -0
  19. package/src/editor/assets/sidebar/globe.svg +1 -0
  20. package/src/editor/assets/sidebar/heart.svg +1 -0
  21. package/src/editor/assets/sidebar/house.svg +1 -0
  22. package/src/editor/assets/sidebar/images.svg +1 -0
  23. package/src/editor/assets/sidebar/layout.svg +1 -0
  24. package/src/editor/assets/sidebar/list.svg +1 -0
  25. package/src/editor/assets/sidebar/magnifying-glass.svg +1 -0
  26. package/src/editor/assets/sidebar/megaphone.svg +1 -0
  27. package/src/editor/assets/sidebar/newspaper.svg +1 -0
  28. package/src/editor/assets/sidebar/pencil-simple.svg +1 -0
  29. package/src/editor/assets/sidebar/shopping-bag.svg +1 -0
  30. package/src/editor/assets/sidebar/sidebar-simple.svg +1 -0
  31. package/src/editor/assets/sidebar/square-half.svg +1 -0
  32. package/src/editor/assets/sidebar/squares-four.svg +1 -0
  33. package/src/editor/assets/sidebar/tag.svg +1 -0
  34. package/src/editor/assets/sidebar/user.svg +1 -0
  35. package/src/editor/assets/sidebar/users.svg +1 -0
  36. package/src/editor/blocks/bulleted-list.ts +2 -0
  37. package/src/editor/blocks/code.ts +1 -0
  38. package/src/editor/blocks/divider.ts +14 -0
  39. package/src/editor/blocks/h1.ts +2 -0
  40. package/src/editor/blocks/h2.ts +2 -0
  41. package/src/editor/blocks/h3.ts +2 -0
  42. package/src/editor/blocks/h4.ts +2 -0
  43. package/src/editor/blocks/h5.ts +2 -0
  44. package/src/editor/blocks/h6.ts +2 -0
  45. package/src/editor/blocks/image.ts +34 -0
  46. package/src/editor/blocks/index.ts +40 -0
  47. package/src/editor/blocks/numbered-list.ts +2 -0
  48. package/src/editor/blocks/paragraph.ts +1 -0
  49. package/src/editor/blocks/quote.ts +1 -0
  50. package/src/editor/blocks/table.ts +5 -0
  51. package/src/editor/lib/ActionDialog.svelte +51 -0
  52. package/src/editor/lib/BlockEditor.svelte +614 -0
  53. package/src/editor/lib/CollectionPanel.svelte +103 -0
  54. package/src/editor/lib/DatePicker.svelte +106 -0
  55. package/src/editor/lib/Field.svelte +80 -0
  56. package/src/editor/lib/Icon.svelte +34 -0
  57. package/src/editor/lib/ImagePicker.svelte +264 -0
  58. package/src/editor/lib/MarkdownEditor.svelte +206 -0
  59. package/src/editor/lib/MenuEditor.svelte +209 -0
  60. package/src/editor/lib/PropertiesTabs.svelte +15 -0
  61. package/src/editor/lib/ReferenceSelect.svelte +17 -0
  62. package/src/editor/lib/SchemaField.svelte +156 -0
  63. package/src/editor/lib/SchemaPanel.svelte +189 -0
  64. package/src/editor/lib/Select.svelte +138 -0
  65. package/src/editor/lib/SeoGuidance.svelte +21 -0
  66. package/src/editor/lib/Settings.svelte +183 -0
  67. package/src/editor/lib/Shortcuts.svelte +29 -0
  68. package/src/editor/lib/SidebarIcon.svelte +8 -0
  69. package/src/editor/lib/SitePreview.svelte +50 -0
  70. package/src/editor/lib/TopBar.svelte +165 -0
  71. package/src/editor/lib/UnsavedChanges.svelte +38 -0
  72. package/src/editor/lib/VisualEditor.svelte +494 -0
  73. package/src/editor/lib/calendar.ts +55 -0
  74. package/src/editor/lib/github-auth.ts +117 -0
  75. package/src/editor/lib/github-backend.ts +263 -0
  76. package/src/editor/lib/icons.ts +111 -0
  77. package/src/editor/lib/images.ts +195 -0
  78. package/src/editor/lib/markdown.ts +71 -0
  79. package/src/editor/lib/media-library.ts +39 -0
  80. package/src/editor/lib/menu-tree.ts +37 -0
  81. package/src/editor/lib/rich-markdown.ts +30 -0
  82. package/src/editor/lib/schema-edit.ts +110 -0
  83. package/src/editor/lib/settings.ts +103 -0
  84. package/src/editor/lib/slugify.ts +10 -0
  85. package/src/editor/lib/storage.ts +17 -0
  86. package/src/editor/lib/store.svelte.ts +33 -0
  87. package/src/editor/main.ts +9 -0
  88. package/src/editor/styles.css +1280 -0
  89. package/src/env.d.ts +22 -0
  90. package/src/index.ts +128 -0
  91. package/src/manifest/frontmatter.ts +56 -0
  92. package/src/manifest/loader.ts +14 -0
  93. package/src/manifest/menu.ts +46 -0
  94. package/src/manifest/navigation.ts +94 -0
  95. package/src/manifest/references.ts +39 -0
  96. package/src/manifest/seo.ts +15 -0
  97. package/src/manifest/types.ts +75 -0
  98. package/src/media-types.ts +21 -0
  99. package/src/preview-urls.ts +3 -0
  100. package/src/preview.ts +167 -0
  101. package/src/routes/local-api.ts +42 -0
  102. package/src/routes/local-documents.ts +83 -0
  103. package/src/routes/local-media.ts +124 -0
  104. package/src/routes/local-menu.ts +81 -0
  105. package/src/routes/local-post.ts +80 -0
  106. package/src/routes/manifest.json.ts +15 -0
  107. package/src/routes/media-files.ts +126 -0
  108. package/src/routes/media-validation.ts +37 -0
  109. package/src/routes/media.json.ts +12 -0
  110. package/src/routes/rigel-page.astro +25 -0
  111. package/src/routes/serialize.ts +139 -0
  112. package/src/schema/docs.ts +33 -0
  113. package/src/schema/paths.ts +136 -0
  114. package/src/schema/store.ts +141 -0
  115. package/src/schema/types.ts +81 -0
  116. package/src/schema/validate.ts +327 -0
  117. package/src/vite/manifest-plugin.ts +180 -0
@@ -0,0 +1,165 @@
1
+ <script lang="ts">
2
+ import Icon from "./Icon.svelte";
3
+ import SidebarIcon from "./SidebarIcon.svelte";
4
+ import { tick } from "svelte";
5
+ import type { Manifest } from "../../manifest/types";
6
+
7
+ type Sel =
8
+ | { kind: "media" }
9
+ | { kind: "menus" }
10
+ | { kind: "menu"; slug: string }
11
+ | { kind: "pages"; category?: string }
12
+ | { kind: "page"; slug: string }
13
+ | { kind: "documents"; type: string }
14
+ | { kind: "document"; type: string; id: string }
15
+ | { kind: "collection"; name: string }
16
+ | { kind: "post"; collection: string; slug: string };
17
+
18
+ let {
19
+ manifest,
20
+ sel,
21
+ onselect,
22
+ }: {
23
+ manifest: Manifest;
24
+ sel: Sel | null;
25
+ onselect: (s: Sel) => void;
26
+ } = $props();
27
+
28
+ let open = $state(false);
29
+ let query = $state("");
30
+ let active = $state(-1);
31
+ let inputEl = $state<HTMLInputElement | null>(null);
32
+
33
+ $effect(() => {
34
+ if (open) inputEl?.focus();
35
+ });
36
+
37
+ interface Hit {
38
+ label: string;
39
+ hint: string;
40
+ action: () => void;
41
+ }
42
+
43
+ const hits = $derived.by((): Hit[] => {
44
+ const q = query.trim().toLowerCase();
45
+ const all: Hit[] = [];
46
+ for (const p of manifest.pages) {
47
+ all.push({
48
+ label: p.label,
49
+ hint: "Page",
50
+ action: () => onselect({ kind: "page", slug: p.slug }),
51
+ });
52
+ }
53
+ for (const doc of manifest.documents ?? []) {
54
+ all.push({
55
+ label: String(doc.data.title ?? doc.data.name ?? doc.slug),
56
+ hint: `${doc.type} · ${doc.slug}`,
57
+ action: () => onselect({ kind: "document", type: doc.type, id: doc.id }),
58
+ });
59
+ }
60
+ for (const c of manifest.collections ?? []) {
61
+ all.push({
62
+ label: c.label,
63
+ hint: "Collection",
64
+ action: () => onselect({ kind: "collection", name: c.name }),
65
+ });
66
+ for (const po of c.posts) {
67
+ all.push({
68
+ label: String(po.frontmatter.title ?? po.slug),
69
+ hint: `${c.label} · ${po.slug}`,
70
+ action: () => onselect({ kind: "post", collection: c.name, slug: po.slug }),
71
+ });
72
+ }
73
+ }
74
+ if (manifest.menus) {
75
+ all.push({ label: "Menus", hint: "Collection", action: () => onselect({ kind: "menus" }) });
76
+ for (const m of manifest.menus.entries) all.push({ label: m.title, hint: `Menu · ${m.slug}`, action: () => onselect({ kind: "menu", slug: m.slug }) });
77
+ }
78
+ if (!q) return all.slice(0, 12);
79
+ return all.filter((h) => h.label.toLowerCase().includes(q) || h.hint.toLowerCase().includes(q)).slice(0, 12);
80
+ });
81
+
82
+ function trigger(h: Hit) {
83
+ h.action();
84
+ open = false;
85
+ query = "";
86
+ active = -1;
87
+ }
88
+
89
+ function onKeydown(e: KeyboardEvent) {
90
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
91
+ e.preventDefault();
92
+ open = !open;
93
+ query = "";
94
+ active = -1;
95
+ }
96
+ if (e.key === "Escape") open = false;
97
+ if (!open || e.isComposing) return;
98
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
99
+ e.preventDefault();
100
+ if (!hits.length) return;
101
+ active = e.key === "ArrowDown" ? (active + 1) % hits.length : (active <= 0 ? hits.length : active) - 1;
102
+ inputEl?.focus();
103
+ void tick().then(() => document.getElementById(`rigel-search-hit-${active}`)?.scrollIntoView({ block: "nearest" }));
104
+ }
105
+ if (e.key === "Enter" && e.target === inputEl) {
106
+ e.preventDefault();
107
+ const hit = hits[active < 0 ? 0 : active];
108
+ if (hit) trigger(hit);
109
+ }
110
+ }
111
+ </script>
112
+
113
+ <svelte:window onkeydown={onKeydown} />
114
+
115
+ <button class="search-trigger" aria-label="Search content" title="Search content (⌘K)" onclick={() => { active = -1; open = true; }}>
116
+ <SidebarIcon name="magnifying-glass" />
117
+ <span class="sidebar-label">Search…</span>
118
+ <kbd>⌘K</kbd>
119
+ </button>
120
+
121
+ {#if open}
122
+ <div
123
+ class="palette-backdrop"
124
+ role="presentation"
125
+ onclick={() => (open = false)}
126
+ onkeydown={(e) => e.key === "Escape" && (open = false)}
127
+ >
128
+ <div class="palette" role="dialog" aria-label="Search content" onclick={(e) => e.stopPropagation()}>
129
+ <div class="palette-search">
130
+ <Icon name="magnifier" size={20} />
131
+ <input
132
+ type="search"
133
+ role="combobox"
134
+ aria-expanded="true"
135
+ aria-controls="rigel-search-results"
136
+ aria-autocomplete="list"
137
+ aria-activedescendant={active >= 0 && hits[active] ? `rigel-search-hit-${active}` : undefined}
138
+ aria-label="Search pages, collections, posts"
139
+ placeholder="Search pages, collections, posts…"
140
+ bind:value={query}
141
+ oninput={() => active = -1}
142
+ bind:this={inputEl}
143
+ />
144
+ <kbd>esc</kbd>
145
+ </div>
146
+ <ul id="rigel-search-results" role="listbox" aria-label="Search results">
147
+ {#each hits as h, i (h.hint + h.label)}
148
+ <li role="presentation">
149
+ <button id={`rigel-search-hit-${i}`} role="option" aria-selected={active === i} onfocus={() => active = i} onclick={() => trigger(h)}>
150
+ <span>{h.label}</span>
151
+ <span class="hint">{h.hint}</span>
152
+ </button>
153
+ </li>
154
+ {:else}
155
+ <li class="empty" role="presentation">No results</li>
156
+ {/each}
157
+ </ul>
158
+ <div class="palette-footer">
159
+ <span><kbd>↑</kbd><kbd>↓</kbd> navigate</span>
160
+ <span><kbd>↵</kbd> open</span>
161
+ <span><kbd>esc</kbd> close</span>
162
+ </div>
163
+ </div>
164
+ </div>
165
+ {/if}
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import Icon from "./Icon.svelte";
3
+ let { busy, error, onsave, ondiscard, oncancel }: { busy: boolean; error: string; onsave: () => void; ondiscard: () => void; oncancel: () => void } = $props();
4
+ let dialog: HTMLDialogElement;
5
+ let cancel: HTMLButtonElement;
6
+ $effect(() => {
7
+ dialog.showModal();
8
+ cancel.focus();
9
+ return () => dialog.close();
10
+ });
11
+ </script>
12
+
13
+ <dialog bind:this={dialog} class="rigel rigel-modal-backdrop unsaved-dialog" aria-labelledby="unsaved-title" aria-describedby="unsaved-description" oncancel={(event) => { event.preventDefault(); if (!busy) oncancel(); }}>
14
+ <div class="rigel-modal">
15
+ <div class="unsaved-head">
16
+ <h2 id="unsaved-title">Unsaved changes</h2>
17
+ <button type="button" class="icon-btn" aria-label="Keep editing" disabled={busy} onclick={oncancel}><Icon name="x" size={16} /></button>
18
+ </div>
19
+ <p id="unsaved-description">You have unsaved changes. Save before leaving, or discard this draft?</p>
20
+ {#if error}<p role="alert">{error}</p>{/if}
21
+ <div class="unsaved-actions">
22
+ <button type="button" bind:this={cancel} disabled={busy} onclick={oncancel}>Keep editing</button>
23
+ <button type="button" disabled={busy} onclick={ondiscard}>Discard changes</button>
24
+ <button type="button" class="primary" disabled={busy} onclick={onsave}>{busy ? 'Saving…' : 'Save and leave'}</button>
25
+ </div>
26
+ </div>
27
+ </dialog>
28
+
29
+ <style>
30
+ .unsaved-dialog { box-sizing: border-box; width: 100%; height: 100%; max-width: none; max-height: none; margin: 0; padding: 1rem; border: none; color: var(--text); }
31
+ .unsaved-dialog .rigel-modal { box-sizing: border-box; }
32
+ .unsaved-dialog:not([open]) { display: none; }
33
+ .unsaved-dialog::backdrop { background: transparent; }
34
+ .unsaved-head, .unsaved-actions { display: flex; align-items: center; gap: 0.75rem; }
35
+ .unsaved-head { justify-content: space-between; }
36
+ .unsaved-actions { justify-content: flex-end; flex-wrap: wrap; margin-top: 1.5rem; }
37
+ .unsaved-actions button:not(.primary) { padding: 0.4375rem 0.75rem; border: 1px solid var(--border-control); border-radius: 0; background: var(--panel); color: var(--text); font: inherit; font-size: 0.8125rem; cursor: pointer; }
38
+ </style>