@commonpub/layer 0.7.17 → 0.7.19

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.
@@ -128,6 +128,26 @@ function removeCover(): void {
128
128
  updateMeta('coverImageUrl', '');
129
129
  }
130
130
 
131
+ // --- Banner image ---
132
+ const bannerUrl = computed(() => (props.metadata.bannerUrl as string) || '');
133
+
134
+ function onBannerUpload(event: Event): void {
135
+ const input = event.target as HTMLInputElement;
136
+ if (!input.files?.length) return;
137
+ const file = input.files[0];
138
+ if (!file) return;
139
+ const formData = new FormData();
140
+ formData.append('file', file);
141
+ formData.append('purpose', 'cover');
142
+ $fetch<{ url: string }>('/api/files/upload', { method: 'POST', body: formData })
143
+ .then((res) => { updateMeta('bannerUrl', res.url); })
144
+ .catch(() => {});
145
+ }
146
+
147
+ function removeBanner(): void {
148
+ updateMeta('bannerUrl', '');
149
+ }
150
+
131
151
  // --- SEO preview ---
132
152
  const seoDomain = computed(() => {
133
153
  try { return new URL(useRequestURL().origin).hostname; } catch { return 'example.com'; }
@@ -136,7 +156,7 @@ const seoPreviewDesc = computed(() => (props.metadata.seoDescription as string)
136
156
 
137
157
  // --- Right panel ---
138
158
  const openSections = ref<Record<string, boolean>>({
139
- content: true, seo: false, publishing: true, cover: false,
159
+ content: true, seo: false, publishing: true, cover: false, banner: false,
140
160
  });
141
161
  function toggleSection(key: string): void {
142
162
  openSections.value[key] = !openSections.value[key];
@@ -347,6 +367,27 @@ const canvasMaxWidth = computed(() => {
347
367
  </div>
348
368
  </EditorSection>
349
369
 
370
+ <!-- Banner Image -->
371
+ <EditorSection title="Banner Image" icon="fa-panorama" :open="openSections.banner" @toggle="toggleSection('banner')">
372
+ <p class="cpub-ep-hint" style="margin-bottom: 8px;">Hero background at the top of the page. Falls back to your profile banner if not set.</p>
373
+ <div v-if="bannerUrl" class="cpub-ae-banner-preview">
374
+ <img :src="bannerUrl" alt="Banner" class="cpub-ae-banner-img" />
375
+ <div class="cpub-ae-banner-actions">
376
+ <button class="cpub-ae-cover-btn" @click="removeBanner"><i class="fa-solid fa-trash"></i> Remove</button>
377
+ <label class="cpub-ae-cover-btn">
378
+ <i class="fa-solid fa-arrow-up-from-bracket"></i> Replace
379
+ <input type="file" accept="image/*" class="cpub-sr-only" @change="onBannerUpload">
380
+ </label>
381
+ </div>
382
+ </div>
383
+ <div v-else>
384
+ <label class="cpub-ae-cover-btn primary" style="display: inline-flex;">
385
+ <i class="fa-solid fa-arrow-up-from-bracket"></i> Upload Banner
386
+ <input type="file" accept="image/*" class="cpub-sr-only" @change="onBannerUpload">
387
+ </label>
388
+ </div>
389
+ </EditorSection>
390
+
350
391
  <!-- SEO -->
351
392
  <EditorSection title="SEO Preview" icon="fa-brands fa-google" :open="openSections.seo" @toggle="toggleSection('seo')">
352
393
  <div class="cpub-seo-card">
@@ -583,4 +624,9 @@ const canvasMaxWidth = computed(() => {
583
624
  }
584
625
  .cpub-seo-title { font-size: 14px; color: var(--accent); font-weight: 500; margin-bottom: 2px; }
585
626
  .cpub-seo-desc { font-size: 11px; color: var(--text-dim); line-height: 1.5; }
627
+
628
+ /* Banner preview */
629
+ .cpub-ae-banner-preview { position: relative; margin-bottom: 8px; }
630
+ .cpub-ae-banner-img { width: 100%; height: 80px; object-fit: cover; display: block; border: var(--border-width-default) solid var(--border); }
631
+ .cpub-ae-banner-actions { display: flex; gap: 6px; margin-top: 6px; }
586
632
  </style>
@@ -45,7 +45,7 @@ const blockTypes: BlockTypeGroup[] = [
45
45
  ];
46
46
 
47
47
  const openSections = ref<Record<string, boolean>>({
48
- meta: true, excerpt: true, seo: true, publishing: true, author: true, social: false,
48
+ meta: true, excerpt: true, banner: false, seo: true, publishing: true, author: true, social: false,
49
49
  });
50
50
  function toggleSection(key: string): void {
51
51
  openSections.value[key] = !openSections.value[key];
@@ -80,6 +80,26 @@ function removeCover(): void {
80
80
  updateMeta('coverImageUrl', '');
81
81
  }
82
82
 
83
+ // --- Banner image ---
84
+ const bannerUrl = computed(() => (props.metadata.bannerUrl as string) || '');
85
+
86
+ function onBannerUpload(event: Event): void {
87
+ const input = event.target as HTMLInputElement;
88
+ if (!input.files?.length) return;
89
+ const file = input.files[0];
90
+ if (!file) return;
91
+ const formData = new FormData();
92
+ formData.append('file', file);
93
+ formData.append('purpose', 'cover');
94
+ $fetch<{ url: string }>('/api/files/upload', { method: 'POST', body: formData })
95
+ .then((res) => { updateMeta('bannerUrl', res.url); })
96
+ .catch(() => {});
97
+ }
98
+
99
+ function removeBanner(): void {
100
+ updateMeta('bannerUrl', '');
101
+ }
102
+
83
103
  // --- Word count ---
84
104
  const wordCount = computed(() => {
85
105
  let count = 0;
@@ -289,6 +309,27 @@ const canvasMaxWidth = computed(() => {
289
309
  </div>
290
310
  </EditorSection>
291
311
 
312
+ <!-- Banner Image -->
313
+ <EditorSection title="Banner Image" icon="fa-panorama" :open="openSections.banner" @toggle="toggleSection('banner')">
314
+ <p class="cpub-ep-hint" style="margin-bottom: 8px;">Hero background at the top of the page. Falls back to your profile banner if not set.</p>
315
+ <div v-if="bannerUrl" class="cpub-be-banner-preview">
316
+ <img :src="bannerUrl" alt="Banner" class="cpub-be-banner-img" />
317
+ <div class="cpub-be-banner-actions">
318
+ <button class="cpub-be-cover-btn" @click="removeBanner"><i class="fa-solid fa-trash"></i> Remove</button>
319
+ <label class="cpub-be-cover-btn">
320
+ <i class="fa-solid fa-arrow-up-from-bracket"></i> Replace
321
+ <input type="file" accept="image/*" class="cpub-sr-only" @change="onBannerUpload">
322
+ </label>
323
+ </div>
324
+ </div>
325
+ <div v-else>
326
+ <label class="cpub-be-cover-btn primary" style="display: inline-flex;">
327
+ <i class="fa-solid fa-arrow-up-from-bracket"></i> Upload Banner
328
+ <input type="file" accept="image/*" class="cpub-sr-only" @change="onBannerUpload">
329
+ </label>
330
+ </div>
331
+ </EditorSection>
332
+
292
333
  <!-- SEO Preview -->
293
334
  <EditorSection title="SEO Preview" icon="fa-brands fa-google" :open="openSections.seo" @toggle="toggleSection('seo')">
294
335
  <div class="cpub-be-seo-card">
@@ -580,4 +621,9 @@ const canvasMaxWidth = computed(() => {
580
621
  .cpub-be-cover-actions { opacity: 1; }
581
622
  .cpub-be-og-overlay { opacity: 1; }
582
623
  }
624
+
625
+ /* Banner preview */
626
+ .cpub-be-banner-preview { position: relative; margin-bottom: 8px; }
627
+ .cpub-be-banner-img { width: 100%; height: 80px; object-fit: cover; display: block; border: var(--border-width-default) solid var(--border); }
628
+ .cpub-be-banner-actions { display: flex; gap: 6px; margin-top: 6px; }
583
629
  </style>
@@ -52,7 +52,7 @@ const seoDomain = computed(() => {
52
52
  const seoPreviewDesc = computed(() => (props.metadata.seoDescription as string) || (props.metadata.description as string) || '');
53
53
 
54
54
  const openSections = ref<Record<string, boolean>>({
55
- meta: true, tags: true, visibility: true, cover: false, seo: false, checklist: true,
55
+ meta: true, tags: true, visibility: true, cover: false, banner: false, seo: false, checklist: true,
56
56
  });
57
57
  function toggleSection(key: string): void {
58
58
  openSections.value[key] = !openSections.value[key];
@@ -85,6 +85,26 @@ function removeCover(): void {
85
85
  updateMeta('coverImageUrl', '');
86
86
  }
87
87
 
88
+ // --- Banner image ---
89
+ const bannerUrl = computed(() => (props.metadata.bannerUrl as string) || '');
90
+
91
+ function onBannerUpload(event: Event): void {
92
+ const input = event.target as HTMLInputElement;
93
+ if (!input.files?.length) return;
94
+ const file = input.files[0];
95
+ if (!file) return;
96
+ const formData = new FormData();
97
+ formData.append('file', file);
98
+ formData.append('purpose', 'cover');
99
+ $fetch<{ url: string }>('/api/files/upload', { method: 'POST', body: formData })
100
+ .then((res) => { updateMeta('bannerUrl', res.url); })
101
+ .catch(() => {});
102
+ }
103
+
104
+ function removeBanner(): void {
105
+ updateMeta('bannerUrl', '');
106
+ }
107
+
88
108
  const tags = computed(() => (props.metadata.tags as string[]) || []);
89
109
  function onTagsUpdate(newTags: string[]): void { updateMeta('tags', newTags); }
90
110
  const visibility = computed(() => (props.metadata.visibility as string) || 'public');
@@ -297,6 +317,27 @@ const blockCount = computed(() => props.blockEditor.blocks.value.length);
297
317
  </div>
298
318
  </EditorSection>
299
319
 
320
+ <!-- Banner Image -->
321
+ <EditorSection title="Banner Image" icon="fa-panorama" :open="openSections.banner" @toggle="toggleSection('banner')">
322
+ <p class="cpub-pe-hint" style="margin-bottom: 8px;">Hero background at the top of the page. Falls back to your profile banner if not set.</p>
323
+ <div v-if="bannerUrl" class="cpub-pe-banner-preview">
324
+ <img :src="bannerUrl" alt="Banner" class="cpub-pe-banner-img" />
325
+ <div class="cpub-pe-banner-actions">
326
+ <button class="cpub-pe-cover-btn" @click="removeBanner"><i class="fa-solid fa-trash"></i> Remove</button>
327
+ <label class="cpub-pe-cover-btn">
328
+ <i class="fa-solid fa-arrow-up-from-bracket"></i> Replace
329
+ <input type="file" accept="image/*" class="cpub-sr-only" @change="onBannerUpload">
330
+ </label>
331
+ </div>
332
+ </div>
333
+ <div v-else>
334
+ <label class="cpub-pe-cover-btn primary" style="display: inline-flex;">
335
+ <i class="fa-solid fa-arrow-up-from-bracket"></i> Upload Banner
336
+ <input type="file" accept="image/*" class="cpub-sr-only" @change="onBannerUpload">
337
+ </label>
338
+ </div>
339
+ </EditorSection>
340
+
300
341
  <EditorSection title="SEO Preview" icon="fa-brands fa-google" :open="openSections.seo" @toggle="toggleSection('seo')">
301
342
  <div class="cpub-seo-card">
302
343
  <div class="cpub-seo-url">
@@ -497,4 +538,9 @@ const blockCount = computed(() => props.blockEditor.blocks.value.length);
497
538
  }
498
539
  .cpub-seo-title { font-size: 14px; color: var(--accent); font-weight: 500; margin-bottom: 2px; }
499
540
  .cpub-seo-desc { font-size: 11px; color: var(--text-dim); line-height: 1.5; }
541
+
542
+ /* Banner preview */
543
+ .cpub-pe-banner-preview { position: relative; margin-bottom: 8px; }
544
+ .cpub-pe-banner-img { width: 100%; height: 80px; object-fit: cover; display: block; border: var(--border-width-default) solid var(--border); }
545
+ .cpub-pe-banner-actions { display: flex; gap: 6px; margin-top: 6px; }
500
546
  </style>
@@ -317,10 +317,14 @@ useJsonLd({
317
317
  }
318
318
 
319
319
  /* ── ARTICLE WRAP ── */
320
+ .cpub-article-view {
321
+ overflow-x: clip;
322
+ }
323
+
320
324
  .cpub-article-wrap {
321
325
  max-width: 720px;
322
326
  margin: 0 auto;
323
- padding: 40px 24px 80px;
327
+ padding: 40px clamp(12px, 4vw, 24px) 80px;
324
328
  }
325
329
 
326
330
  /* ── TYPE BADGE ── */
@@ -771,6 +775,13 @@ useJsonLd({
771
775
  gap: 6px;
772
776
  }
773
777
 
778
+ /* Global overflow protection for content */
779
+ .cpub-article-wrap :deep(img),
780
+ .cpub-article-wrap :deep(video),
781
+ .cpub-article-wrap :deep(iframe) { max-width: 100%; height: auto; }
782
+ .cpub-article-wrap :deep(pre) { overflow-x: auto; -webkit-overflow-scrolling: touch; }
783
+ .cpub-article-wrap { overflow-wrap: break-word; word-break: break-word; }
784
+
774
785
  /* ── COVER PHOTO (in-body) ── */
775
786
  .cpub-cover-photo {
776
787
  margin-bottom: 24px;
@@ -784,7 +795,7 @@ useJsonLd({
784
795
 
785
796
  /* ── RESPONSIVE ── */
786
797
  @media (max-width: 768px) {
787
- .cpub-article-wrap { padding: 24px 16px 48px; }
798
+ .cpub-article-wrap { padding-top: 24px; padding-bottom: 48px; }
788
799
  .cpub-article-title { font-size: 22px; }
789
800
  .cpub-article-lead { font-size: 14px; margin-bottom: 16px; }
790
801
  .cpub-related-grid { grid-template-columns: 1fr 1fr; }
@@ -795,7 +806,7 @@ useJsonLd({
795
806
  }
796
807
 
797
808
  @media (max-width: 480px) {
798
- .cpub-article-wrap { padding: 16px 12px 40px; }
809
+ .cpub-article-wrap { padding-top: 16px; padding-bottom: 40px; }
799
810
  .cpub-article-title { font-size: 20px; }
800
811
  .cpub-article-lead { font-size: 13px; }
801
812
  .cpub-related-grid { grid-template-columns: 1fr; }
@@ -216,12 +216,24 @@ const hasSeries = computed(() => !!seriesTitle.value && seriesTotalParts.value >
216
216
  }
217
217
 
218
218
  /* ── BLOG WRAP ── */
219
+ .cpub-blog-view {
220
+ overflow-x: clip;
221
+ }
222
+
219
223
  .cpub-blog-wrap {
220
224
  max-width: 720px;
221
225
  margin: 0 auto;
222
- padding: 40px 24px 80px;
226
+ padding: 40px clamp(12px, 4vw, 24px) 80px;
227
+ overflow-wrap: break-word;
228
+ word-break: break-word;
223
229
  }
224
230
 
231
+ /* Global overflow protection for content */
232
+ .cpub-blog-wrap :deep(img),
233
+ .cpub-blog-wrap :deep(video),
234
+ .cpub-blog-wrap :deep(iframe) { max-width: 100%; height: auto; }
235
+ .cpub-blog-wrap :deep(pre) { overflow-x: auto; -webkit-overflow-scrolling: touch; }
236
+
225
237
  /* ── TYPE BADGE ── */
226
238
  .cpub-content-type-badge {
227
239
  display: inline-flex;
@@ -702,7 +714,7 @@ const hasSeries = computed(() => !!seriesTitle.value && seriesTotalParts.value >
702
714
 
703
715
  /* ── RESPONSIVE ── */
704
716
  @media (max-width: 768px) {
705
- .cpub-blog-wrap { padding: 24px 16px 48px; }
717
+ .cpub-blog-wrap { padding-top: 24px; padding-bottom: 48px; }
706
718
  .cpub-blog-title { font-size: 22px; }
707
719
  .cpub-engagement-row { flex-wrap: wrap; gap: 6px; }
708
720
  .cpub-engage-btn { padding: 8px 12px; min-height: 36px; }
@@ -713,7 +725,7 @@ const hasSeries = computed(() => !!seriesTitle.value && seriesTotalParts.value >
713
725
  }
714
726
 
715
727
  @media (max-width: 480px) {
716
- .cpub-blog-wrap { padding: 16px 12px 40px; }
728
+ .cpub-blog-wrap { padding-top: 16px; padding-bottom: 40px; }
717
729
  .cpub-blog-title { font-size: 20px; }
718
730
  .cpub-blog-lead { font-size: 13px; }
719
731
  .cpub-author-card { flex-direction: column; gap: 12px; }
@@ -591,8 +591,8 @@ async function handleBuild(): Promise<void> {
591
591
  <style scoped>
592
592
  /* ── HERO COVER ── */
593
593
  .cpub-project-view {
594
- overflow-x: hidden;
595
- width: 100%;
594
+ /* Prevent any child from creating horizontal scroll */
595
+ overflow-x: clip;
596
596
  }
597
597
 
598
598
  .cpub-hero-cover {
@@ -680,7 +680,7 @@ async function handleBuild(): Promise<void> {
680
680
  .cpub-page-outer {
681
681
  max-width: 1160px;
682
682
  margin: 0 auto;
683
- padding: 0 32px;
683
+ padding: 0 clamp(12px, 3vw, 32px);
684
684
  }
685
685
 
686
686
  /* ── BREADCRUMB ── */
@@ -925,16 +925,19 @@ async function handleBuild(): Promise<void> {
925
925
  /* ── CONTENT GRID ── */
926
926
  .cpub-content-grid {
927
927
  display: grid;
928
- grid-template-columns: 1fr 260px;
929
- gap: 32px;
928
+ grid-template-columns: minmax(0, 1fr) 260px;
929
+ gap: clamp(16px, 3vw, 32px);
930
930
  align-items: start;
931
931
  padding-bottom: 64px;
932
932
  }
933
933
  .cpub-content-grid.cpub-has-toc {
934
- grid-template-columns: 200px 1fr 260px;
934
+ grid-template-columns: 200px minmax(0, 1fr) 260px;
935
935
  }
936
936
 
937
- .cpub-content-col {
937
+ /* Prevent grid children from overflowing */
938
+ .cpub-content-col,
939
+ .cpub-sidebar,
940
+ .cpub-toc-col {
938
941
  min-width: 0;
939
942
  overflow-wrap: break-word;
940
943
  }
@@ -960,6 +963,20 @@ async function handleBuild(): Promise<void> {
960
963
  word-break: break-word;
961
964
  }
962
965
 
966
+ /* Prevent images and media from overflowing prose */
967
+ .cpub-prose :deep(img),
968
+ .cpub-prose :deep(video),
969
+ .cpub-prose :deep(iframe) {
970
+ max-width: 100%;
971
+ height: auto;
972
+ }
973
+
974
+ /* Code blocks scroll horizontally instead of overflowing */
975
+ .cpub-prose :deep(pre) {
976
+ overflow-x: auto;
977
+ -webkit-overflow-scrolling: touch;
978
+ }
979
+
963
980
  .cpub-prose :deep(h2),
964
981
  .cpub-prose :deep(.section-title) {
965
982
  font-size: 16px;
@@ -1525,48 +1542,53 @@ async function handleBuild(): Promise<void> {
1525
1542
  }
1526
1543
 
1527
1544
  /* ── RESPONSIVE ── */
1545
+
1546
+ /* 1200px: Drop left TOC column, keep sidebar */
1528
1547
  @media (max-width: 1200px) {
1529
1548
  .cpub-content-grid.cpub-has-toc {
1530
- grid-template-columns: 1fr 260px;
1549
+ grid-template-columns: minmax(0, 1fr) 260px;
1531
1550
  }
1532
1551
  .cpub-toc-col { display: none; }
1533
1552
  }
1553
+
1554
+ /* 1024px: Single column — sidebar stacks below content */
1534
1555
  @media (max-width: 1024px) {
1535
1556
  .cpub-content-grid,
1536
1557
  .cpub-content-grid.cpub-has-toc {
1537
- grid-template-columns: 1fr;
1558
+ grid-template-columns: minmax(0, 1fr);
1538
1559
  }
1539
1560
  .cpub-sidebar { position: static; }
1540
1561
  .cpub-toc-col { display: none; }
1541
1562
  }
1542
1563
 
1564
+ /* 768px: Tablet — reduce sizes, wrap engagement, scroll tabs */
1543
1565
  @media (max-width: 768px) {
1544
- .cpub-page-outer { padding: 0 16px; }
1545
1566
  .cpub-hero-cover { height: 200px; }
1546
1567
  .cpub-project-title { font-size: 18px; }
1547
1568
  .cpub-project-desc { font-size: 13px; }
1548
- .cpub-tabs-strip { padding: 0 16px; overflow-x: auto; -webkit-overflow-scrolling: touch; }
1549
- .cpub-tabs-inner { max-width: none; padding: 0; }
1569
+ .cpub-tabs-strip { overflow-x: auto; -webkit-overflow-scrolling: touch; }
1570
+ .cpub-tabs-inner { max-width: none; padding: 0 clamp(12px, 3vw, 32px); }
1550
1571
  .cpub-tab { padding: 10px 12px; font-size: 11px; white-space: nowrap; }
1551
1572
  .cpub-engagement-row { flex-wrap: wrap; gap: 6px; }
1552
1573
  .cpub-engage-btn { padding: 8px 12px; min-height: 36px; }
1553
1574
  .cpub-engage-sep { display: none; }
1554
- .cpub-content-area { padding: 0 16px; }
1555
1575
  .cpub-sidebar { position: static; }
1556
1576
  .cpub-author-tags .cpub-author-tag { padding: 2px 8px; font-size: 10px; }
1577
+ .cpub-author-row { flex-wrap: wrap; gap: 8px; }
1578
+ .cpub-content-grid { padding-bottom: 32px; }
1557
1579
  }
1558
1580
 
1581
+ /* 480px: Phone — compact everything */
1559
1582
  @media (max-width: 480px) {
1560
- .cpub-page-outer { padding: 0 12px; }
1561
1583
  .cpub-hero-cover { height: 160px; }
1562
1584
  .cpub-project-title { font-size: 16px; }
1563
1585
  .cpub-project-desc { font-size: 12px; line-height: 1.5; }
1564
- .cpub-project-meta { padding: 16px 0 0; }
1565
- .cpub-breadcrumbs { font-size: 10px; padding: 10px 0 8px; }
1586
+ .cpub-project-meta { padding: 12px 0 0; }
1587
+ .cpub-breadcrumb { font-size: 10px; padding: 10px 0 8px; }
1566
1588
  .cpub-tab { padding: 8px 10px; font-size: 10px; }
1567
1589
  .cpub-engage-btn { font-size: 11px; padding: 8px 10px; }
1568
- .cpub-author-row { gap: 8px; }
1569
1590
  .cpub-author-detail { font-size: 10px; }
1570
1591
  .cpub-toc-item { padding: 6px 0 6px 10px; font-size: 11px; min-height: 36px; display: flex; align-items: center; }
1592
+ .cpub-content-grid { gap: 12px; padding-bottom: 24px; }
1571
1593
  }
1572
1594
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonpub/layer",
3
- "version": "0.7.17",
3
+ "version": "0.7.19",
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/editor": "0.7.5",
54
+ "@commonpub/docs": "0.6.2",
53
55
  "@commonpub/auth": "0.5.0",
54
56
  "@commonpub/config": "0.9.0",
55
- "@commonpub/docs": "0.6.2",
56
- "@commonpub/learning": "0.5.0",
57
57
  "@commonpub/explainer": "0.7.6",
58
- "@commonpub/editor": "0.7.5",
59
- "@commonpub/ui": "0.8.5",
58
+ "@commonpub/learning": "0.5.0",
60
59
  "@commonpub/server": "2.27.7",
61
60
  "@commonpub/protocol": "0.9.7",
61
+ "@commonpub/ui": "0.8.5",
62
62
  "@commonpub/schema": "0.9.5"
63
63
  },
64
64
  "devDependencies": {
@@ -41,6 +41,7 @@ const metadata = ref<Record<string, unknown>>({
41
41
  tags: [],
42
42
  visibility: 'public',
43
43
  coverImageUrl: '',
44
+ bannerUrl: '',
44
45
  ...(hubFromQuery ? { hubSlug: hubFromQuery } : {}),
45
46
  });
46
47
  const isDirty = ref(false);
@@ -147,6 +148,7 @@ if (!isNew.value) {
147
148
  tags: d.tags ? (d.tags as { name: string }[]).map((t) => t.name) : [],
148
149
  visibility: (d.visibility as string) || 'public',
149
150
  coverImageUrl: (d.coverImageUrl as string) || '',
151
+ bannerUrl: (d.bannerUrl as string) || '',
150
152
  seoDescription: (d.seoDescription as string) || '',
151
153
  difficulty: (d.difficulty as string) || '',
152
154
  buildTime: (d.buildTime as string) || '',