@commonpub/layer 0.4.5 → 0.4.7
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/components/SiteLogo.vue +19 -12
- package/components/editors/BlockWrapper.vue +3 -3
- package/components/editors/DocsPageTree.vue +559 -0
- package/components/editors/EditorShell.vue +62 -34
- package/components/views/ExplainerView.vue +14 -1
- package/package.json +7 -7
- package/pages/docs/[siteSlug]/[...pagePath].vue +20 -6
- package/pages/docs/[siteSlug]/edit.vue +1021 -383
- package/pages/index.vue +42 -0
- package/server/api/docs/[siteSlug]/pages/[pageId].get.ts +35 -2
- package/server/api/docs/[siteSlug]/pages/index.get.ts +14 -1
- package/server/api/docs/migrate-content.post.ts +101 -0
- package/theme/agora-dark.css +3 -0
- package/theme/agora.css +3 -0
|
@@ -19,47 +19,61 @@ function toggleRight(): void {
|
|
|
19
19
|
</script>
|
|
20
20
|
|
|
21
21
|
<template>
|
|
22
|
-
<div class="cpub-editor-shell-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<
|
|
30
|
-
|
|
22
|
+
<div class="cpub-editor-shell-wrapper">
|
|
23
|
+
<div class="cpub-editor-shell-inner">
|
|
24
|
+
<!-- Mobile sidebar toggles -->
|
|
25
|
+
<div class="cpub-editor-mobile-toggles">
|
|
26
|
+
<button v-if="showLeftSidebar" class="cpub-editor-toggle-btn" aria-label="Toggle blocks panel" @click="toggleLeft">
|
|
27
|
+
<i class="fa-solid fa-layer-group"></i>
|
|
28
|
+
</button>
|
|
29
|
+
<button v-if="showRightSidebar" class="cpub-editor-toggle-btn" aria-label="Toggle properties panel" @click="toggleRight">
|
|
30
|
+
<i class="fa-solid fa-sliders"></i>
|
|
31
|
+
</button>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<!-- Left sidebar -->
|
|
35
|
+
<aside
|
|
36
|
+
v-if="showLeftSidebar"
|
|
37
|
+
class="cpub-editor-left"
|
|
38
|
+
:class="{ 'cpub-editor-sidebar-open': leftOpen }"
|
|
39
|
+
aria-label="Editor sidebar"
|
|
40
|
+
>
|
|
41
|
+
<slot name="left" />
|
|
42
|
+
</aside>
|
|
43
|
+
|
|
44
|
+
<!-- Overlay for mobile sidebars -->
|
|
45
|
+
<div v-if="leftOpen || rightOpen" class="cpub-editor-overlay" @click="leftOpen = false; rightOpen = false" />
|
|
46
|
+
|
|
47
|
+
<div class="cpub-editor-center">
|
|
48
|
+
<slot />
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<!-- Right sidebar -->
|
|
52
|
+
<aside
|
|
53
|
+
v-if="showRightSidebar"
|
|
54
|
+
class="cpub-editor-right"
|
|
55
|
+
:class="{ 'cpub-editor-sidebar-open': rightOpen }"
|
|
56
|
+
aria-label="Properties"
|
|
57
|
+
>
|
|
58
|
+
<slot name="right" />
|
|
59
|
+
</aside>
|
|
31
60
|
</div>
|
|
32
61
|
|
|
33
|
-
<!--
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
class="cpub-editor-left"
|
|
37
|
-
:class="{ 'cpub-editor-sidebar-open': leftOpen }"
|
|
38
|
-
aria-label="Editor sidebar"
|
|
39
|
-
>
|
|
40
|
-
<slot name="left" />
|
|
41
|
-
</aside>
|
|
42
|
-
|
|
43
|
-
<!-- Overlay for mobile sidebars -->
|
|
44
|
-
<div v-if="leftOpen || rightOpen" class="cpub-editor-overlay" @click="leftOpen = false; rightOpen = false" />
|
|
45
|
-
|
|
46
|
-
<div class="cpub-editor-center">
|
|
47
|
-
<slot />
|
|
62
|
+
<!-- Status bar -->
|
|
63
|
+
<div v-if="$slots.status" class="cpub-editor-status-bar">
|
|
64
|
+
<slot name="status" />
|
|
48
65
|
</div>
|
|
49
|
-
|
|
50
|
-
<!-- Right sidebar -->
|
|
51
|
-
<aside
|
|
52
|
-
v-if="showRightSidebar"
|
|
53
|
-
class="cpub-editor-right"
|
|
54
|
-
:class="{ 'cpub-editor-sidebar-open': rightOpen }"
|
|
55
|
-
aria-label="Properties"
|
|
56
|
-
>
|
|
57
|
-
<slot name="right" />
|
|
58
|
-
</aside>
|
|
59
66
|
</div>
|
|
60
67
|
</template>
|
|
61
68
|
|
|
62
69
|
<style scoped>
|
|
70
|
+
.cpub-editor-shell-wrapper {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column;
|
|
73
|
+
flex: 1;
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
}
|
|
76
|
+
|
|
63
77
|
.cpub-editor-shell-inner {
|
|
64
78
|
display: flex;
|
|
65
79
|
flex: 1;
|
|
@@ -67,6 +81,20 @@ function toggleRight(): void {
|
|
|
67
81
|
position: relative;
|
|
68
82
|
}
|
|
69
83
|
|
|
84
|
+
.cpub-editor-status-bar {
|
|
85
|
+
height: 28px;
|
|
86
|
+
flex-shrink: 0;
|
|
87
|
+
background: var(--surface);
|
|
88
|
+
border-top: var(--border-width-default) solid var(--border);
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
padding: 0 12px;
|
|
92
|
+
gap: 14px;
|
|
93
|
+
font-family: var(--font-mono);
|
|
94
|
+
font-size: 10px;
|
|
95
|
+
color: var(--text-faint);
|
|
96
|
+
}
|
|
97
|
+
|
|
70
98
|
.cpub-editor-left {
|
|
71
99
|
width: 220px;
|
|
72
100
|
flex-shrink: 0;
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { ContentViewData } from '../../composables/useEngagement';
|
|
3
3
|
import type { BlockTuple } from '@commonpub/editor';
|
|
4
|
+
import { isExplainerDocument } from '@commonpub/explainer';
|
|
5
|
+
import type { ExplainerDocument } from '@commonpub/explainer';
|
|
4
6
|
|
|
5
7
|
const props = defineProps<{
|
|
6
8
|
content: ContentViewData;
|
|
7
9
|
federatedId?: string;
|
|
8
10
|
}>();
|
|
9
11
|
|
|
12
|
+
// Detect V2 ExplainerDocument format vs legacy BlockTuple[] format
|
|
13
|
+
const isV2Format = computed(() => isExplainerDocument(props.content?.content));
|
|
14
|
+
const v2Document = computed<ExplainerDocument | null>(() =>
|
|
15
|
+
isV2Format.value ? (props.content.content as unknown as ExplainerDocument) : null,
|
|
16
|
+
);
|
|
17
|
+
|
|
10
18
|
const blocks = computed<BlockTuple[]>(() => {
|
|
19
|
+
if (isV2Format.value) return []; // V2 uses its own viewer
|
|
11
20
|
const raw = props.content?.content;
|
|
12
21
|
if (!Array.isArray(raw)) return [];
|
|
13
22
|
return raw as BlockTuple[];
|
|
@@ -154,7 +163,11 @@ onUnmounted(() => { document.removeEventListener('keydown', onKeydown); });
|
|
|
154
163
|
</script>
|
|
155
164
|
|
|
156
165
|
<template>
|
|
157
|
-
|
|
166
|
+
<!-- V2 Scroll Viewer for ExplainerDocument format -->
|
|
167
|
+
<ScrollViewer v-if="isV2Format && v2Document" :document="v2Document" />
|
|
168
|
+
|
|
169
|
+
<!-- Legacy slide-deck viewer for BlockTuple[] format -->
|
|
170
|
+
<div v-else class="cpub-explainer-view">
|
|
158
171
|
<!-- PROGRESS BAR -->
|
|
159
172
|
<div class="cpub-progress-line">
|
|
160
173
|
<div class="cpub-progress-line-fill" :style="{ width: progressPct + '%' }"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonpub/layer",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"files": [
|
|
@@ -50,15 +50,15 @@
|
|
|
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
53
|
"@commonpub/docs": "0.5.2",
|
|
55
|
-
"@commonpub/auth": "0.5.0",
|
|
56
54
|
"@commonpub/editor": "0.5.0",
|
|
57
|
-
"@commonpub/ui": "0.8.3",
|
|
58
55
|
"@commonpub/learning": "0.5.0",
|
|
59
|
-
"@commonpub/
|
|
60
|
-
"@commonpub/
|
|
61
|
-
"@commonpub/
|
|
56
|
+
"@commonpub/auth": "0.5.0",
|
|
57
|
+
"@commonpub/config": "0.8.0",
|
|
58
|
+
"@commonpub/server": "2.22.1",
|
|
59
|
+
"@commonpub/protocol": "0.9.5",
|
|
60
|
+
"@commonpub/ui": "0.8.4",
|
|
61
|
+
"@commonpub/schema": "0.8.13"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { TocEntry } from '@commonpub/docs';
|
|
3
|
+
import type { BlockTuple } from '@commonpub/editor';
|
|
3
4
|
|
|
4
5
|
const route = useRoute();
|
|
5
6
|
const siteSlug = computed(() => route.params.siteSlug as string);
|
|
@@ -12,19 +13,29 @@ const { data: site } = useLazyFetch(() => `/api/docs/${siteSlug.value}`);
|
|
|
12
13
|
const { data: nav } = useLazyFetch(() => `/api/docs/${siteSlug.value}/nav`);
|
|
13
14
|
const { data: pages } = useLazyFetch(() => `/api/docs/${siteSlug.value}/pages`);
|
|
14
15
|
|
|
15
|
-
// Fetch the rendered page (server-side markdown rendering)
|
|
16
|
+
// Fetch the rendered page (server-side markdown rendering or block content)
|
|
16
17
|
interface RenderedPage {
|
|
17
18
|
id: string;
|
|
18
19
|
title: string;
|
|
19
20
|
slug: string;
|
|
20
|
-
content: string;
|
|
21
|
+
content: string | BlockTuple[];
|
|
21
22
|
sortOrder: number;
|
|
22
23
|
parentId: string | null;
|
|
23
|
-
html: string;
|
|
24
|
+
html: string | null;
|
|
24
25
|
toc: TocEntry[];
|
|
25
26
|
frontmatter: { title?: string; description?: string };
|
|
27
|
+
format?: 'blocks' | 'markdown';
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
const isBlockContent = computed(() =>
|
|
31
|
+
renderedPage.value?.format === 'blocks' || Array.isArray(renderedPage.value?.content),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const blockContent = computed<BlockTuple[]>(() => {
|
|
35
|
+
if (!renderedPage.value || !isBlockContent.value) return [];
|
|
36
|
+
return renderedPage.value.content as BlockTuple[];
|
|
37
|
+
});
|
|
38
|
+
|
|
28
39
|
const { data: renderedPage, pending: pagePending, error: pageError, refresh: refreshPage } = useLazyFetch<RenderedPage>(
|
|
29
40
|
() => `/api/docs/${siteSlug.value}/pages/${pagePath.value}`,
|
|
30
41
|
{ key: `doc-page-${siteSlug.value}-${pagePath.value}` },
|
|
@@ -89,7 +100,7 @@ const activeHeadingId = ref('');
|
|
|
89
100
|
// Scroll spy for TOC
|
|
90
101
|
function setupScrollSpy(): void {
|
|
91
102
|
if (!import.meta.client) return;
|
|
92
|
-
const headings = document.querySelectorAll('.docs-content h2[id], .docs-content h3[id]');
|
|
103
|
+
const headings = document.querySelectorAll('.docs-content h2[id], .docs-content h3[id], .docs-content .cpub-block-heading[id]');
|
|
93
104
|
if (!headings.length) return;
|
|
94
105
|
const observer = new IntersectionObserver(
|
|
95
106
|
(entries) => {
|
|
@@ -197,7 +208,7 @@ useSeoMeta({
|
|
|
197
208
|
<aside class="docs-sidebar" :class="{ open: sidebarOpen }" aria-label="Documentation navigation">
|
|
198
209
|
<div class="docs-sidebar-header">
|
|
199
210
|
<NuxtLink :to="`/docs/${siteSlug}`" class="docs-sidebar-title">{{ site.name }}</NuxtLink>
|
|
200
|
-
<NuxtLink v-if="isOwner" :to="`/docs/${siteSlug}/edit`" class="docs-edit-link" aria-label="Edit
|
|
211
|
+
<NuxtLink v-if="isOwner" :to="`/docs/${siteSlug}/edit?page=${pagePath}`" class="docs-edit-link" aria-label="Edit this page">
|
|
201
212
|
<i class="fa-solid fa-pen"></i>
|
|
202
213
|
</NuxtLink>
|
|
203
214
|
</div>
|
|
@@ -301,7 +312,10 @@ useSeoMeta({
|
|
|
301
312
|
<h1 class="docs-page-title">{{ renderedPage.title }}</h1>
|
|
302
313
|
|
|
303
314
|
<!-- Rendered Content -->
|
|
304
|
-
<div class="docs-content cpub-prose"
|
|
315
|
+
<div v-if="isBlockContent" class="docs-content cpub-prose">
|
|
316
|
+
<BlocksBlockContentRenderer :blocks="blockContent" />
|
|
317
|
+
</div>
|
|
318
|
+
<div v-else class="docs-content cpub-prose" v-html="renderedPage.html" />
|
|
305
319
|
|
|
306
320
|
<!-- Prev / Next -->
|
|
307
321
|
<div class="docs-prev-next" v-if="prevNextLinks.prev || prevNextLinks.next">
|