@commonpub/layer 0.8.1 → 0.8.2
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.
|
@@ -82,25 +82,77 @@ function scrollToSection(sectionId: string): void {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
// --- Assets ---
|
|
85
|
-
const
|
|
85
|
+
const MAX_CONTENT_UPLOAD_MB = 10;
|
|
86
|
+
const MAX_CONTENT_UPLOAD_BYTES = MAX_CONTENT_UPLOAD_MB * 1024 * 1024;
|
|
87
|
+
|
|
88
|
+
interface UploadedAsset {
|
|
89
|
+
name: string;
|
|
90
|
+
size: string;
|
|
91
|
+
type: 'image' | 'file';
|
|
92
|
+
url: string;
|
|
93
|
+
mimeType: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const uploadedFiles = ref<UploadedAsset[]>([]);
|
|
97
|
+
const uploadError = ref('');
|
|
98
|
+
const uploading = ref(false);
|
|
86
99
|
|
|
87
100
|
function onAssetUpload(event: Event): void {
|
|
88
101
|
const input = event.target as HTMLInputElement;
|
|
89
102
|
if (!input.files?.length) return;
|
|
90
103
|
const file = input.files[0];
|
|
91
104
|
if (!file) return;
|
|
105
|
+
uploadError.value = '';
|
|
106
|
+
|
|
107
|
+
// Client-side size check
|
|
108
|
+
if (file.size > MAX_CONTENT_UPLOAD_BYTES) {
|
|
109
|
+
uploadError.value = `File too large (${(file.size / 1024 / 1024).toFixed(1)} MB). Max ${MAX_CONTENT_UPLOAD_MB} MB.`;
|
|
110
|
+
input.value = '';
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
uploading.value = true;
|
|
92
115
|
const formData = new FormData();
|
|
93
116
|
formData.append('file', file);
|
|
94
117
|
formData.append('purpose', 'content');
|
|
95
|
-
$fetch<{ url: string; originalName: string;
|
|
118
|
+
$fetch<{ url: string; originalName: string; sizeBytes: number; mimeType: string }>('/api/files/upload', { method: 'POST', body: formData })
|
|
96
119
|
.then((res) => {
|
|
97
120
|
uploadedFiles.value.unshift({
|
|
98
121
|
name: res.originalName || file.name,
|
|
99
|
-
size:
|
|
100
|
-
|
|
122
|
+
size: res.sizeBytes < 1024 * 1024
|
|
123
|
+
? `${(res.sizeBytes / 1024).toFixed(0)} KB`
|
|
124
|
+
: `${(res.sizeBytes / 1024 / 1024).toFixed(1)} MB`,
|
|
125
|
+
type: (res.mimeType || file.type).startsWith('image/') ? 'image' : 'file',
|
|
126
|
+
url: res.url,
|
|
127
|
+
mimeType: res.mimeType || file.type,
|
|
101
128
|
});
|
|
129
|
+
uploadError.value = '';
|
|
130
|
+
})
|
|
131
|
+
.catch((err) => {
|
|
132
|
+
uploadError.value = err?.data?.statusMessage || 'Upload failed';
|
|
102
133
|
})
|
|
103
|
-
.
|
|
134
|
+
.finally(() => {
|
|
135
|
+
uploading.value = false;
|
|
136
|
+
input.value = '';
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function insertAsset(asset: UploadedAsset): void {
|
|
141
|
+
if (asset.type === 'image') {
|
|
142
|
+
// Insert image block after selected block (or at end)
|
|
143
|
+
const idx = props.blockEditor.selectedBlockId.value
|
|
144
|
+
? props.blockEditor.getBlockIndex(props.blockEditor.selectedBlockId.value) + 1
|
|
145
|
+
: undefined;
|
|
146
|
+
props.blockEditor.addBlock('image', { url: asset.url, alt: asset.name }, idx);
|
|
147
|
+
} else {
|
|
148
|
+
// Copy URL to clipboard for non-image files
|
|
149
|
+
navigator.clipboard.writeText(asset.url).catch(() => {});
|
|
150
|
+
asset.name = `${asset.name} (copied!)`;
|
|
151
|
+
setTimeout(() => {
|
|
152
|
+
const found = uploadedFiles.value.find((f) => f.url === asset.url);
|
|
153
|
+
if (found) found.name = found.name.replace(' (copied!)', '');
|
|
154
|
+
}, 1500);
|
|
155
|
+
}
|
|
104
156
|
}
|
|
105
157
|
|
|
106
158
|
// --- Cover image ---
|
|
@@ -264,25 +316,34 @@ const canvasMaxWidth = computed(() => {
|
|
|
264
316
|
|
|
265
317
|
<!-- Assets tab -->
|
|
266
318
|
<div v-else class="cpub-ae-left-body">
|
|
267
|
-
<label class="cpub-ae-assets-drop">
|
|
268
|
-
<i class="fa-solid fa-cloud-arrow-up"></i>
|
|
269
|
-
<div class="cpub-ae-assets-drop-label">Drop files here</div>
|
|
270
|
-
<div class="cpub-ae-assets-drop-sub">JPG, PNG, GIF, SVG, PDF</div>
|
|
271
|
-
<input type="file" class="cpub-sr-only" @change="onAssetUpload">
|
|
319
|
+
<label class="cpub-ae-assets-drop" :class="{ 'cpub-ae-assets-uploading': uploading }">
|
|
320
|
+
<i :class="uploading ? 'fa-solid fa-spinner fa-spin' : 'fa-solid fa-cloud-arrow-up'"></i>
|
|
321
|
+
<div class="cpub-ae-assets-drop-label">{{ uploading ? 'Uploading...' : 'Drop files here' }}</div>
|
|
322
|
+
<div class="cpub-ae-assets-drop-sub">JPG, PNG, GIF, SVG, PDF — max {{ MAX_CONTENT_UPLOAD_MB }} MB</div>
|
|
323
|
+
<input type="file" class="cpub-sr-only" :disabled="uploading" @change="onAssetUpload">
|
|
272
324
|
</label>
|
|
325
|
+
<div v-if="uploadError" class="cpub-ae-assets-error">
|
|
326
|
+
<i class="fa-solid fa-triangle-exclamation"></i> {{ uploadError }}
|
|
327
|
+
</div>
|
|
273
328
|
<div v-if="uploadedFiles.length > 0" class="cpub-ae-assets-list">
|
|
274
329
|
<div class="cpub-ae-assets-heading">Recent Uploads</div>
|
|
275
330
|
<div
|
|
276
331
|
v-for="(file, idx) in uploadedFiles"
|
|
277
332
|
:key="idx"
|
|
278
333
|
class="cpub-ae-asset-item"
|
|
334
|
+
:title="file.type === 'image' ? 'Click to insert image' : 'Click to copy URL'"
|
|
335
|
+
@click="insertAsset(file)"
|
|
279
336
|
>
|
|
280
|
-
<
|
|
281
|
-
|
|
337
|
+
<img v-if="file.type === 'image'" :src="file.url" :alt="file.name" class="cpub-ae-asset-thumb" />
|
|
338
|
+
<div v-else class="cpub-ae-asset-icon">
|
|
339
|
+
<i class="fa-solid fa-file" />
|
|
282
340
|
</div>
|
|
283
341
|
<div class="cpub-ae-asset-info">
|
|
284
342
|
<div class="cpub-ae-asset-name">{{ file.name }}</div>
|
|
285
|
-
<div class="cpub-ae-asset-
|
|
343
|
+
<div class="cpub-ae-asset-meta">
|
|
344
|
+
<span class="cpub-ae-asset-size">{{ file.size }}</span>
|
|
345
|
+
<span class="cpub-ae-asset-action">{{ file.type === 'image' ? 'insert' : 'copy url' }}</span>
|
|
346
|
+
</div>
|
|
286
347
|
</div>
|
|
287
348
|
</div>
|
|
288
349
|
</div>
|
|
@@ -505,15 +566,26 @@ const canvasMaxWidth = computed(() => {
|
|
|
505
566
|
transition: border-color 0.15s, background 0.15s; text-align: center;
|
|
506
567
|
}
|
|
507
568
|
.cpub-ae-assets-drop:hover { border-color: var(--accent); background: var(--accent-bg); }
|
|
569
|
+
.cpub-ae-assets-uploading { border-color: var(--accent); background: var(--accent-bg); pointer-events: none; }
|
|
508
570
|
.cpub-ae-assets-drop i { font-size: 20px; color: var(--text-faint); }
|
|
509
571
|
.cpub-ae-assets-drop-label { font-size: 11px; font-weight: 600; color: var(--text-dim); }
|
|
510
572
|
.cpub-ae-assets-drop-sub { font-size: 10px; color: var(--text-faint); font-family: var(--font-mono); }
|
|
573
|
+
.cpub-ae-assets-error {
|
|
574
|
+
margin: 4px 8px 8px; padding: 6px 10px; font-size: 10px; font-family: var(--font-mono);
|
|
575
|
+
color: var(--red); background: var(--red-bg); border: var(--border-width-default) solid var(--red);
|
|
576
|
+
display: flex; align-items: center; gap: 6px;
|
|
577
|
+
}
|
|
511
578
|
.cpub-ae-assets-list { padding: 8px 12px; }
|
|
512
579
|
.cpub-ae-assets-heading { font-family: var(--font-mono); font-size: 10px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); padding: 4px 0 10px; }
|
|
513
580
|
.cpub-ae-asset-item {
|
|
514
581
|
display: flex; align-items: center; gap: 10px; padding: 8px 10px;
|
|
515
582
|
background: var(--surface); border: var(--border-width-default) solid var(--border); cursor: pointer;
|
|
516
|
-
box-shadow: var(--shadow-sm); margin-bottom: 5px;
|
|
583
|
+
box-shadow: var(--shadow-sm); margin-bottom: 5px; transition: background 0.1s, border-color 0.1s;
|
|
584
|
+
}
|
|
585
|
+
.cpub-ae-asset-item:hover { background: var(--surface2); border-color: var(--accent-border); }
|
|
586
|
+
.cpub-ae-asset-thumb {
|
|
587
|
+
width: 34px; height: 34px; object-fit: cover; flex-shrink: 0;
|
|
588
|
+
border: var(--border-width-default) solid var(--border2);
|
|
517
589
|
}
|
|
518
590
|
.cpub-ae-asset-icon {
|
|
519
591
|
width: 34px; height: 34px; background: var(--surface2); display: flex;
|
|
@@ -522,7 +594,12 @@ const canvasMaxWidth = computed(() => {
|
|
|
522
594
|
.cpub-ae-asset-icon i { font-size: 11px; color: var(--text-faint); }
|
|
523
595
|
.cpub-ae-asset-info { flex: 1; min-width: 0; }
|
|
524
596
|
.cpub-ae-asset-name { font-size: 10px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
|
|
597
|
+
.cpub-ae-asset-meta { display: flex; align-items: center; gap: 6px; }
|
|
525
598
|
.cpub-ae-asset-size { font-family: var(--font-mono); font-size: 8px; color: var(--text-faint); }
|
|
599
|
+
.cpub-ae-asset-action {
|
|
600
|
+
font-family: var(--font-mono); font-size: 8px; color: var(--accent);
|
|
601
|
+
text-transform: uppercase; letter-spacing: 0.05em;
|
|
602
|
+
}
|
|
526
603
|
|
|
527
604
|
/* Center */
|
|
528
605
|
.cpub-ae-center { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonpub/layer",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
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/auth": "0.5.0",
|
|
54
|
-
"@commonpub/docs": "0.6.2",
|
|
55
53
|
"@commonpub/editor": "0.7.9",
|
|
56
|
-
"@commonpub/schema": "0.9.6",
|
|
57
54
|
"@commonpub/config": "0.9.1",
|
|
58
|
-
"@commonpub/
|
|
59
|
-
"@commonpub/
|
|
55
|
+
"@commonpub/explainer": "0.7.10",
|
|
56
|
+
"@commonpub/docs": "0.6.2",
|
|
57
|
+
"@commonpub/auth": "0.5.0",
|
|
60
58
|
"@commonpub/ui": "0.8.5",
|
|
59
|
+
"@commonpub/schema": "0.9.6",
|
|
60
|
+
"@commonpub/protocol": "0.9.8",
|
|
61
61
|
"@commonpub/server": "2.28.0",
|
|
62
|
-
"@commonpub/
|
|
62
|
+
"@commonpub/learning": "0.5.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@testing-library/jest-dom": "^6.9.1",
|