@commonpub/layer 0.7.1 → 0.7.3

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 (43) hide show
  1. package/components/editors/ArticleEditor.vue +11 -12
  2. package/components/editors/BlogEditor.vue +17 -18
  3. package/components/editors/ExplainerEditor.vue +13 -14
  4. package/components/editors/ProjectEditor.vue +17 -18
  5. package/composables/useMarkdownImport.ts +1 -1
  6. package/package.json +8 -8
  7. package/pages/docs/[siteSlug]/edit.vue +4 -4
  8. package/pages/federated-hubs/[id]/posts/[postId].vue +91 -13
  9. package/pages/hubs/[slug]/index.vue +1 -1
  10. package/pages/hubs/[slug]/posts/[postId].vue +12 -0
  11. package/pages/u/[username]/[type]/[slug]/edit.vue +2 -1
  12. package/server/api/federated-hubs/[id]/posts/[postId]/replies.get.ts +13 -0
  13. package/server/api/federation/hub-post-reply.post.ts +16 -10
  14. package/components/editors/BlockCanvas.vue +0 -487
  15. package/components/editors/BlockInsertZone.vue +0 -84
  16. package/components/editors/BlockPicker.vue +0 -285
  17. package/components/editors/BlockWrapper.vue +0 -192
  18. package/components/editors/EditorBlocks.vue +0 -248
  19. package/components/editors/EditorSection.vue +0 -81
  20. package/components/editors/EditorShell.vue +0 -196
  21. package/components/editors/EditorTagInput.vue +0 -114
  22. package/components/editors/EditorVisibility.vue +0 -110
  23. package/components/editors/blocks/BuildStepBlock.vue +0 -102
  24. package/components/editors/blocks/CalloutBlock.vue +0 -122
  25. package/components/editors/blocks/CheckpointBlock.vue +0 -27
  26. package/components/editors/blocks/CodeBlock.vue +0 -177
  27. package/components/editors/blocks/DividerBlock.vue +0 -22
  28. package/components/editors/blocks/DownloadsBlock.vue +0 -41
  29. package/components/editors/blocks/EmbedBlock.vue +0 -20
  30. package/components/editors/blocks/GalleryBlock.vue +0 -236
  31. package/components/editors/blocks/HeadingBlock.vue +0 -96
  32. package/components/editors/blocks/ImageBlock.vue +0 -271
  33. package/components/editors/blocks/MarkdownBlock.vue +0 -258
  34. package/components/editors/blocks/MathBlock.vue +0 -37
  35. package/components/editors/blocks/PartsListBlock.vue +0 -358
  36. package/components/editors/blocks/QuizBlock.vue +0 -47
  37. package/components/editors/blocks/QuoteBlock.vue +0 -101
  38. package/components/editors/blocks/SectionHeaderBlock.vue +0 -130
  39. package/components/editors/blocks/SliderBlock.vue +0 -318
  40. package/components/editors/blocks/TextBlock.vue +0 -201
  41. package/components/editors/blocks/ToolListBlock.vue +0 -70
  42. package/components/editors/blocks/VideoBlock.vue +0 -22
  43. package/composables/useBlockEditor.ts +0 -187
