@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,285 +0,0 @@
1
- <script setup lang="ts">
2
- /**
3
- * Block type picker — appears when clicking an insert zone.
4
- * Shows available block types grouped by category, with search.
5
- */
6
- export interface BlockTypeDef {
7
- type: string;
8
- label: string;
9
- icon: string;
10
- description?: string;
11
- attrs?: Record<string, unknown>;
12
- }
13
-
14
- export interface BlockTypeGroup {
15
- name: string;
16
- blocks: BlockTypeDef[];
17
- }
18
-
19
- const props = defineProps<{
20
- groups: BlockTypeGroup[];
21
- visible: boolean;
22
- }>();
23
-
24
- const emit = defineEmits<{
25
- select: [type: string, attrs?: Record<string, unknown>];
26
- close: [];
27
- }>();
28
-
29
- const search = ref('');
30
- const selectedIndex = ref(0);
31
- const pickerRef = ref<HTMLElement | null>(null);
32
-
33
- const flatBlocks = computed(() => {
34
- return props.groups.flatMap((g) => g.blocks);
35
- });
36
-
37
- const filteredBlocks = computed(() => {
38
- const q = search.value.toLowerCase();
39
- if (!q) return flatBlocks.value;
40
- return flatBlocks.value.filter(
41
- (b) => b.label.toLowerCase().includes(q) || b.type.toLowerCase().includes(q),
42
- );
43
- });
44
-
45
- watch(() => props.visible, (v) => {
46
- if (v) {
47
- search.value = '';
48
- selectedIndex.value = 0;
49
- nextTick(() => {
50
- (pickerRef.value?.querySelector('.cpub-picker-search') as HTMLInputElement)?.focus();
51
- });
52
- }
53
- });
54
-
55
- watch(search, () => {
56
- selectedIndex.value = 0;
57
- });
58
-
59
- function handleKeydown(event: KeyboardEvent): void {
60
- if (event.key === 'Escape') {
61
- event.preventDefault();
62
- emit('close');
63
- return;
64
- }
65
- if (event.key === 'ArrowDown') {
66
- event.preventDefault();
67
- selectedIndex.value = Math.min(selectedIndex.value + 1, filteredBlocks.value.length - 1);
68
- return;
69
- }
70
- if (event.key === 'ArrowUp') {
71
- event.preventDefault();
72
- selectedIndex.value = Math.max(selectedIndex.value - 1, 0);
73
- return;
74
- }
75
- if (event.key === 'Enter') {
76
- event.preventDefault();
77
- const block = filteredBlocks.value[selectedIndex.value];
78
- if (block) {
79
- emit('select', block.type, block.attrs);
80
- }
81
- return;
82
- }
83
- }
84
-
85
- function selectBlock(block: BlockTypeDef): void {
86
- emit('select', block.type, block.attrs);
87
- }
88
-
89
- // Close on click outside
90
- function handleClickOutside(event: MouseEvent): void {
91
- if (pickerRef.value && !pickerRef.value.contains(event.target as Node)) {
92
- emit('close');
93
- }
94
- }
95
-
96
- onMounted(() => {
97
- document.addEventListener('mousedown', handleClickOutside);
98
- });
99
-
100
- onUnmounted(() => {
101
- document.removeEventListener('mousedown', handleClickOutside);
102
- });
103
- </script>
104
-
105
- <template>
106
- <div v-if="visible" ref="pickerRef" class="cpub-picker" @keydown="handleKeydown">
107
- <div class="cpub-picker-header">
108
- <i class="fa-solid fa-magnifying-glass cpub-picker-search-icon"></i>
109
- <input
110
- v-model="search"
111
- type="text"
112
- class="cpub-picker-search"
113
- placeholder="Search blocks..."
114
- aria-label="Search block types"
115
- />
116
- </div>
117
- <div class="cpub-picker-body">
118
- <template v-if="filteredBlocks.length > 0">
119
- <button
120
- v-for="(block, i) in filteredBlocks"
121
- :key="block.type + (block.attrs?.variant ?? '')"
122
- :data-block="block.type"
123
- class="cpub-picker-item"
124
- :class="{ 'cpub-picker-item--active': i === selectedIndex }"
125
- @mouseenter="selectedIndex = i"
126
- @click="selectBlock(block)"
127
- >
128
- <span class="cpub-picker-icon"><i :class="['fa-solid', block.icon]"></i></span>
129
- <span class="cpub-picker-text">
130
- <span class="cpub-picker-label">{{ block.label }}</span>
131
- <span v-if="block.description" class="cpub-picker-desc">{{ block.description }}</span>
132
- </span>
133
- </button>
134
- </template>
135
- <div v-else class="cpub-picker-empty">
136
- No blocks match "{{ search }}"
137
- </div>
138
- </div>
139
- </div>
140
- </template>
141
-
142
- <style scoped>
143
- .cpub-picker {
144
- position: absolute;
145
- left: 50%;
146
- transform: translateX(-50%);
147
- z-index: 100;
148
- background: var(--surface);
149
- border: var(--border-width-default) solid var(--border);
150
- box-shadow: var(--shadow-lg);
151
- min-width: 260px;
152
- max-width: 340px;
153
- max-height: 360px;
154
- display: flex;
155
- flex-direction: column;
156
- }
157
-
158
- .cpub-picker-header {
159
- display: flex;
160
- align-items: center;
161
- padding: 8px 10px;
162
- gap: 8px;
163
- border-bottom: var(--border-width-default) solid var(--border);
164
- flex-shrink: 0;
165
- }
166
-
167
- .cpub-picker-search-icon {
168
- font-size: 10px;
169
- color: var(--text-faint);
170
- flex-shrink: 0;
171
- }
172
-
173
- .cpub-picker-search {
174
- background: transparent;
175
- border: none;
176
- outline: none;
177
- font-size: 12px;
178
- color: var(--text);
179
- width: 100%;
180
- font-family: var(--font-sans);
181
- }
182
-
183
- .cpub-picker-search::placeholder {
184
- color: var(--text-faint);
185
- }
186
-
187
- .cpub-picker-body {
188
- overflow-y: auto;
189
- flex: 1;
190
- padding: 4px;
191
- }
192
-
193
- .cpub-picker-item {
194
- display: flex;
195
- align-items: center;
196
- gap: 10px;
197
- width: 100%;
198
- padding: 7px 10px;
199
- background: transparent;
200
- border: none;
201
- text-align: left;
202
- cursor: pointer;
203
- transition: background 0.08s;
204
- color: var(--text);
205
- font-size: 12px;
206
- }
207
-
208
- .cpub-picker-item:hover,
209
- .cpub-picker-item--active {
210
- background: var(--accent-bg);
211
- }
212
-
213
- .cpub-picker-icon {
214
- width: 26px;
215
- height: 26px;
216
- background: var(--surface2);
217
- border: var(--border-width-default) solid var(--border2);
218
- display: flex;
219
- align-items: center;
220
- justify-content: center;
221
- font-size: 10px;
222
- color: var(--text-dim);
223
- flex-shrink: 0;
224
- }
225
-
226
- .cpub-picker-item--active .cpub-picker-icon,
227
- .cpub-picker-item:hover .cpub-picker-icon {
228
- background: var(--accent-bg);
229
- border-color: var(--accent-border);
230
- color: var(--accent);
231
- }
232
-
233
- .cpub-picker-text {
234
- display: flex;
235
- flex-direction: column;
236
- min-width: 0;
237
- }
238
-
239
- .cpub-picker-label {
240
- font-size: 12px;
241
- font-weight: 500;
242
- }
243
-
244
- .cpub-picker-desc {
245
- font-size: 10px;
246
- color: var(--text-faint);
247
- font-family: var(--font-mono);
248
- }
249
-
250
- .cpub-picker-empty {
251
- padding: 16px;
252
- text-align: center;
253
- font-size: 11px;
254
- color: var(--text-faint);
255
- }
256
-
257
- /* Per-block-type icon colors */
258
- [data-block="heading"] .cpub-picker-icon { color: var(--teal); background: color-mix(in srgb, var(--teal) 10%, transparent); }
259
- [data-block="text"] .cpub-picker-icon,
260
- [data-block="paragraph"] .cpub-picker-icon { color: var(--text-dim); background: var(--surface2); }
261
- [data-block="image"] .cpub-picker-icon { color: #38bdf8; background: rgba(56, 189, 248, 0.08); }
262
- [data-block="code"] .cpub-picker-icon,
263
- [data-block="code_block"] .cpub-picker-icon { color: #c084fc; background: rgba(192, 132, 252, 0.08); }
264
- [data-block="callout"] .cpub-picker-icon { color: #fbbf24; background: rgba(251, 191, 36, 0.08); }
265
- [data-block="quote"] .cpub-picker-icon,
266
- [data-block="blockquote"] .cpub-picker-icon { color: #94a3b8; background: rgba(148, 163, 184, 0.08); }
267
- [data-block="embed"] .cpub-picker-icon { color: #f472b6; background: rgba(244, 114, 182, 0.08); }
268
- [data-block="video"] .cpub-picker-icon { color: #fb923c; background: rgba(251, 146, 60, 0.08); }
269
- [data-block="divider"] .cpub-picker-icon,
270
- [data-block="horizontal_rule"] .cpub-picker-icon,
271
- [data-block="horizontalRule"] .cpub-picker-icon { color: var(--text-faint); background: var(--surface2); }
272
- [data-block="gallery"] .cpub-picker-icon { color: #2dd4bf; background: rgba(45, 212, 191, 0.08); }
273
- [data-block="quiz"] .cpub-picker-icon { color: #4ade80; background: rgba(74, 222, 128, 0.08); }
274
- [data-block="slider"] .cpub-picker-icon,
275
- [data-block="interactiveSlider"] .cpub-picker-icon { color: #818cf8; background: rgba(129, 140, 248, 0.08); }
276
- [data-block="math"] .cpub-picker-icon,
277
- [data-block="mathNotation"] .cpub-picker-icon { color: #e879f9; background: rgba(232, 121, 249, 0.08); }
278
- [data-block="markdown"] .cpub-picker-icon { color: var(--text-dim); background: var(--surface2); }
279
- [data-block="buildStep"] .cpub-picker-icon { color: var(--accent); background: var(--accent-bg); }
280
- [data-block="partsList"] .cpub-picker-icon { color: #fb7185; background: rgba(251, 113, 133, 0.08); }
281
- [data-block="toolList"] .cpub-picker-icon { color: #a78bfa; background: rgba(167, 139, 250, 0.08); }
282
- [data-block="downloads"] .cpub-picker-icon { color: #22d3ee; background: rgba(34, 211, 238, 0.08); }
283
- [data-block="sectionHeader"] .cpub-picker-icon { color: var(--accent); background: var(--accent-bg); }
284
- [data-block="checkpoint"] .cpub-picker-icon { color: #34d399; background: rgba(52, 211, 153, 0.08); }
285
- </style>
@@ -1,192 +0,0 @@
1
- <script setup lang="ts">
2
- /**
3
- * Block wrapper — wraps every content block with:
4
- * - Drag handle (left, appears on hover)
5
- * - Block controls (top-right: move, clone, delete)
6
- * - Selected state (accent outline)
7
- * - Click-to-select
8
- */
9
- import type { EditorBlock } from '../../composables/useBlockEditor';
10
-
11
- const props = defineProps<{
12
- block: EditorBlock;
13
- selected: boolean;
14
- }>();
15
-
16
- const emit = defineEmits<{
17
- select: [];
18
- delete: [];
19
- duplicate: [];
20
- 'move-up': [];
21
- 'move-down': [];
22
- 'drag-start': [event: DragEvent];
23
- 'drag-end': [event: DragEvent];
24
- }>();
25
-
26
- function onDragStart(event: DragEvent): void {
27
- event.dataTransfer?.setData('text/plain', props.block.id);
28
- event.dataTransfer!.effectAllowed = 'move';
29
- emit('drag-start', event);
30
- }
31
-
32
- function onDragEnd(event: DragEvent): void {
33
- emit('drag-end', event);
34
- }
35
- </script>
36
-
37
- <template>
38
- <div
39
- class="cpub-block-wrap"
40
- :class="{ 'cpub-block-wrap--selected': selected }"
41
- @click.stop="emit('select')"
42
- >
43
- <!-- Drag handle (left side) -->
44
- <div class="cpub-block-handle">
45
- <button
46
- class="cpub-handle-btn"
47
- title="Drag to reorder"
48
- draggable="true"
49
- @dragstart="onDragStart"
50
- @dragend="onDragEnd"
51
- >
52
- <i class="fa-solid fa-grip-vertical"></i>
53
- </button>
54
- </div>
55
-
56
- <!-- Block controls (top-right, shown on hover) -->
57
- <div class="cpub-block-controls">
58
- <button class="cpub-block-ctrl" title="Move up" @click.stop="emit('move-up')">
59
- <i class="fa-solid fa-arrow-up"></i>
60
- </button>
61
- <button class="cpub-block-ctrl" title="Move down" @click.stop="emit('move-down')">
62
- <i class="fa-solid fa-arrow-down"></i>
63
- </button>
64
- <button class="cpub-block-ctrl" title="Duplicate" @click.stop="emit('duplicate')">
65
- <i class="fa-solid fa-copy"></i>
66
- </button>
67
- <button class="cpub-block-ctrl cpub-block-ctrl--danger" title="Delete" @click.stop="emit('delete')">
68
- <i class="fa-solid fa-trash"></i>
69
- </button>
70
- </div>
71
-
72
- <!-- Block content -->
73
- <div class="cpub-block-inner">
74
- <slot />
75
- </div>
76
- </div>
77
- </template>
78
-
79
- <style scoped>
80
- .cpub-block-wrap {
81
- position: relative;
82
- border: var(--border-width-default) solid transparent;
83
- transition: border-color 0.12s;
84
- }
85
-
86
- .cpub-block-wrap:hover {
87
- border-color: var(--border2);
88
- }
89
-
90
- .cpub-block-wrap--selected {
91
- border-color: var(--accent);
92
- box-shadow: 0 0 0 2px var(--accent-bg);
93
- }
94
-
95
- /* Drag handle — left side */
96
- .cpub-block-handle {
97
- position: absolute;
98
- left: -36px;
99
- top: 50%;
100
- transform: translateY(-50%);
101
- display: flex;
102
- flex-direction: column;
103
- gap: 2px;
104
- opacity: 0;
105
- transition: opacity 0.12s;
106
- }
107
-
108
- .cpub-block-wrap:hover .cpub-block-handle,
109
- .cpub-block-wrap--selected .cpub-block-handle {
110
- opacity: 1;
111
- }
112
-
113
- .cpub-handle-btn {
114
- width: 28px;
115
- height: 28px;
116
- display: flex;
117
- align-items: center;
118
- justify-content: center;
119
- background: var(--surface);
120
- border: var(--border-width-default) solid var(--border2);
121
- color: var(--text-faint);
122
- cursor: grab;
123
- font-size: 11px;
124
- padding: 0;
125
- }
126
-
127
- .cpub-handle-btn:hover {
128
- border-color: var(--border);
129
- color: var(--text-dim);
130
- background: var(--surface2);
131
- }
132
-
133
- .cpub-handle-btn:active {
134
- cursor: grabbing;
135
- }
136
-
137
- /* Block controls — top-right, offset above the block */
138
- .cpub-block-controls {
139
- --ctrl-surface: rgba(255, 255, 255, 0.15);
140
- position: absolute;
141
- top: -30px;
142
- right: 0;
143
- display: flex;
144
- gap: 0;
145
- background: var(--text);
146
- padding: 2px;
147
- opacity: 0;
148
- transition: opacity 0.12s;
149
- z-index: 10;
150
- }
151
-
152
- .cpub-block-wrap:hover .cpub-block-controls,
153
- .cpub-block-wrap--selected .cpub-block-controls {
154
- opacity: 1;
155
- }
156
-
157
- .cpub-block-ctrl {
158
- width: 26px;
159
- height: 26px;
160
- display: flex;
161
- align-items: center;
162
- justify-content: center;
163
- background: transparent;
164
- border: none;
165
- color: var(--surface3);
166
- cursor: pointer;
167
- font-size: 10px;
168
- padding: 0;
169
- transition: background 0.1s, color 0.1s;
170
- }
171
-
172
- .cpub-block-ctrl:hover {
173
- background: var(--ctrl-surface);
174
- color: var(--surface);
175
- }
176
-
177
- .cpub-block-ctrl--danger:hover {
178
- background: var(--red);
179
- color: var(--surface);
180
- }
181
-
182
- /* Block inner */
183
- .cpub-block-inner {
184
- min-height: 20px;
185
- }
186
-
187
- /* Touch devices: only show controls on selected block since there's no hover */
188
- @media (hover: none) {
189
- .cpub-block-wrap--selected .cpub-block-handle { opacity: 1; }
190
- .cpub-block-wrap--selected .cpub-block-controls { opacity: 1; }
191
- }
192
- </style>