@commonpub/layer 0.4.13 → 0.5.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.
- package/package.json +8 -8
- package/pages/[type]/[slug]/edit.vue +79 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonpub/layer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"files": [
|
|
@@ -50,16 +50,16 @@
|
|
|
50
50
|
"vue": "^3.4.0",
|
|
51
51
|
"vue-router": "^4.3.0",
|
|
52
52
|
"zod": "^4.3.6",
|
|
53
|
+
"@commonpub/config": "0.8.0",
|
|
54
|
+
"@commonpub/editor": "0.5.0",
|
|
55
|
+
"@commonpub/explainer": "0.6.1",
|
|
56
|
+
"@commonpub/schema": "0.8.15",
|
|
57
|
+
"@commonpub/protocol": "0.9.6",
|
|
58
|
+
"@commonpub/docs": "0.6.0",
|
|
53
59
|
"@commonpub/auth": "0.5.0",
|
|
54
60
|
"@commonpub/learning": "0.5.0",
|
|
55
|
-
"@commonpub/schema": "0.8.15",
|
|
56
|
-
"@commonpub/editor": "0.5.0",
|
|
57
|
-
"@commonpub/config": "0.8.0",
|
|
58
|
-
"@commonpub/docs": "0.5.2",
|
|
59
|
-
"@commonpub/explainer": "0.5.3",
|
|
60
61
|
"@commonpub/server": "2.23.1",
|
|
61
|
-
"@commonpub/ui": "0.8.4"
|
|
62
|
-
"@commonpub/protocol": "0.9.5"
|
|
62
|
+
"@commonpub/ui": "0.8.4"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { Component } from 'vue';
|
|
3
3
|
import type { BlockTuple } from '@commonpub/editor';
|
|
4
|
+
import { isExplainerDocument, createEmptyDocument } from '@commonpub/explainer';
|
|
5
|
+
import type { ExplainerDocument } from '@commonpub/explainer';
|
|
6
|
+
import { ExplainerSectionEditor } from '@commonpub/explainer/vue';
|
|
4
7
|
definePageMeta({ layout: false, middleware: 'auth' });
|
|
5
8
|
|
|
6
9
|
const route = useRoute();
|
|
@@ -29,9 +32,23 @@ const { extract: extractError } = useApiError();
|
|
|
29
32
|
const mode = ref<'write' | 'preview' | 'code'>('write');
|
|
30
33
|
const contentId = ref<string | null>(null);
|
|
31
34
|
|
|
32
|
-
// --- Block editor ---
|
|
35
|
+
// --- Block editor (articles, blogs, projects) ---
|
|
33
36
|
const blockEditor = useBlockEditor();
|
|
34
37
|
|
|
38
|
+
// --- Explainer document (explainers only) ---
|
|
39
|
+
const isExplainer = computed(() => contentType.value === 'explainer');
|
|
40
|
+
// The prop passed to ExplainerSectionEditor — set once on load, not updated on edits
|
|
41
|
+
const explainerDocInit = ref<ExplainerDocument | null>(null);
|
|
42
|
+
// The latest version from the editor — used for saving
|
|
43
|
+
const explainerDocLatest = ref<ExplainerDocument | null>(null);
|
|
44
|
+
|
|
45
|
+
function getContentForSave(): unknown {
|
|
46
|
+
if (isExplainer.value) {
|
|
47
|
+
return explainerDocLatest.value ?? explainerDocInit.value;
|
|
48
|
+
}
|
|
49
|
+
return blockEditor.toBlockTuples();
|
|
50
|
+
}
|
|
51
|
+
|
|
35
52
|
// --- Content save composable ---
|
|
36
53
|
const {
|
|
37
54
|
saving,
|
|
@@ -50,7 +67,7 @@ const {
|
|
|
50
67
|
isNew,
|
|
51
68
|
contentId,
|
|
52
69
|
isDirty,
|
|
53
|
-
getBlockTuples: () =>
|
|
70
|
+
getBlockTuples: getContentForSave as () => BlockTuple[],
|
|
54
71
|
extractError,
|
|
55
72
|
onAfterSave: syncBOM,
|
|
56
73
|
});
|
|
@@ -59,7 +76,7 @@ const {
|
|
|
59
76
|
const { errors: publishErrors, showErrors: showPublishErrors, validate, dismiss: dismissPublishErrors } = usePublishValidation({
|
|
60
77
|
title,
|
|
61
78
|
metadata,
|
|
62
|
-
getBlockTuples: () =>
|
|
79
|
+
getBlockTuples: getContentForSave as () => BlockTuple[],
|
|
63
80
|
});
|
|
64
81
|
|
|
65
82
|
// --- Specialized editor component map ---
|
|
@@ -80,7 +97,18 @@ if (!isNew.value) {
|
|
|
80
97
|
const d = data.value as Record<string, unknown>;
|
|
81
98
|
contentId.value = d.id as string;
|
|
82
99
|
title.value = d.title as string;
|
|
83
|
-
if (
|
|
100
|
+
if (isExplainer.value && isExplainerDocument(d.content)) {
|
|
101
|
+
// Load ExplainerDocument for explainers
|
|
102
|
+
const doc = d.content as unknown as ExplainerDocument;
|
|
103
|
+
// Sync: prefer row-level title if hero title is empty
|
|
104
|
+
if (!doc.hero.title && title.value) {
|
|
105
|
+
doc.hero.title = title.value;
|
|
106
|
+
} else if (doc.hero.title) {
|
|
107
|
+
title.value = doc.hero.title;
|
|
108
|
+
}
|
|
109
|
+
explainerDocInit.value = doc;
|
|
110
|
+
explainerDocLatest.value = JSON.parse(JSON.stringify(doc));
|
|
111
|
+
} else if (Array.isArray(d.content)) {
|
|
84
112
|
blockEditor.fromBlockTuples(d.content as [string, Record<string, unknown>][]);
|
|
85
113
|
}
|
|
86
114
|
metadata.value = {
|
|
@@ -115,7 +143,42 @@ watch(title, (newTitle) => {
|
|
|
115
143
|
|
|
116
144
|
// --- Dirty tracking + autosave ---
|
|
117
145
|
watch(() => blockEditor.blocks.value, () => { isDirty.value = true; }, { deep: true });
|
|
118
|
-
|
|
146
|
+
watch(explainerDocLatest, () => { isDirty.value = true; }, { deep: true });
|
|
147
|
+
initAutoSave([() => blockEditor.blocks.value, () => explainerDocLatest.value, title, metadata]);
|
|
148
|
+
|
|
149
|
+
// --- Explainer document events ---
|
|
150
|
+
function handleExplainerUpdate(doc: ExplainerDocument): void {
|
|
151
|
+
// Store latest for saving — DON'T update explainerDocInit (would cause editor re-render loop)
|
|
152
|
+
explainerDocLatest.value = doc;
|
|
153
|
+
// Sync title from hero
|
|
154
|
+
if (doc.hero.title) title.value = doc.hero.title;
|
|
155
|
+
// Sync metadata
|
|
156
|
+
if (doc.meta.description) metadata.value = { ...metadata.value, description: doc.meta.description };
|
|
157
|
+
if (doc.meta.difficulty) metadata.value = { ...metadata.value, difficulty: doc.meta.difficulty };
|
|
158
|
+
if (doc.meta.estimatedMinutes) metadata.value = { ...metadata.value, estimatedMinutes: doc.meta.estimatedMinutes };
|
|
159
|
+
if (doc.meta.tags?.length) metadata.value = { ...metadata.value, tags: doc.meta.tags };
|
|
160
|
+
if (doc.hero.coverImageUrl) metadata.value = { ...metadata.value, coverImageUrl: doc.hero.coverImageUrl };
|
|
161
|
+
isDirty.value = true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function handleExplainerSave(doc: ExplainerDocument): void {
|
|
165
|
+
handleExplainerUpdate(doc);
|
|
166
|
+
silentSave();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// --- Init new explainer ---
|
|
170
|
+
if (isNew.value && isExplainer.value) {
|
|
171
|
+
const emptyDoc = createEmptyDocument();
|
|
172
|
+
explainerDocInit.value = emptyDoc;
|
|
173
|
+
explainerDocLatest.value = JSON.parse(JSON.stringify(emptyDoc));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Sync starter form title to explainer doc hero
|
|
177
|
+
watch(title, (newTitle) => {
|
|
178
|
+
if (isExplainer.value && explainerDocInit.value && !explainerDocLatest.value?.hero.title) {
|
|
179
|
+
explainerDocInit.value.hero.title = newTitle;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
119
182
|
|
|
120
183
|
function handleMetadataUpdate(newMetadata: Record<string, unknown>): void {
|
|
121
184
|
if (newMetadata.title !== undefined && typeof newMetadata.title === 'string') {
|
|
@@ -344,8 +407,17 @@ async function handleUrlImport(result: ImportedContent): Promise<void> {
|
|
|
344
407
|
|
|
345
408
|
<div v-if="error" class="cpub-editor-error" role="alert">{{ error }}</div>
|
|
346
409
|
|
|
347
|
-
<!--
|
|
348
|
-
<template v-if="mode === 'write' &&
|
|
410
|
+
<!-- Explainer: section-oriented editor -->
|
|
411
|
+
<template v-if="mode === 'write' && isExplainer && explainerDocInit">
|
|
412
|
+
<ExplainerSectionEditor
|
|
413
|
+
:document="explainerDocInit"
|
|
414
|
+
@update:document="handleExplainerUpdate"
|
|
415
|
+
@save="handleExplainerSave"
|
|
416
|
+
/>
|
|
417
|
+
</template>
|
|
418
|
+
|
|
419
|
+
<!-- Write mode with specialized editor (articles, blogs, projects) -->
|
|
420
|
+
<template v-else-if="mode === 'write' && hasSpecializedEditor && !isExplainer">
|
|
349
421
|
<component
|
|
350
422
|
:is="editorComponent"
|
|
351
423
|
:block-editor="blockEditor"
|