@@ -1,248 +0,0 @@
1
- <script setup lang="ts">
2
- import type { BlockEditor } from '../../composables/useBlockEditor';
3
-
4
- /**
5
- * Reusable block library sidebar for editors.
6
- * Renders a searchable list of insertable blocks grouped by category.
7
- * Clicking a block adds it to the end of the document via the block editor composable.
8
- */
9
- export interface BlockDef {
10
- type: string;
11
- label: string;
12
- icon: string;
13
- description?: string;
14
- attrs?: Record<string, unknown>;
15
- }
16
-
17
- export interface BlockGroup {
18
- name: string;
19
- variant?: string;
20
- blocks: BlockDef[];
21
- }
22
-
23
- const props = defineProps<{
24
- groups: BlockGroup[];
25
- blockEditor: BlockEditor;
26
- }>();
27
-
28
- const blockSearch = ref('');
29
-
30
- const filteredGroups = computed(() => {
31
- const q = blockSearch.value.toLowerCase();
32
- if (!q) return props.groups;
33
- return props.groups
34
- .map((g) => ({ ...g, blocks: g.blocks.filter((b) => b.label.toLowerCase().includes(q)) }))
35
- .filter((g) => g.blocks.length > 0);
36
- });
37
-
38
- function insertBlock(block: BlockDef): void {
39
- const selectedId = props.blockEditor.selectedBlockId.value;
40
- if (selectedId) {
41
- const idx = props.blockEditor.getBlockIndex(selectedId);
42
- props.blockEditor.addBlock(block.type, block.attrs, idx + 1);
43
- } else {
44
- props.blockEditor.addBlock(block.type, block.attrs);
45
- }
46
- }
47
- </script>
48
-
49
- <template>
50
- <div class="cpub-block-library">
51
- <div class="cpub-bl-search">
52
- <i class="fa-solid fa-magnifying-glass cpub-bl-search-icon"></i>
53
- <input
54
- v-model="blockSearch"
55
- type="text"
56
- placeholder="Search blocks..."
57
- class="cpub-bl-search-input"
58
- aria-label="Search blocks"
59
- />
60
- </div>
61
- <div class="cpub-bl-groups">
62
- <div v-for="group in filteredGroups" :key="group.name" class="cpub-bl-group">
63
- <div class="cpub-bl-group-label">{{ group.name }}</div>
64
- <div class="cpub-bl-blocks">
65
- <button
66
- v-for="block in group.blocks"
67
- :key="block.type + (block.attrs?.variant ?? '')"
68
- :data-block="block.type"
69
- class="cpub-bl-block"
70
- :class="group.variant"
71
- :title="block.label"
72
- @click="insertBlock(block)"
73
- >
74
- <span class="cpub-bl-block-icon"><i :class="['fa-solid', block.icon]"></i></span>
75
- <span class="cpub-bl-block-label">{{ block.label }}</span>
76
- <span class="cpub-bl-block-drag"><i class="fa-solid fa-grip-dots-vertical"></i></span>
77
- </button>
78
- </div>
79
- </div>
80
- <div v-if="filteredGroups.length === 0" class="cpub-bl-empty">
81
- No blocks match "{{ blockSearch }}"
82
- </div>
83
- </div>
84
- </div>
85
- </template>
86
-
87
- <style scoped>
88
- .cpub-block-library {
89
- display: flex;
90
- flex-direction: column;
91
- height: 100%;
92
- }
93
-
94
- .cpub-bl-search {
95
- display: flex;
96
- align-items: center;
97
- gap: 7px;
98
- background: var(--surface2);
99
- border: var(--border-width-default) solid var(--border);
100
- padding: 5px 9px;
101
- margin: 10px 8px 4px;
102
- }
103
-
104
- .cpub-bl-search-icon {
105
- font-size: 10px;
106
- color: var(--text-faint);
107
- flex-shrink: 0;
108
- }
109
-
110
- .cpub-bl-search-input {
111
- background: transparent;
112
- border: none;
113
- outline: none;
114
- font-size: 12px;
115
- color: var(--text);
116
- width: 100%;
117
- }
118
-
119
- .cpub-bl-search-input::placeholder {
120
- color: var(--text-faint);
121
- }
122
-
123
- .cpub-bl-groups {
124
- flex: 1;
125
- overflow-y: auto;
126
- padding: 4px 0;
127
- }
128
-
129
- .cpub-bl-group {
130
- padding: 4px 0;
131
- }
132
-
133
- .cpub-bl-group-label {
134
- font-family: var(--font-mono);
135
- font-size: 9px;
136
- font-weight: 600;
137
- letter-spacing: 0.14em;
138
- text-transform: uppercase;
139
- color: var(--text-faint);
140
- padding: 6px 12px 4px;
141
- }
142
-
143
- .cpub-bl-blocks {
144
- display: flex;
145
- flex-direction: column;
146
- gap: 1px;
147
- }
148
-
149
- .cpub-bl-block {
150
- display: flex;
151
- align-items: center;
152
- gap: 9px;
153
- padding: 10px 10px;
154
- cursor: pointer;
155
- border: var(--border-width-default) solid transparent;
156
- background: transparent;
157
- color: var(--text-dim);
158
- font-size: 12px;
159
- user-select: none;
160
- transition: background 0.1s;
161
- text-align: left;
162
- width: 100%;
163
- margin: 0 4px;
164
- }
165
-
166
- .cpub-bl-block:hover {
167
- background: var(--surface2);
168
- border-color: var(--border2);
169
- color: var(--text);
170
- }
171
-
172
- .cpub-bl-block-icon {
173
- width: 22px;
174
- height: 22px;
175
- background: var(--surface3);
176
- border: var(--border-width-default) solid var(--border2);
177
- display: flex;
178
- align-items: center;
179
- justify-content: center;
180
- font-size: 9px;
181
- color: var(--text-faint);
182
- flex-shrink: 0;
183
- transition: background 0.1s, color 0.1s;
184
- }
185
-
186
- .cpub-bl-block:hover .cpub-bl-block-icon {
187
- background: var(--accent-bg);
188
- border-color: var(--accent-border);
189
- color: var(--accent);
190
- }
191
-
192
- .cpub-bl-block-label {
193
- font-size: 11px;
194
- flex: 1;
195
- }
196
-
197
- .cpub-bl-block-drag {
198
- font-size: 9px;
199
- color: var(--text-faint);
200
- opacity: 0;
201
- transition: opacity 0.1s;
202
- }
203
-
204
- .cpub-bl-block:hover .cpub-bl-block-drag {
205
- opacity: 1;
206
- }
207
-
208
- .cpub-bl-empty {
209
- font-size: 11px;
210
- color: var(--text-faint);
211
- padding: 12px;
212
- text-align: center;
213
- }
214
-
215
- /* Touch devices: always show drag grip since there's no hover */
216
- @media (hover: none) {
217
- .cpub-bl-block-drag { opacity: 1; }
218
- }
219
-
220
- /* Per-block-type icon colors */
221
- [data-block="heading"] .cpub-bl-block-icon { color: var(--teal); background: color-mix(in srgb, var(--teal) 10%, transparent); }
222
- [data-block="text"] .cpub-bl-block-icon,
223
- [data-block="paragraph"] .cpub-bl-block-icon { color: var(--text-dim); background: var(--surface2); }
224
- [data-block="image"] .cpub-bl-block-icon { color: #38bdf8; background: rgba(56, 189, 248, 0.08); }
225
- [data-block="code"] .cpub-bl-block-icon,
226
- [data-block="code_block"] .cpub-bl-block-icon { color: #c084fc; background: rgba(192, 132, 252, 0.08); }
227
- [data-block="callout"] .cpub-bl-block-icon { color: #fbbf24; background: rgba(251, 191, 36, 0.08); }
228
- [data-block="quote"] .cpub-bl-block-icon,
229
- [data-block="blockquote"] .cpub-bl-block-icon { color: #94a3b8; background: rgba(148, 163, 184, 0.08); }
230
- [data-block="embed"] .cpub-bl-block-icon { color: #f472b6; background: rgba(244, 114, 182, 0.08); }
231
- [data-block="video"] .cpub-bl-block-icon { color: #fb923c; background: rgba(251, 146, 60, 0.08); }
232
- [data-block="divider"] .cpub-bl-block-icon,
233
- [data-block="horizontal_rule"] .cpub-bl-block-icon,
234
- [data-block="horizontalRule"] .cpub-bl-block-icon { color: var(--text-faint); background: var(--surface2); }
235
- [data-block="gallery"] .cpub-bl-block-icon { color: #2dd4bf; background: rgba(45, 212, 191, 0.08); }
236
- [data-block="quiz"] .cpub-bl-block-icon { color: #4ade80; background: rgba(74, 222, 128, 0.08); }
237
- [data-block="slider"] .cpub-bl-block-icon,
238
- [data-block="interactiveSlider"] .cpub-bl-block-icon { color: #818cf8; background: rgba(129, 140, 248, 0.08); }
239
- [data-block="math"] .cpub-bl-block-icon,
240
- [data-block="mathNotation"] .cpub-bl-block-icon { color: #e879f9; background: rgba(232, 121, 249, 0.08); }
241
- [data-block="markdown"] .cpub-bl-block-icon { color: var(--text-dim); background: var(--surface2); }
242
- [data-block="buildStep"] .cpub-bl-block-icon { color: var(--accent); background: var(--accent-bg); }
243
- [data-block="partsList"] .cpub-bl-block-icon { color: #fb7185; background: rgba(251, 113, 133, 0.08); }
244
- [data-block="toolList"] .cpub-bl-block-icon { color: #a78bfa; background: rgba(167, 139, 250, 0.08); }
245
- [data-block="downloads"] .cpub-bl-block-icon { color: #22d3ee; background: rgba(34, 211, 238, 0.08); }
246
- [data-block="sectionHeader"] .cpub-bl-block-icon { color: var(--accent); background: var(--accent-bg); }
247
- [data-block="checkpoint"] .cpub-bl-block-icon { color: #34d399; background: rgba(52, 211, 153, 0.08); }
248
- </style>
@@ -1,81 +0,0 @@
1
- <script setup lang="ts">
2
- /**
3
- * Collapsible section for editor property panels.
4
- * Reused across all editor types.
5
- */
6
- defineProps<{
7
- title: string;
8
- icon: string;
9
- open?: boolean;
10
- }>();
11
-
12
- const emit = defineEmits<{
13
- toggle: [];
14
- }>();
15
- </script>
16
-
17
- <template>
18
- <div class="cpub-ep-section" :class="{ collapsed: !open }">
19
- <button class="cpub-ep-section-header" @click="emit('toggle')">
20
- <i :class="['fa', icon, 'cpub-ep-sec-icon']"></i>
21
- <span class="cpub-ep-sec-label">{{ title }}</span>
22
- <i class="fa-solid fa-chevron-down cpub-ep-sec-arrow"></i>
23
- </button>
24
- <div v-if="open" class="cpub-ep-section-body">
25
- <slot />
26
- </div>
27
- </div>
28
- </template>
29
-
30
- <style scoped>
31
- .cpub-ep-section {
32
- border-bottom: var(--border-width-default) solid var(--border);
33
- }
34
-
35
- .cpub-ep-section-header {
36
- display: flex;
37
- align-items: center;
38
- gap: 8px;
39
- width: 100%;
40
- padding: 10px 12px;
41
- background: none;
42
- border: none;
43
- cursor: pointer;
44
- color: var(--text);
45
- font-size: 11px;
46
- font-family: var(--font-mono);
47
- font-weight: 600;
48
- letter-spacing: 0.08em;
49
- text-transform: uppercase;
50
- text-align: left;
51
- }
52
-
53
- .cpub-ep-section-header:hover {
54
- background: var(--surface2);
55
- }
56
-
57
- .cpub-ep-sec-icon {
58
- font-size: 10px;
59
- color: var(--text-faint);
60
- width: 14px;
61
- text-align: center;
62
- }
63
-
64
- .cpub-ep-sec-label {
65
- flex: 1;
66
- }
67
-
68
- .cpub-ep-sec-arrow {
69
- font-size: 9px;
70
- color: var(--text-faint);
71
- transition: transform 0.15s;
72
- }
73
-
74
- .collapsed .cpub-ep-sec-arrow {
75
- transform: rotate(-90deg);
76
- }
77
-
78
- .cpub-ep-section-body {
79
- padding: 8px 12px 14px;
80
- }
81
- </style>
@@ -1,196 +0,0 @@
1
- <script setup lang="ts">
2
- defineProps<{
3
- showLeftSidebar?: boolean;
4
- showRightSidebar?: boolean;
5
- }>();
6
-
7
- const leftOpen = ref(false);
8
- const rightOpen = ref(false);
9
-
10
- function toggleLeft(): void {
11
- leftOpen.value = !leftOpen.value;
12
- if (leftOpen.value) rightOpen.value = false;
13
- }
14
-
15
- function toggleRight(): void {
16
- rightOpen.value = !rightOpen.value;
17
- if (rightOpen.value) leftOpen.value = false;
18
- }
19
- </script>
20
-
21
- <template>
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>
60
- </div>
61
-
62
- <!-- Status bar -->
63
- <div v-if="$slots.status" class="cpub-editor-status-bar">
64
- <slot name="status" />
65
- </div>
66
- </div>
67
- </template>
68
-
69
- <style scoped>
70
- .cpub-editor-shell-wrapper {
71
- display: flex;
72
- flex-direction: column;
73
- flex: 1;
74
- overflow: hidden;
75
- }
76
-
77
- .cpub-editor-shell-inner {
78
- display: flex;
79
- flex: 1;
80
- overflow: hidden;
81
- position: relative;
82
- }
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
-
98
- .cpub-editor-left {
99
- width: 220px;
100
- flex-shrink: 0;
101
- background: var(--surface);
102
- border-right: var(--border-width-default) solid var(--border);
103
- overflow-y: auto;
104
- padding: var(--space-4);
105
- }
106
-
107
- .cpub-editor-center {
108
- flex: 1;
109
- overflow-y: auto;
110
- padding: var(--space-6);
111
- background: var(--bg);
112
- }
113
-
114
- .cpub-editor-right {
115
- width: 280px;
116
- flex-shrink: 0;
117
- background: var(--surface);
118
- border-left: var(--border-width-default) solid var(--border);
119
- overflow-y: auto;
120
- padding: var(--space-4);
121
- }
122
-
123
- .cpub-editor-mobile-toggles {
124
- display: none;
125
- }
126
-
127
- .cpub-editor-overlay {
128
- display: none;
129
- }
130
-
131
- @media (max-width: 1024px) {
132
- .cpub-editor-left,
133
- .cpub-editor-right {
134
- position: fixed;
135
- top: 0;
136
- bottom: 0;
137
- z-index: var(--z-modal, 200);
138
- transform: translateX(-100%);
139
- transition: transform 0.2s ease;
140
- }
141
-
142
- .cpub-editor-left {
143
- left: 0;
144
- }
145
-
146
- .cpub-editor-right {
147
- right: 0;
148
- left: auto;
149
- transform: translateX(100%);
150
- }
151
-
152
- .cpub-editor-left.cpub-editor-sidebar-open {
153
- transform: translateX(0);
154
- }
155
-
156
- .cpub-editor-right.cpub-editor-sidebar-open {
157
- transform: translateX(0);
158
- }
159
-
160
- .cpub-editor-overlay {
161
- display: block;
162
- position: fixed;
163
- inset: 0;
164
- background: var(--color-surface-overlay-light);
165
- z-index: calc(var(--z-modal, 200) - 1);
166
- }
167
-
168
- .cpub-editor-mobile-toggles {
169
- display: flex;
170
- position: fixed;
171
- bottom: var(--space-4);
172
- right: var(--space-4);
173
- gap: var(--space-2);
174
- z-index: var(--z-fixed, 100);
175
- }
176
-
177
- .cpub-editor-toggle-btn {
178
- width: 44px;
179
- height: 44px;
180
- border: var(--border-width-default) solid var(--border);
181
- background: var(--surface);
182
- color: var(--text-dim);
183
- font-size: 16px;
184
- cursor: pointer;
185
- display: flex;
186
- align-items: center;
187
- justify-content: center;
188
- box-shadow: var(--shadow-md, var(--shadow-md));
189
- }
190
-
191
- .cpub-editor-toggle-btn:hover {
192
- background: var(--surface2);
193
- color: var(--text);
194
- }
195
- }
196
- </style>
@@ -1,114 +0,0 @@
1
- <script setup lang="ts">
2
- /**
3
- * Reusable tag input for editor panels.
4
- * Used by Article, Blog, Project editors.
5
- */
6
- const props = defineProps<{
7
- tags: string[];
8
- }>();
9
-
10
- const emit = defineEmits<{
11
- 'update:tags': [tags: string[]];
12
- }>();
13
-
14
- const tagInput = ref('');
15
-
16
- function addTag(e: KeyboardEvent): void {
17
- if (e.key === 'Enter' || e.key === ',') {
18
- e.preventDefault();
19
- const val = tagInput.value.trim().replace(/,$/, '');
20
- if (val && !props.tags.includes(val)) {
21
- emit('update:tags', [...props.tags, val]);
22
- }
23
- tagInput.value = '';
24
- }
25
- }
26
-
27
- function removeTag(idx: number): void {
28
- emit('update:tags', props.tags.filter((_: string, i: number) => i !== idx));
29
- }
30
- </script>
31
-
32
- <template>
33
- <div class="cpub-tag-input-wrap">
34
- <div class="cpub-tag-chips">
35
- <span v-for="(tag, i) in tags" :key="i" class="cpub-tag-chip">
36
- {{ tag }}
37
- <button class="cpub-tag-remove" aria-label="Remove tag" @click="removeTag(i)">
38
- <i class="fa-solid fa-xmark"></i>
39
- </button>
40
- </span>
41
- </div>
42
- <input
43
- v-model="tagInput"
44
- type="text"
45
- class="cpub-tag-input"
46
- placeholder="Add tag..."
47
- aria-label="Add tag"
48
- @keydown="addTag"
49
- />
50
- </div>
51
- </template>
52
-
53
- <style scoped>
54
- .cpub-tag-input-wrap {
55
- display: flex;
56
- flex-direction: column;
57
- gap: 6px;
58
- }
59
-
60
- .cpub-tag-chips {
61
- display: flex;
62
- flex-wrap: wrap;
63
- gap: 4px;
64
- }
65
-
66
- .cpub-tag-chip {
67
- display: inline-flex;
68
- align-items: center;
69
- gap: 4px;
70
- font-size: 11px;
71
- font-family: var(--font-mono);
72
- padding: 2px 8px;
73
- background: var(--surface2);
74
- border: var(--border-width-default) solid var(--border2);
75
- color: var(--text-dim);
76
- }
77
-
78
- .cpub-tag-chip:hover {
79
- border-color: var(--accent);
80
- color: var(--accent);
81
- }
82
-
83
- .cpub-tag-remove {
84
- background: none;
85
- border: none;
86
- color: var(--text-faint);
87
- cursor: pointer;
88
- font-size: 9px;
89
- padding: 0;
90
- line-height: 1;
91
- }
92
-
93
- .cpub-tag-remove:hover {
94
- color: var(--red);
95
- }
96
-
97
- .cpub-tag-input {
98
- width: 100%;
99
- background: var(--surface);
100
- border: var(--border-width-default) solid var(--border);
101
- padding: 5px 8px;
102
- font-size: 11px;
103
- color: var(--text);
104
- outline: none;
105
- }
106
-
107
- .cpub-tag-input:focus {
108
- border-color: var(--accent);
109
- }
110
-
111
- .cpub-tag-input::placeholder {
112
- color: var(--text-faint);
113
- }
114
- </style>