@commonpub/layer 0.7.2 → 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 (38) 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 +5 -5
  7. package/pages/docs/[siteSlug]/edit.vue +4 -4
  8. package/pages/u/[username]/[type]/[slug]/edit.vue +2 -1
  9. package/components/editors/BlockCanvas.vue +0 -487
  10. package/components/editors/BlockInsertZone.vue +0 -84
  11. package/components/editors/BlockPicker.vue +0 -285
  12. package/components/editors/BlockWrapper.vue +0 -192
  13. package/components/editors/EditorBlocks.vue +0 -248
  14. package/components/editors/EditorSection.vue +0 -81
  15. package/components/editors/EditorShell.vue +0 -196
  16. package/components/editors/EditorTagInput.vue +0 -114
  17. package/components/editors/EditorVisibility.vue +0 -110
  18. package/components/editors/blocks/BuildStepBlock.vue +0 -102
  19. package/components/editors/blocks/CalloutBlock.vue +0 -122
  20. package/components/editors/blocks/CheckpointBlock.vue +0 -27
  21. package/components/editors/blocks/CodeBlock.vue +0 -177
  22. package/components/editors/blocks/DividerBlock.vue +0 -22
  23. package/components/editors/blocks/DownloadsBlock.vue +0 -41
  24. package/components/editors/blocks/EmbedBlock.vue +0 -20
  25. package/components/editors/blocks/GalleryBlock.vue +0 -236
  26. package/components/editors/blocks/HeadingBlock.vue +0 -96
  27. package/components/editors/blocks/ImageBlock.vue +0 -271
  28. package/components/editors/blocks/MarkdownBlock.vue +0 -258
  29. package/components/editors/blocks/MathBlock.vue +0 -37
  30. package/components/editors/blocks/PartsListBlock.vue +0 -358
  31. package/components/editors/blocks/QuizBlock.vue +0 -47
  32. package/components/editors/blocks/QuoteBlock.vue +0 -101
  33. package/components/editors/blocks/SectionHeaderBlock.vue +0 -130
  34. package/components/editors/blocks/SliderBlock.vue +0 -318
  35. package/components/editors/blocks/TextBlock.vue +0 -201
  36. package/components/editors/blocks/ToolListBlock.vue +0 -70
  37. package/components/editors/blocks/VideoBlock.vue +0 -22
  38. 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>