@type32/yaml-editor-form 1.0.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 +1092 -0
- package/dist/module.d.mts +8 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +21 -0
- package/dist/runtime/components/YamlCollapsible.d.vue.ts +41 -0
- package/dist/runtime/components/YamlCollapsible.vue +43 -0
- package/dist/runtime/components/YamlCollapsible.vue.d.ts +41 -0
- package/dist/runtime/components/YamlFieldInput.d.vue.ts +37 -0
- package/dist/runtime/components/YamlFieldInput.vue +157 -0
- package/dist/runtime/components/YamlFieldInput.vue.d.ts +37 -0
- package/dist/runtime/components/YamlFormEditor.d.vue.ts +92 -0
- package/dist/runtime/components/YamlFormEditor.vue +68 -0
- package/dist/runtime/components/YamlFormEditor.vue.d.ts +92 -0
- package/dist/runtime/components/YamlFormField.d.vue.ts +64 -0
- package/dist/runtime/components/YamlFormField.vue +493 -0
- package/dist/runtime/components/YamlFormField.vue.d.ts +64 -0
- package/dist/runtime/composables/useYamlFieldTypes.d.ts +17 -0
- package/dist/runtime/composables/useYamlFieldTypes.js +136 -0
- package/dist/runtime/composables/useYamlFormData.d.ts +24 -0
- package/dist/runtime/composables/useYamlFormData.js +122 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/types/types.d.ts +30 -0
- package/dist/types.d.mts +3 -0
- package/package.json +68 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
export const DEFAULT_FIELD_TYPES = [
|
|
2
|
+
{
|
|
3
|
+
type: "string",
|
|
4
|
+
label: "Text",
|
|
5
|
+
icon: "i-lucide-type",
|
|
6
|
+
defaultValue: "",
|
|
7
|
+
detect: (value) => typeof value === "string" && !isDateString(value) && !isDateTimeString(value)
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
type: "textarea",
|
|
11
|
+
label: "Long Text",
|
|
12
|
+
icon: "i-lucide-align-left",
|
|
13
|
+
defaultValue: "",
|
|
14
|
+
component: "textarea"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
type: "number",
|
|
18
|
+
label: "Number",
|
|
19
|
+
icon: "i-lucide-hash",
|
|
20
|
+
defaultValue: 0,
|
|
21
|
+
detect: (value) => typeof value === "number"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: "boolean",
|
|
25
|
+
label: "Boolean",
|
|
26
|
+
icon: "i-lucide-circle-check",
|
|
27
|
+
defaultValue: false,
|
|
28
|
+
detect: (value) => typeof value === "boolean"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
type: "date",
|
|
32
|
+
label: "Date",
|
|
33
|
+
icon: "i-lucide-calendar",
|
|
34
|
+
defaultValue: () => /* @__PURE__ */ new Date(),
|
|
35
|
+
detect: (value) => isDateObject(value) || isDateString(value)
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: "datetime",
|
|
39
|
+
label: "Date & Time",
|
|
40
|
+
icon: "i-lucide-calendar-clock",
|
|
41
|
+
defaultValue: () => /* @__PURE__ */ new Date(),
|
|
42
|
+
detect: (value) => isDateTimeString(value)
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: "string-array",
|
|
46
|
+
label: "Tags",
|
|
47
|
+
icon: "i-lucide-tags",
|
|
48
|
+
defaultValue: [],
|
|
49
|
+
detect: (value) => isStringArray(value)
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: "array",
|
|
53
|
+
label: "Array",
|
|
54
|
+
icon: "i-lucide-list",
|
|
55
|
+
defaultValue: [],
|
|
56
|
+
detect: (value) => Array.isArray(value) && !isStringArray(value)
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: "object",
|
|
60
|
+
label: "Object",
|
|
61
|
+
icon: "i-lucide-box",
|
|
62
|
+
defaultValue: {},
|
|
63
|
+
detect: (value) => typeof value === "object" && value !== null && !Array.isArray(value) && !isDateObject(value)
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
type: "null",
|
|
67
|
+
label: "Null",
|
|
68
|
+
icon: "i-lucide-circle-slash",
|
|
69
|
+
defaultValue: null,
|
|
70
|
+
detect: (value) => value === null
|
|
71
|
+
}
|
|
72
|
+
];
|
|
73
|
+
function isDateObject(value) {
|
|
74
|
+
return value instanceof Date;
|
|
75
|
+
}
|
|
76
|
+
function isDateString(value) {
|
|
77
|
+
if (typeof value !== "string") return false;
|
|
78
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
79
|
+
}
|
|
80
|
+
function isDateTimeString(value) {
|
|
81
|
+
if (typeof value !== "string") return false;
|
|
82
|
+
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(value);
|
|
83
|
+
}
|
|
84
|
+
function isStringArray(value) {
|
|
85
|
+
return Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string");
|
|
86
|
+
}
|
|
87
|
+
export function useYamlFieldTypes(customTypes) {
|
|
88
|
+
const fieldTypes = computed(() => {
|
|
89
|
+
const types = [...DEFAULT_FIELD_TYPES];
|
|
90
|
+
if (customTypes) {
|
|
91
|
+
for (const customType of customTypes) {
|
|
92
|
+
const existingIndex = types.findIndex((t) => t.type === customType.type);
|
|
93
|
+
if (existingIndex >= 0) {
|
|
94
|
+
types[existingIndex] = customType;
|
|
95
|
+
} else {
|
|
96
|
+
types.push(customType);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return types;
|
|
101
|
+
});
|
|
102
|
+
const getFieldType = (type) => {
|
|
103
|
+
return fieldTypes.value.find((t) => t.type === type);
|
|
104
|
+
};
|
|
105
|
+
const detectFieldType = (value) => {
|
|
106
|
+
for (const fieldType of fieldTypes.value) {
|
|
107
|
+
if (fieldType.detect && fieldType.detect(value)) {
|
|
108
|
+
return fieldType;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return fieldTypes.value.find((t) => t.type === "string");
|
|
112
|
+
};
|
|
113
|
+
const getDefaultValue = (type) => {
|
|
114
|
+
const fieldType = getFieldType(type);
|
|
115
|
+
if (!fieldType) return "";
|
|
116
|
+
return typeof fieldType.defaultValue === "function" ? fieldType.defaultValue() : fieldType.defaultValue;
|
|
117
|
+
};
|
|
118
|
+
const getIcon = (type) => {
|
|
119
|
+
return getFieldType(type)?.icon || "i-lucide-circle-question-mark";
|
|
120
|
+
};
|
|
121
|
+
const getTypeMenuItems = (onSelect) => {
|
|
122
|
+
return fieldTypes.value.map((fieldType) => ({
|
|
123
|
+
label: fieldType.label,
|
|
124
|
+
icon: fieldType.icon,
|
|
125
|
+
onSelect: () => onSelect(fieldType.type)
|
|
126
|
+
}));
|
|
127
|
+
};
|
|
128
|
+
return {
|
|
129
|
+
fieldTypes,
|
|
130
|
+
getFieldType,
|
|
131
|
+
detectFieldType,
|
|
132
|
+
getDefaultValue,
|
|
133
|
+
getIcon,
|
|
134
|
+
getTypeMenuItems
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { YamlFormData } from "../types/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Composable for working with YAML form data
|
|
4
|
+
* Provides utilities for parsing, stringifying, and validating YAML
|
|
5
|
+
*/
|
|
6
|
+
export declare function useYamlFormData(): {
|
|
7
|
+
parseYaml: (yamlString: string) => YamlFormData;
|
|
8
|
+
stringifyYaml: (data: YamlFormData) => string;
|
|
9
|
+
validateYaml: (yamlString: string) => {
|
|
10
|
+
valid: boolean;
|
|
11
|
+
error?: string;
|
|
12
|
+
};
|
|
13
|
+
cloneYaml: (data: YamlFormData) => YamlFormData;
|
|
14
|
+
mergeYaml: (target: YamlFormData, source: YamlFormData) => YamlFormData;
|
|
15
|
+
getValueAtPath: (data: YamlFormData, path: string) => any;
|
|
16
|
+
setValueAtPath: (data: YamlFormData, path: string, value: any) => YamlFormData;
|
|
17
|
+
isEmpty: (data: YamlFormData) => boolean;
|
|
18
|
+
getAllKeys: (data: YamlFormData, prefix?: string) => string[];
|
|
19
|
+
diffYaml: (oldData: YamlFormData, newData: YamlFormData) => {
|
|
20
|
+
added: string[];
|
|
21
|
+
removed: string[];
|
|
22
|
+
modified: string[];
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import YAML from "yaml";
|
|
2
|
+
export function useYamlFormData() {
|
|
3
|
+
function parseYaml(yamlString) {
|
|
4
|
+
try {
|
|
5
|
+
return YAML.parse(yamlString) || {};
|
|
6
|
+
} catch (error) {
|
|
7
|
+
console.error("Failed to parse YAML:", error);
|
|
8
|
+
return {};
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function stringifyYaml(data) {
|
|
12
|
+
try {
|
|
13
|
+
return YAML.stringify(data, {
|
|
14
|
+
indent: 2,
|
|
15
|
+
lineWidth: 0,
|
|
16
|
+
// Don't wrap lines
|
|
17
|
+
minContentWidth: 0
|
|
18
|
+
});
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error("Failed to stringify YAML:", error);
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function validateYaml(yamlString) {
|
|
25
|
+
try {
|
|
26
|
+
YAML.parse(yamlString);
|
|
27
|
+
return { valid: true };
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return {
|
|
30
|
+
valid: false,
|
|
31
|
+
error: error?.message || "Invalid YAML"
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function cloneYaml(data) {
|
|
36
|
+
return JSON.parse(JSON.stringify(data));
|
|
37
|
+
}
|
|
38
|
+
function mergeYaml(target, source) {
|
|
39
|
+
const result = { ...target };
|
|
40
|
+
for (const key in source) {
|
|
41
|
+
const targetValue = result[key];
|
|
42
|
+
const sourceValue = source[key];
|
|
43
|
+
if (typeof targetValue === "object" && !Array.isArray(targetValue) && targetValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && sourceValue !== null) {
|
|
44
|
+
result[key] = mergeYaml(targetValue, sourceValue);
|
|
45
|
+
} else {
|
|
46
|
+
result[key] = sourceValue;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
function getValueAtPath(data, path) {
|
|
52
|
+
const keys = path.split(/\.|\[|\]/).filter(Boolean);
|
|
53
|
+
let current = data;
|
|
54
|
+
for (const key of keys) {
|
|
55
|
+
if (current === null || current === void 0) return void 0;
|
|
56
|
+
current = current[key];
|
|
57
|
+
}
|
|
58
|
+
return current;
|
|
59
|
+
}
|
|
60
|
+
function setValueAtPath(data, path, value) {
|
|
61
|
+
const keys = path.split(/\.|\[|\]/).filter(Boolean);
|
|
62
|
+
const result = cloneYaml(data);
|
|
63
|
+
let current = result;
|
|
64
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
65
|
+
const key = keys[i];
|
|
66
|
+
if (!key) continue;
|
|
67
|
+
if (!(key in current)) {
|
|
68
|
+
const nextKey = keys[i + 1];
|
|
69
|
+
if (nextKey) {
|
|
70
|
+
current[key] = /^\d+$/.test(nextKey) ? [] : {};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
current = current[key];
|
|
74
|
+
}
|
|
75
|
+
const lastKey = keys[keys.length - 1];
|
|
76
|
+
if (lastKey) {
|
|
77
|
+
current[lastKey] = value;
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
function isEmpty(data) {
|
|
82
|
+
if (!data) return true;
|
|
83
|
+
return Object.keys(data).length === 0;
|
|
84
|
+
}
|
|
85
|
+
function getAllKeys(data, prefix = "") {
|
|
86
|
+
const keys = [];
|
|
87
|
+
for (const key in data) {
|
|
88
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
89
|
+
keys.push(fullKey);
|
|
90
|
+
const value = data[key];
|
|
91
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
92
|
+
keys.push(...getAllKeys(value, fullKey));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return keys;
|
|
96
|
+
}
|
|
97
|
+
function diffYaml(oldData, newData) {
|
|
98
|
+
const oldKeys = getAllKeys(oldData);
|
|
99
|
+
const newKeys = getAllKeys(newData);
|
|
100
|
+
const added = newKeys.filter((key) => !oldKeys.includes(key));
|
|
101
|
+
const removed = oldKeys.filter((key) => !newKeys.includes(key));
|
|
102
|
+
const modified = newKeys.filter((key) => {
|
|
103
|
+
if (added.includes(key)) return false;
|
|
104
|
+
const oldValue = getValueAtPath(oldData, key);
|
|
105
|
+
const newValue = getValueAtPath(newData, key);
|
|
106
|
+
return JSON.stringify(oldValue) !== JSON.stringify(newValue);
|
|
107
|
+
});
|
|
108
|
+
return { added, removed, modified };
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
parseYaml,
|
|
112
|
+
stringifyYaml,
|
|
113
|
+
validateYaml,
|
|
114
|
+
cloneYaml,
|
|
115
|
+
mergeYaml,
|
|
116
|
+
getValueAtPath,
|
|
117
|
+
setValueAtPath,
|
|
118
|
+
isEmpty,
|
|
119
|
+
getAllKeys,
|
|
120
|
+
diffYaml
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type YamlFormData<T extends object = {}> = {
|
|
2
|
+
title?: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
date?: Date;
|
|
5
|
+
draft?: boolean;
|
|
6
|
+
tags?: string[];
|
|
7
|
+
categories?: string[];
|
|
8
|
+
image?: string;
|
|
9
|
+
slug?: string;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
} & T
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* YAML Field Type Definition
|
|
15
|
+
* Centralized schema for all field types supported by the YAML editor
|
|
16
|
+
*/
|
|
17
|
+
export interface YamlFieldType {
|
|
18
|
+
/** Unique type identifier */
|
|
19
|
+
type: string
|
|
20
|
+
/** Display label */
|
|
21
|
+
label: string
|
|
22
|
+
/** Lucide icon name */
|
|
23
|
+
icon: string
|
|
24
|
+
/** Default value when creating new field of this type */
|
|
25
|
+
defaultValue: any
|
|
26
|
+
/** Optional slot name for custom component rendering */
|
|
27
|
+
component?: string
|
|
28
|
+
/** Optional detection function for auto-typing existing values */
|
|
29
|
+
detect?: (value: any) => boolean
|
|
30
|
+
}
|
package/dist/types.d.mts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@type32/yaml-editor-form",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A powerful, schema-driven YAML/frontmatter editor for Nuxt v4 with Nuxt UI components. Supports custom field types, nested structures, and extensible type system.",
|
|
5
|
+
"repository": "https://github.com/CTRL-Neo-Studios/yaml-editor-form",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/types.d.mts",
|
|
11
|
+
"import": "./dist/module.mjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/module.mjs",
|
|
15
|
+
"typesVersions": {
|
|
16
|
+
"*": {
|
|
17
|
+
".": [
|
|
18
|
+
"./dist/types.d.mts"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"workspaces": [
|
|
26
|
+
"playground"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"prepack": "bunx nuxt-module-build build",
|
|
30
|
+
"dev": "bun run dev:prepare && bunx nuxi dev playground",
|
|
31
|
+
"dev:build": "bunx nuxi build playground",
|
|
32
|
+
"dev:prepare": "bunx nuxt-module-build build --stub && bunx nuxt-module-build prepare && bunx nuxi prepare playground",
|
|
33
|
+
"release": "bun run prepack && bunx changelogen --release && bun publish && git push --follow-tags",
|
|
34
|
+
"lint": "bunx eslint .",
|
|
35
|
+
"test": "bunx vitest run",
|
|
36
|
+
"test:watch": "bunx vitest watch",
|
|
37
|
+
"test:types": "bunx vue-tsc --noEmit && cd playground && bunx vue-tsc --noEmit"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@iconify-json/lucide": "^1.2.87",
|
|
41
|
+
"@iconify-json/simple-icons": "^1.2.68",
|
|
42
|
+
"@internationalized/date": "^3.10.1",
|
|
43
|
+
"@nuxt/icon": "2.2.1",
|
|
44
|
+
"@nuxt/kit": "^4.3.0",
|
|
45
|
+
"@nuxt/ui": "4.4.0",
|
|
46
|
+
"@tiptap/extension-collaboration": "^3.17.1",
|
|
47
|
+
"@tiptap/extension-node-range": "^3.17.1",
|
|
48
|
+
"@tiptap/y-tiptap": "^3.0.2",
|
|
49
|
+
"tailwindcss": "^4.1.18",
|
|
50
|
+
"yaml": "^2.8.2",
|
|
51
|
+
"yjs": "^13.6.29"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@nuxt/devtools": "^3.1.1",
|
|
55
|
+
"@nuxt/eslint-config": "^1.13.0",
|
|
56
|
+
"@nuxt/hints": "1.0.0-alpha.6",
|
|
57
|
+
"@nuxt/image": "2.0.0",
|
|
58
|
+
"@nuxt/module-builder": "^1.0.2",
|
|
59
|
+
"@nuxt/schema": "^4.3.0",
|
|
60
|
+
"@nuxt/test-utils": "^3.23.0",
|
|
61
|
+
"@types/node": "^25.0.10",
|
|
62
|
+
"changelogen": "^0.6.2",
|
|
63
|
+
"nuxt": "^4.3.0",
|
|
64
|
+
"typescript": "~5.9.3",
|
|
65
|
+
"vitest": "^4.0.18",
|
|
66
|
+
"vue-tsc": "^3.2.4"
|
|
67
|
+
}
|
|
68
|
+
}
|