@karibsen/eponyme 0.1.0 → 0.2.0
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/README.md +198 -16
- package/dist/eponyme.d.mts +22 -2
- package/dist/eponyme.mjs +5 -1
- package/dist/module.d.mts +3 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +73 -43
- package/dist/runtime/components/editor/EponymeCollectionPage.vue +81 -19
- package/dist/runtime/components/editor/EponymeForm.vue +3 -3
- package/dist/runtime/components/editor/EponymeFormPage.vue +7 -3
- package/dist/runtime/components/editor/EponymeSidebar.vue +25 -33
- package/dist/runtime/components/editor/EponymeSidebarTree.d.vue.ts +2 -0
- package/dist/runtime/components/editor/EponymeSidebarTree.vue +3 -1
- package/dist/runtime/components/editor/EponymeSidebarTree.vue.d.ts +2 -0
- package/dist/runtime/components/fields/RichTextField.vue +80 -8
- package/dist/runtime/components/ui/EPLinkDialog.d.vue.ts +15 -0
- package/dist/runtime/components/ui/EPLinkDialog.vue +142 -0
- package/dist/runtime/components/ui/EPLinkDialog.vue.d.ts +15 -0
- package/dist/runtime/components/ui/EPLinkEditor.vue +10 -91
- package/dist/runtime/composables/useEponyme.d.ts +2 -0
- package/dist/runtime/composables/useEponyme.js +7 -5
- package/dist/runtime/composables/useEponymeCollection.d.ts +12 -2
- package/dist/runtime/composables/useEponymeCollection.js +15 -5
- package/dist/runtime/composables/useEponymeForm.js +4 -1
- package/dist/runtime/composables/useEponymeHistory.d.ts +2 -2
- package/dist/runtime/composables/useEponymeHistory.js +2 -1
- package/dist/runtime/composables/useEponymeVariables.d.ts +3 -0
- package/dist/runtime/composables/useEponymeVariables.js +5 -0
- package/dist/runtime/pages/EponymeDetailPage.vue +1 -1
- package/dist/runtime/pages/EponymeIndexPage.vue +173 -10
- package/dist/runtime/server/api/eponyme/[name].get.d.ts +5 -1
- package/dist/runtime/server/api/eponyme/[name].get.js +3 -1
- package/dist/runtime/server/api/eponyme/[name].patch.js +17 -1
- package/dist/runtime/server/api/eponyme-collections/[path].delete.js +7 -2
- package/dist/runtime/server/api/eponyme-collections/[path].get.d.ts +1 -3
- package/dist/runtime/server/api/eponyme-collections/[path].get.js +31 -4
- package/dist/runtime/server/api/eponyme-export.get.d.ts +2 -0
- package/dist/runtime/server/api/eponyme-export.get.js +11 -0
- package/dist/runtime/server/api/eponyme-forms/[path].post.js +14 -1
- package/dist/runtime/server/api/eponyme-history/[path].patch.d.ts +1 -1
- package/dist/runtime/server/api/eponyme-history/[path].patch.js +14 -2
- package/dist/runtime/server/api/eponyme-import.post.d.ts +2 -0
- package/dist/runtime/server/api/eponyme-import.post.js +31 -0
- package/dist/runtime/server/api/eponyme-trash/[path].delete.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-trash/[path].delete.js +16 -0
- package/dist/runtime/server/api/eponyme-trash/[path].get.d.ts +2 -0
- package/dist/runtime/server/api/eponyme-trash/[path].get.js +11 -0
- package/dist/runtime/server/api/eponyme-trash/[path].patch.d.ts +4 -0
- package/dist/runtime/server/api/eponyme-trash/[path].patch.js +16 -0
- package/dist/runtime/server/services/eponyme-form-store.d.ts +1 -0
- package/dist/runtime/server/services/eponyme-store.d.ts +91 -3
- package/dist/runtime/server/services/eponyme-store.js +272 -9
- package/dist/runtime/server/utils/eponyme-entry.d.ts +9 -0
- package/dist/runtime/server/utils/eponyme-entry.js +7 -0
- package/dist/runtime/server/utils/eponyme-hooks.d.ts +16 -0
- package/dist/runtime/server/utils/eponyme-hooks.js +24 -0
- package/dist/runtime/server/utils/eponyme-variables.d.ts +5 -0
- package/dist/runtime/server/utils/eponyme-variables.js +5 -0
- package/dist/runtime/types/eponyme-config.d.ts +7 -0
- package/dist/runtime/types/field.d.ts +1 -0
- package/dist/runtime/types/hooks.d.ts +67 -0
- package/dist/runtime/types/hooks.js +0 -0
- package/dist/runtime/types/index.d.ts +1 -0
- package/dist/runtime/types/index.js +1 -0
- package/dist/runtime/types/nuxt.d.ts +10 -0
- package/dist/runtime/types/variables.d.ts +17 -0
- package/dist/runtime/types/variables.js +0 -0
- package/dist/runtime/utils/build-navigation-tree.d.ts +22 -0
- package/dist/runtime/utils/build-navigation-tree.js +30 -0
- package/dist/runtime/utils/date-locale.d.ts +10 -0
- package/dist/runtime/utils/date-locale.js +1 -0
- package/dist/runtime/utils/downloadable-link.d.ts +2 -0
- package/dist/runtime/utils/downloadable-link.js +35 -0
- package/dist/runtime/utils/empty-variables.d.ts +2 -0
- package/dist/runtime/utils/empty-variables.js +1 -0
- package/dist/runtime/utils/filter-navigation-tree.d.ts +7 -0
- package/dist/runtime/utils/filter-navigation-tree.js +39 -0
- package/dist/runtime/utils/get-field-default-value.js +1 -1
- package/dist/runtime/utils/hydration-cache.d.ts +11 -0
- package/dist/runtime/utils/hydration-cache.js +3 -0
- package/dist/runtime/utils/validate-eponyme-data.js +1 -1
- package/dist/runtime/utils/variables.d.ts +18 -0
- package/dist/runtime/utils/variables.js +52 -0
- package/dist/types.d.mts +2 -2
- package/package.json +3 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const DOWNLOADABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2
|
+
"7z",
|
|
3
|
+
"apk",
|
|
4
|
+
"bz2",
|
|
5
|
+
"csv",
|
|
6
|
+
"dmg",
|
|
7
|
+
"doc",
|
|
8
|
+
"docx",
|
|
9
|
+
"epub",
|
|
10
|
+
"gz",
|
|
11
|
+
"json",
|
|
12
|
+
"odg",
|
|
13
|
+
"odp",
|
|
14
|
+
"ods",
|
|
15
|
+
"odt",
|
|
16
|
+
"pdf",
|
|
17
|
+
"ppt",
|
|
18
|
+
"pptx",
|
|
19
|
+
"rar",
|
|
20
|
+
"rtf",
|
|
21
|
+
"tar",
|
|
22
|
+
"txt",
|
|
23
|
+
"xls",
|
|
24
|
+
"xlsx",
|
|
25
|
+
"xml",
|
|
26
|
+
"zip"
|
|
27
|
+
]);
|
|
28
|
+
export function getDownloadableExtension(href) {
|
|
29
|
+
const path = href.trim().split(/[?#]/, 1)[0] ?? "";
|
|
30
|
+
const extension = path.match(/\.([a-z0-9]+)$/i)?.[1]?.toLowerCase();
|
|
31
|
+
return extension && DOWNLOADABLE_EXTENSIONS.has(extension) ? extension : void 0;
|
|
32
|
+
}
|
|
33
|
+
export function isDownloadableLink(href) {
|
|
34
|
+
return getDownloadableExtension(href) !== void 0;
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { EponymeNavigationNode } from '../types/eponyme-navigation.js';
|
|
2
|
+
/**
|
|
3
|
+
* Keeps the nodes matching `query`, along with every ancestor needed to reach them.
|
|
4
|
+
* A node matched on its own label keeps all of its children, so selecting a folder
|
|
5
|
+
* shows what it holds. An empty query returns the tree untouched.
|
|
6
|
+
*/
|
|
7
|
+
export declare function filterEponymeNavigationTree(nodes: EponymeNavigationNode[], query: string): EponymeNavigationNode[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import Fuse from "fuse.js";
|
|
2
|
+
function normalize(value) {
|
|
3
|
+
return value.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase();
|
|
4
|
+
}
|
|
5
|
+
function collect(nodes, items) {
|
|
6
|
+
for (const node of nodes) {
|
|
7
|
+
items.push({ path: node.path, label: normalize(node.label), searchPath: normalize(node.path) });
|
|
8
|
+
collect(node.children, items);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const indexes = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
function indexOf(nodes) {
|
|
13
|
+
const cached = indexes.get(nodes);
|
|
14
|
+
if (cached) return cached;
|
|
15
|
+
const items = [];
|
|
16
|
+
collect(nodes, items);
|
|
17
|
+
const fuse = new Fuse(items, {
|
|
18
|
+
keys: ["label", "searchPath"],
|
|
19
|
+
// Tolerant enough for a typo, strict enough that a short query does not match everything.
|
|
20
|
+
threshold: 0.35,
|
|
21
|
+
// Without this a match near the end of a long label is penalised into irrelevance.
|
|
22
|
+
ignoreLocation: true
|
|
23
|
+
});
|
|
24
|
+
indexes.set(nodes, fuse);
|
|
25
|
+
return fuse;
|
|
26
|
+
}
|
|
27
|
+
export function filterEponymeNavigationTree(nodes, query) {
|
|
28
|
+
const term = normalize(query.trim());
|
|
29
|
+
if (!term) return nodes;
|
|
30
|
+
const matched = new Set(indexOf(nodes).search(term).map((result) => result.item.path));
|
|
31
|
+
function keep(list) {
|
|
32
|
+
return list.flatMap((node) => {
|
|
33
|
+
if (matched.has(node.path)) return [node];
|
|
34
|
+
const children = keep(node.children);
|
|
35
|
+
return children.length ? [{ ...node, children }] : [];
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return keep(nodes);
|
|
39
|
+
}
|
|
@@ -31,7 +31,7 @@ export function getFieldDefaultValue(definition) {
|
|
|
31
31
|
case "number":
|
|
32
32
|
return 0;
|
|
33
33
|
case "url":
|
|
34
|
-
return { href: "", type: "external", openInNewTab: false };
|
|
34
|
+
return { href: "", type: "external", openInNewTab: false, download: false };
|
|
35
35
|
case "boolean":
|
|
36
36
|
return false;
|
|
37
37
|
default:
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NuxtApp } from '#app';
|
|
2
|
+
/**
|
|
3
|
+
* `getCachedData` for reads that must never serve a stale value.
|
|
4
|
+
*
|
|
5
|
+
* Returning `undefined` unconditionally looks like the obvious way to always refetch,
|
|
6
|
+
* but it also discards the payload the server rendered from: the first client render
|
|
7
|
+
* then has no data and disagrees with the HTML, which Vue reports as a hydration
|
|
8
|
+
* mismatch. Reusing the payload while hydrating, and nothing afterwards, keeps the
|
|
9
|
+
* first paint faithful and still refetches on every later navigation.
|
|
10
|
+
*/
|
|
11
|
+
export declare function cacheDuringHydrationOnly<T>(key: string, nuxtApp: NuxtApp): T | undefined;
|
|
@@ -127,7 +127,7 @@ function validateFieldRules(name, definition, value, errors, mode, _context) {
|
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
129
129
|
const link = value;
|
|
130
|
-
if (typeof link.href !== "string" || !["internal", "external"].includes(String(link.type)) || typeof link.openInNewTab !== "boolean") {
|
|
130
|
+
if (typeof link.href !== "string" || !["internal", "external"].includes(String(link.type)) || typeof link.openInNewTab !== "boolean" || link.download !== void 0 && typeof link.download !== "boolean") {
|
|
131
131
|
addError(errors, name, "Must be a valid link.");
|
|
132
132
|
return;
|
|
133
133
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { EponymeVariableDefinition, EponymeVariables } from '../types/variables.js';
|
|
2
|
+
/** Always available, whatever the host application configures. */
|
|
3
|
+
export declare function builtinEponymeVariables(now?: Date): Record<string, EponymeVariableDefinition>;
|
|
4
|
+
/** Host variables win, so an application can override a built-in for its own wording. */
|
|
5
|
+
export declare function resolveEponymeVariables(custom?: EponymeVariables, now?: Date): Record<string, string>;
|
|
6
|
+
export declare function summariseEponymeVariables(custom?: EponymeVariables): Array<{
|
|
7
|
+
name: string;
|
|
8
|
+
label: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
preview: string;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Replaces every `{{ name }}` occurrence. An unknown name is left untouched rather
|
|
14
|
+
* than blanked, so a typo shows up on the page instead of silently deleting text.
|
|
15
|
+
*/
|
|
16
|
+
export declare function interpolateEponymeText(text: string, variables: Record<string, string>): string;
|
|
17
|
+
/** Walks strings nested in objects and arrays, leaving every other value alone. */
|
|
18
|
+
export declare function interpolateEponymeValue<T>(value: T, variables: Record<string, string>): T;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const VARIABLE_PATTERN = /\{\{\s*([A-Z_]\w*)\s*\}\}/gi;
|
|
2
|
+
export function builtinEponymeVariables(now = /* @__PURE__ */ new Date()) {
|
|
3
|
+
return {
|
|
4
|
+
currentYear: { label: "Current year", description: "The year at the time the page is served.", value: () => now.getFullYear() },
|
|
5
|
+
nextYear: { label: "Next year", description: "Useful for a season spanning two years.", value: () => now.getFullYear() + 1 },
|
|
6
|
+
previousYear: { label: "Previous year", value: () => now.getFullYear() - 1 },
|
|
7
|
+
currentMonth: { label: "Current month", value: () => now.toLocaleDateString(void 0, { month: "long" }) },
|
|
8
|
+
currentDay: { label: "Current day", value: () => String(now.getDate()) },
|
|
9
|
+
today: { label: "Today", description: "Localised date, for example 29 July 2026.", value: () => now.toLocaleDateString(void 0, { dateStyle: "long" }) },
|
|
10
|
+
currentDate: { label: "Today (ISO)", description: "Machine-readable date, for example 2026-07-29.", value: () => now.toISOString().slice(0, 10) }
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function toDefinition(entry) {
|
|
14
|
+
return typeof entry === "object" && entry !== null && "value" in entry ? entry : { value: entry };
|
|
15
|
+
}
|
|
16
|
+
export function resolveEponymeVariables(custom = {}, now) {
|
|
17
|
+
const merged = { ...builtinEponymeVariables(now) };
|
|
18
|
+
for (const [name, entry] of Object.entries(custom)) merged[name] = toDefinition(entry);
|
|
19
|
+
return Object.fromEntries(Object.entries(merged).map(([name, definition]) => {
|
|
20
|
+
try {
|
|
21
|
+
const value = typeof definition.value === "function" ? definition.value() : definition.value;
|
|
22
|
+
return [name, String(value)];
|
|
23
|
+
} catch {
|
|
24
|
+
return [name, ""];
|
|
25
|
+
}
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
export function summariseEponymeVariables(custom = {}) {
|
|
29
|
+
const merged = { ...builtinEponymeVariables() };
|
|
30
|
+
for (const [name, entry] of Object.entries(custom)) merged[name] = toDefinition(entry);
|
|
31
|
+
const resolved = resolveEponymeVariables(custom);
|
|
32
|
+
return Object.entries(merged).map(([name, definition]) => ({
|
|
33
|
+
name,
|
|
34
|
+
label: definition.label ?? name,
|
|
35
|
+
description: definition.description,
|
|
36
|
+
preview: resolved[name] ?? ""
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
export function interpolateEponymeText(text, variables) {
|
|
40
|
+
if (!text.includes("{{")) return text;
|
|
41
|
+
return text.replace(VARIABLE_PATTERN, (match, name) => variables[name] ?? match);
|
|
42
|
+
}
|
|
43
|
+
export function interpolateEponymeValue(value, variables) {
|
|
44
|
+
if (typeof value === "string") return interpolateEponymeText(value, variables);
|
|
45
|
+
if (Array.isArray(value)) return value.map((item) => interpolateEponymeValue(item, variables));
|
|
46
|
+
if (value && typeof value === "object") {
|
|
47
|
+
return Object.fromEntries(
|
|
48
|
+
Object.entries(value).map(([key, nested]) => [key, interpolateEponymeValue(nested, variables)])
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
package/dist/types.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export { type collection, type defineEponymeConfig, type form } from './eponyme.mjs'
|
|
1
|
+
export { type collection, type defineEponymeConfig, type defineEponymeVariables, type form } from './eponyme.mjs'
|
|
2
2
|
|
|
3
3
|
export { type field, type today } from '../dist/runtime/fields/index.js'
|
|
4
4
|
|
|
5
5
|
export { type EPONYME_ROLES, type canEditEponyme, type isEponymeRole } from '../dist/runtime/types/auth.js'
|
|
6
6
|
|
|
7
|
-
export { type ArrayFieldDefinition, type ArrayFieldOptions, type ArrayItemDefinition, type ArrayItemFieldDefinition, type ArrayItemSchema, type ArrayItemValue, type BooleanFieldDefinition, type BooleanFieldOptions, type CheckboxGroupFieldDefinition, type CheckboxGroupFieldOptions, type CollectionSlugField, type ColorFieldDefinition, type ColorFieldOptions, type DateFieldDefinition, type DateFieldOptions, type EmailFieldDefinition, type EmailFieldOptions, type EponymeAuthSession, type EponymeAuthUser, type EponymeCollectionDataByName, type EponymeCollectionDefinition, type EponymeCollectionDefinitionBase, type EponymeCollectionName, type EponymeCollectionOptions, type EponymeConfig, type EponymeData, type EponymeDataByName, type EponymeFormDataByName, type EponymeFormDefinition, type EponymeFormDefinitionBase, type EponymeFormFieldDefinition, type EponymeFormMode, type EponymeFormName, type EponymeFormOptions, type EponymeFormSchema, type EponymeManagedUserResult, type EponymeName, type EponymeRole, type EponymeSchema, type FieldDefinition, type FieldValidationResult, type FieldValidator, type FieldVisibilityCondition, type ImageFieldDefinition, type ImageFieldOptions, type NumberFieldDefinition, type NumberFieldOptions, type RadioFieldDefinition, type RadioFieldOptions, type RichTextFieldDefinition, type RichTextFieldOptions, type SectionFieldDefinition, type SectionFieldOptions, type SectionItemFieldDefinition, type SectionItemValue, type SectionSchema, type SectionValue, type SelectFieldDefinition, type SelectFieldOptions, type SelectOption, type SlugFieldDefinition, type SlugFieldOptions, type StringFieldDefinition, type StringFieldOptions, type TabDefinition, type TabFieldDefinition, type TabFieldOptions, type TabsSchema, type TabsValue, type TextareaFieldDefinition, type TextareaFieldOptions, type TodayDateDefault, type UrlFieldDefinition, type UrlFieldOptions, type UrlType, type UrlValue } from '../dist/runtime/types/index.js'
|
|
7
|
+
export { type ArrayFieldDefinition, type ArrayFieldOptions, type ArrayItemDefinition, type ArrayItemFieldDefinition, type ArrayItemSchema, type ArrayItemValue, type BooleanFieldDefinition, type BooleanFieldOptions, type CheckboxGroupFieldDefinition, type CheckboxGroupFieldOptions, type CollectionSlugField, type ColorFieldDefinition, type ColorFieldOptions, type DateFieldDefinition, type DateFieldOptions, type EmailFieldDefinition, type EmailFieldOptions, type EponymeAuthSession, type EponymeAuthUser, type EponymeCollectionDataByName, type EponymeCollectionDefinition, type EponymeCollectionDefinitionBase, type EponymeCollectionName, type EponymeCollectionOptions, type EponymeConfig, type EponymeData, type EponymeDataByName, type EponymeFormDataByName, type EponymeFormDefinition, type EponymeFormDefinitionBase, type EponymeFormFieldDefinition, type EponymeFormMode, type EponymeFormName, type EponymeFormOptions, type EponymeFormSchema, type EponymeManagedUserResult, type EponymeName, type EponymeRole, type EponymeSchema, type EponymeVariableDefinition, type EponymeVariableSummary, type EponymeVariableValue, type EponymeVariables, type FieldDefinition, type FieldValidationResult, type FieldValidator, type FieldVisibilityCondition, type ImageFieldDefinition, type ImageFieldOptions, type NumberFieldDefinition, type NumberFieldOptions, type RadioFieldDefinition, type RadioFieldOptions, type RichTextFieldDefinition, type RichTextFieldOptions, type SectionFieldDefinition, type SectionFieldOptions, type SectionItemFieldDefinition, type SectionItemValue, type SectionSchema, type SectionValue, type SelectFieldDefinition, type SelectFieldOptions, type SelectOption, type SlugFieldDefinition, type SlugFieldOptions, type StringFieldDefinition, type StringFieldOptions, type TabDefinition, type TabFieldDefinition, type TabFieldOptions, type TabsSchema, type TabsValue, type TextareaFieldDefinition, type TextareaFieldOptions, type TodayDateDefault, type UrlFieldDefinition, type UrlFieldOptions, type UrlType, type UrlValue } from '../dist/runtime/types/index.js'
|
|
8
8
|
|
|
9
9
|
export { default } from './module.mjs'
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karibsen/eponyme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A lightweight, type-safe content manager for Nuxt.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"@iconify-json/mingcute": "^1.2.7",
|
|
63
63
|
"@nuxt/icon": "^2.3.1",
|
|
64
64
|
"@nuxt/kit": "^4.4.8",
|
|
65
|
+
"@karibsen/ui": "latest",
|
|
65
66
|
"@tailwindcss/vite": "^4.1.12",
|
|
66
67
|
"@tanstack/vue-table": "^8.21.3",
|
|
67
68
|
"@tiptap/extension-image": "^3.29.2",
|
|
@@ -70,6 +71,7 @@
|
|
|
70
71
|
"@tiptap/vue-3": "^3.28.0",
|
|
71
72
|
"@vueuse/core": "^14.3.0",
|
|
72
73
|
"@vueuse/integrations": "^14.3.0",
|
|
74
|
+
"fuse.js": "^7.5.0",
|
|
73
75
|
"picocolors": "^1.1.1",
|
|
74
76
|
"reka-ui": "^2.10.1",
|
|
75
77
|
"slugify": "^1.6.9",
|