@xleddyl/nuxt-cms 0.1.13 → 0.1.15

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.
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xleddyl/nuxt-cms",
3
3
  "configKey": "cms",
4
- "version": "0.1.13",
4
+ "version": "0.1.15",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -557,7 +557,7 @@ const module$1 = defineNuxtModule({
557
557
  ` dialect: '${dialect}',`,
558
558
  ` schema: '${toPosix(schemaTemplate.dst)}',`,
559
559
  ` out: '${toPosix(migrationsDir)}',`,
560
- driver === "postgres" ? ` dbCredentials: { url: process.env.NUXT_CMS_DATABASE_URL ?? '${databaseUrl}' },` : driver === "libsql" ? ` dbCredentials: { url: process.env.NUXT_CMS_DATABASE_URL ?? '${databaseUrl || `file:${toPosix(resolvedDbPath)}`}', authToken: process.env.NUXT_CMS_DATABASE_AUTH_TOKEN ?? '${databaseAuthToken}' || undefined },` : ` dbCredentials: { url: '${toPosix(resolvedDbPath)}' },`,
560
+ driver === "postgres" ? ` dbCredentials: { url: process.env.NUXT_CMS_DATABASE_URL ?? '${databaseUrl}' },` : driver === "libsql" ? ` dbCredentials: { url: process.env.NUXT_CMS_DATABASE_URL ?? '${databaseUrl || `file:${toPosix(resolvedDbPath)}`}', authToken: (process.env.NUXT_CMS_DATABASE_AUTH_TOKEN ?? '${databaseAuthToken}') || undefined },` : ` dbCredentials: { url: '${toPosix(resolvedDbPath)}' },`,
561
561
  `}`,
562
562
  ``
563
563
  ].join("\n")
@@ -32,7 +32,7 @@
32
32
  import { useCmsConfirmState } from "../../composables/cms-confirm";
33
33
  import { CMS_MODAL_UI } from "../../utils/ui";
34
34
  const state = useCmsConfirmState();
35
- const ui = { ...CMS_MODAL_UI, content: "rounded-2xl shadow-xl sm:max-w-md" };
35
+ const ui = { ...CMS_MODAL_UI, content: "rounded-2xl sm:max-w-md" };
36
36
  function finish(value) {
37
37
  const resolve = state.value.resolve;
38
38
  if (!resolve) return;
@@ -18,11 +18,11 @@
18
18
  class="w-full"
19
19
  />
20
20
  <CmsSlugField v-else-if="field.type === 'slug'" v-model="strOrNull" :source="slugSource" />
21
- <CmsSelect
21
+ <CmsSelectMenu
22
22
  v-else-if="field.type === 'select'"
23
- v-model="sel"
24
- :items="field.options"
25
- :required="field.required"
23
+ v-model="selValue"
24
+ :items="selectItems"
25
+ placeholder="Select…"
26
26
  size="lg"
27
27
  class="w-full"
28
28
  />
@@ -57,11 +57,15 @@
57
57
  <script setup>
58
58
  import { isTranslatableField } from "#nuxt-cms";
59
59
  import { computed } from "#imports";
60
- defineProps({
60
+ const props = defineProps({
61
61
  field: { type: Object, required: true },
62
62
  slugSource: { type: [String, null], required: false }
63
63
  });
64
64
  const model = defineModel({ type: null, ...{ required: true } });
65
+ const selectItems = computed(() => [
66
+ ...props.field.required ? [] : [{ label: "\u2014", value: null }],
67
+ ...(props.field.options ?? []).map((option) => ({ label: option, value: option }))
68
+ ]);
65
69
  function proxy(fromModel, toModel = (v) => v) {
66
70
  return computed({
67
71
  get: () => fromModel(model.value),
@@ -75,10 +79,7 @@ const str = proxy(
75
79
  (v) => v === "" ? null : v
76
80
  );
77
81
  const strOrNull = proxy((v) => v);
78
- const sel = proxy(
79
- (v) => v ?? void 0,
80
- (v) => v ?? null
81
- );
82
+ const selValue = proxy((v) => v ?? null);
82
83
  const num = proxy(
83
84
  (v) => v ?? void 0,
84
85
  (v) => typeof v === "number" && !Number.isNaN(v) ? v : null
@@ -1,10 +1,10 @@
1
1
  <template>
2
2
  <div class="flex flex-col gap-1.5">
3
- <label v-if="label" :class="ui?.label ?? 'text-[13px] font-medium text-(--ui-text-toned)'">
3
+ <label v-if="label" :class="ui?.label ?? 'text-sm font-medium text-(--ui-text-toned)'">
4
4
  {{ label }}<span v-if="required" class="text-(--ui-error)"> *</span>
5
5
  </label>
6
6
  <slot />
7
- <p v-if="error" class="text-xs text-(--ui-error)">{{ error }}</p>
7
+ <p v-if="error" class="text-sm text-(--ui-error)">{{ error }}</p>
8
8
  </div>
9
9
  </template>
10
10
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <component :is="icon" v-if="icon" />
2
+ <component :is="icon" v-if="icon" class="size-5 stroke-[1.5]" />
3
3
  </template>
4
4
 
5
5
  <script setup>
@@ -9,7 +9,7 @@
9
9
  @blur="commit"
10
10
  @update:model-value="invalid = false"
11
11
  />
12
- <p v-if="invalid" class="text-xs text-(--ui-error)">Invalid JSON</p>
12
+ <p v-if="invalid" class="text-sm text-(--ui-error)">Invalid JSON</p>
13
13
  </div>
14
14
  </template>
15
15
 
@@ -16,7 +16,7 @@
16
16
  />
17
17
  <div class="flex items-center gap-2.5 px-4 py-3">
18
18
  <CmsIcon :name="icon" class="size-4 shrink-0 text-(--ui-text-dimmed)" />
19
- <span class="min-w-0 flex-1 truncate text-[13px] font-medium" :title="model">{{
19
+ <span class="min-w-0 flex-1 truncate text-sm font-medium" :title="model">{{
20
20
  mediaFilename(model)
21
21
  }}</span>
22
22
  <CmsButton
@@ -42,7 +42,7 @@
42
42
 
43
43
  <button v-else type="button" class="cms-dropzone is-dense" @click="openGallery">
44
44
  <CmsIcon name="photo" class="size-5 shrink-0" />
45
- <span class="text-[13px] font-medium">Choose from the gallery</span>
45
+ <span class="text-sm font-medium">Choose from the gallery</span>
46
46
  </button>
47
47
 
48
48
  <CmsModal v-model:open="galleryOpen" title="Media" :ui="CMS_MODAL_UI">
@@ -59,7 +59,7 @@
59
59
  :type="selectable ? 'button' : void 0"
60
60
  class="cms-card group overflow-hidden text-left"
61
61
  :class="{
62
- 'hover:border-(--cms-fern) hover:shadow-md': selectable
62
+ 'hover:border-(--cms-line-strong)': selectable
63
63
  }"
64
64
  @click="selectable && emit('select', { key: item.key, url: item.url })"
65
65
  >
@@ -93,7 +93,7 @@
93
93
  size="xs"
94
94
  color="neutral"
95
95
  variant="solid"
96
- class="rounded-full shadow-sm"
96
+ class="rounded-full"
97
97
  aria-label="Edit details"
98
98
  @click="openEdit(item)"
99
99
  />
@@ -102,7 +102,7 @@
102
102
  size="xs"
103
103
  color="neutral"
104
104
  variant="solid"
105
- class="rounded-full shadow-sm"
105
+ class="rounded-full"
106
106
  aria-label="Copy URL"
107
107
  @click="copy(item)"
108
108
  />
@@ -111,14 +111,14 @@
111
111
  size="xs"
112
112
  color="error"
113
113
  variant="solid"
114
- class="rounded-full shadow-sm"
114
+ class="rounded-full"
115
115
  aria-label="Delete"
116
116
  @click="remove(item)"
117
117
  />
118
118
  </div>
119
119
  </div>
120
120
  <div class="flex flex-col gap-1 px-3.5 py-2.5">
121
- <div class="truncate text-[13px] font-medium" :title="item.key">
121
+ <div class="truncate text-sm font-medium" :title="item.key">
122
122
  {{ mediaFilename(item.key) }}
123
123
  </div>
124
124
  <div class="cms-kicker">
@@ -23,7 +23,7 @@
23
23
  class="size-5 shrink-0"
24
24
  :class="{ 'animate-spin': uploading }"
25
25
  />
26
- <span class="text-[13px] font-medium">
26
+ <span class="text-sm font-medium">
27
27
  {{
28
28
  uploading ? `Uploading ${Math.min(done + 1, total)} of ${total}\u2026` : multiple ? "Drop files here or click to browse" : "Drop a file here or click to browse"
29
29
  }}
@@ -8,6 +8,7 @@ type __VLS_Props = {
8
8
  ignoreFilter?: boolean;
9
9
  loading?: boolean;
10
10
  size?: string;
11
+ placeholder?: string;
11
12
  };
12
13
  type __VLS_ModelProps = {
13
14
  modelValue?: string | string[] | null;
@@ -15,11 +15,11 @@
15
15
  </template>
16
16
  <input
17
17
  ref="input"
18
- class="min-w-16 flex-1 bg-transparent outline-none"
19
- :value="search"
18
+ class="min-w-16 flex-1 bg-transparent text-(--ui-text-highlighted) outline-none"
19
+ :value="displayValue"
20
20
  :placeholder="placeholder"
21
- @input="search = $event.target.value"
22
- @focus="open = true"
21
+ @input="onInput"
22
+ @focus="onFocus"
23
23
  />
24
24
  <CmsIcon
25
25
  v-if="loading"
@@ -29,7 +29,7 @@
29
29
  </div>
30
30
  <div v-if="open" class="cms-selectmenu-panel">
31
31
  <button
32
- v-for="item in items"
32
+ v-for="item in visibleItems"
33
33
  :key="String(item[valueKey])"
34
34
  type="button"
35
35
  class="cms-menu-item"
@@ -37,12 +37,12 @@
37
37
  >
38
38
  <span class="truncate">{{ item.label }}</span>
39
39
  <CmsIcon
40
- v-if="isSelected(item[valueKey])"
40
+ v-if="!multiple && isSelected(item[valueKey])"
41
41
  name="check"
42
42
  class="cms-menu-check size-3.5"
43
43
  />
44
44
  </button>
45
- <div v-if="!items.length" class="cms-menu-item pointer-events-none opacity-60">
45
+ <div v-if="!visibleItems.length" class="cms-menu-item pointer-events-none opacity-60">
46
46
  No results
47
47
  </div>
48
48
  </div>
@@ -57,7 +57,8 @@ const props = defineProps({
57
57
  multiple: { type: Boolean, required: false },
58
58
  ignoreFilter: { type: Boolean, required: false },
59
59
  loading: { type: Boolean, required: false },
60
- size: { type: String, required: false }
60
+ size: { type: String, required: false },
61
+ placeholder: { type: String, required: false }
61
62
  });
62
63
  const model = defineModel({ type: [String, Array, null] });
63
64
  const search = defineModel("searchTerm", { type: String, ...{ default: "" } });
@@ -72,7 +73,33 @@ const selectedLabel = computed(() => {
72
73
  const current = model.value;
73
74
  return current == null ? "" : labelFor(current);
74
75
  });
75
- const placeholder = computed(() => props.multiple ? "" : selectedLabel.value);
76
+ const placeholder = computed(() => {
77
+ if (props.multiple) return modelArray.value.length ? "" : props.placeholder ?? "";
78
+ return selectedLabel.value || props.placeholder || "";
79
+ });
80
+ const displayValue = computed(() => {
81
+ if (props.multiple) return search.value;
82
+ return open.value ? search.value : selectedLabel.value;
83
+ });
84
+ function onInput(event) {
85
+ search.value = event.target.value;
86
+ open.value = true;
87
+ }
88
+ function onFocus() {
89
+ if (!props.multiple) search.value = "";
90
+ open.value = true;
91
+ }
92
+ const visibleItems = computed(() => {
93
+ let list = props.items;
94
+ if (!props.ignoreFilter) {
95
+ const query = search.value.trim().toLowerCase();
96
+ if (query) list = list.filter((item) => item.label.toLowerCase().includes(query));
97
+ }
98
+ if (props.multiple) {
99
+ list = list.filter((item) => !modelArray.value.includes(item[props.valueKey]));
100
+ }
101
+ return list;
102
+ });
76
103
  function labelFor(value) {
77
104
  return props.items.find((item) => item[props.valueKey] === value)?.label ?? `#${value}`;
78
105
  }
@@ -90,6 +117,8 @@ function pick(item) {
90
117
  const value = item[props.valueKey];
91
118
  if (props.multiple) {
92
119
  if (value != null) toggle(value);
120
+ search.value = "";
121
+ focusInput();
93
122
  } else {
94
123
  model.value = value;
95
124
  search.value = "";
@@ -8,6 +8,7 @@ type __VLS_Props = {
8
8
  ignoreFilter?: boolean;
9
9
  loading?: boolean;
10
10
  size?: string;
11
+ placeholder?: string;
11
12
  };
12
13
  type __VLS_ModelProps = {
13
14
  modelValue?: string | string[] | null;
@@ -13,10 +13,10 @@
13
13
  >
14
14
  <div class="flex items-start gap-3">
15
15
  <div class="min-w-0 flex-1">
16
- <p class="text-[13px] font-medium text-(--ui-text-highlighted)">
16
+ <p class="text-sm font-medium text-(--ui-text-highlighted)">
17
17
  {{ toast.title }}
18
18
  </p>
19
- <p v-if="toast.description" class="mt-0.5 text-xs text-(--ui-text-muted)">
19
+ <p v-if="toast.description" class="mt-0.5 text-sm text-(--ui-text-muted)">
20
20
  {{ toast.description }}
21
21
  </p>
22
22
  </div>
@@ -22,7 +22,7 @@
22
22
  </CmsButton>
23
23
  <span
24
24
  v-if="props.field.required && active === i18n.defaultLocale"
25
- class="ml-auto font-mono text-xs text-(--ui-text-muted)"
25
+ class="ml-auto font-mono text-sm text-(--ui-text-muted)"
26
26
  >
27
27
  {{ i18n.defaultLocale }} *
28
28
  </span>
@@ -33,7 +33,7 @@
33
33
  </nav>
34
34
 
35
35
  <div class="flex flex-col gap-3 border-t border-(--cms-line) px-3 pt-5">
36
- <div class="truncate text-[11px] font-medium text-(--ui-text-muted)">
36
+ <div class="truncate text-sm font-medium text-(--ui-text-muted)">
37
37
  {{ user?.email }}
38
38
  </div>
39
39
  <CmsButton
@@ -1,6 +1,6 @@
1
- export const CMS_FIELD_UI = { label: "text-[13px] font-medium text-(--ui-text-toned)" };
1
+ export const CMS_FIELD_UI = { label: "text-sm font-medium text-(--ui-text-toned)" };
2
2
  export const CMS_MODAL_UI = {
3
- content: "rounded-2xl shadow-xl sm:max-w-3xl",
3
+ content: "rounded-2xl sm:max-w-3xl",
4
4
  title: "cms-display text-xl font-medium text-(--ui-text-highlighted)"
5
5
  };
6
6
  export const CMS_FORM_ERRORS = Symbol("cms-form-errors");
@@ -1 +1 @@
1
- @import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600&family=Hanken+Grotesk:ital,wght@0,300..800;1,300..800&family=Fragment+Mono:ital@0;1&display=swap");@layer theme, base, components, utilities;@import "tailwindcss/theme.css" layer(theme);.cms-scope{@import "tailwindcss/preflight.css" layer(base);@import "tailwindcss/utilities.css" layer(utilities) source(none)}@source "../app";.cms-scope,body:has(.cms-scope){--cms-paper:#f6f4ef;--cms-surface:#fdfcfa;--cms-field:#f1ede4;--cms-ink:#262119;--cms-line:#e8e3d8;--cms-line-strong:#d8d1c1;--cms-fern:#3e6b4f;--cms-sage:#e3ebe1;--cms-sage-ink:#2e5138;--cms-shadow-soft:0 1px 2px rgba(38,33,25,.05),0 16px 40px -24px rgba(38,33,25,.22);color-scheme:light;--ui-text-dimmed:#a69d8b;--ui-text-muted:#877e6c;--ui-text-toned:#60594a;--ui-text:#403a2f;--ui-text-highlighted:var(--cms-ink);--ui-text-inverted:#fffdf8;--ui-bg:var(--cms-surface);--ui-bg-muted:var(--cms-paper);--ui-bg-elevated:var(--cms-field);--ui-bg-accented:#e7e1d3;--ui-bg-inverted:var(--cms-ink);--ui-border:var(--cms-line);--ui-border-muted:var(--cms-line);--ui-border-accented:var(--cms-line-strong);--ui-border-inverted:var(--cms-ink);--ui-primary:var(--cms-fern);--ui-error:#b4442f;--ui-radius:0.4rem;color:var(--ui-text);font-family:Hanken Grotesk,ui-sans-serif,system-ui,sans-serif;margin:0;padding:0;-webkit-font-smoothing:antialiased}body:has(.cms-scope) ::-moz-selection{background:var(--cms-sage);color:var(--cms-sage-ink)}body:has(.cms-scope) ::selection{background:var(--cms-sage);color:var(--cms-sage-ink)}.cms-display{font-family:Poppins,ui-sans-serif,system-ui,sans-serif;letter-spacing:-.02em}.cms-kicker{color:var(--ui-text-dimmed);font-family:Fragment Mono,ui-monospace,monospace;font-size:.6875rem;letter-spacing:.16em;line-height:1.4;text-transform:uppercase}body:has(.cms-scope) :is(button,a,label,[role=button],[role=menuitem],[role=option],.__clickable):not(:disabled,[aria-disabled=true]){@apply cursor-pointer transition-all duration-200 select-none}body:has(.cms-scope) :is(button,a,label,[role=button],[role=menuitem],[role=option],.__clickable):not(:disabled,[aria-disabled=true]):active{@apply transition-none;scale:.99}.cms-canvas{background-attachment:fixed;background-color:var(--cms-paper);background-image:radial-gradient(50rem 34rem at 10% -10%,rgba(62,107,79,.07),transparent 62%),radial-gradient(44rem 30rem at 108% 112%,rgba(193,149,87,.09),transparent 62%)}.cms-grain:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='a'%3E%3CfeTurbulence baseFrequency='.8' numOctaves='2' stitchTiles='stitch' type='fractalNoise'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)'/%3E%3C/svg%3E");content:"";inset:0;mix-blend-mode:multiply;opacity:.035;pointer-events:none;position:fixed;z-index:50}.cms-card{background:var(--cms-surface);border:1px solid var(--cms-line);border-radius:1rem;box-shadow:var(--cms-shadow-soft)}.cms-empty{background:rgba(253,252,250,.5);border:1.5px dashed var(--cms-line-strong);border-radius:1rem}.cms-navlink{align-items:center;border-radius:.75rem;color:var(--ui-text-toned);display:flex;font-size:.875rem;font-weight:500;gap:.625rem;padding:.5rem .75rem;transition:background-color .15s ease,color .15s ease}.cms-navlink:hover{background:rgba(38,33,25,.05);color:var(--ui-text-highlighted)}.cms-navlink.is-active{background:var(--cms-sage);color:var(--cms-sage-ink);font-weight:600}.cms-navlink.is-active .cms-navlink-icon{color:var(--cms-fern)}.cms-badge{border:1px solid transparent;border-radius:9999px;cursor:pointer;font-family:Fragment Mono,ui-monospace,monospace;font-size:.625rem;letter-spacing:.12em;padding:.25rem .625rem;text-transform:uppercase;transition:background-color .15s ease,border-color .15s ease}.cms-badge.is-published{background:var(--cms-sage);color:var(--cms-sage-ink)}.cms-badge.is-published:hover{background:#d6e2d3}.cms-badge.is-draft{background:transparent;border:1px dashed var(--cms-line-strong);color:var(--ui-text-muted)}.cms-badge.is-draft:hover{background:rgba(38,33,25,.04)}.cms-dropzone{align-items:center;background:rgba(253,252,250,.5);border:1.5px dashed var(--cms-line-strong);border-radius:1rem;color:var(--ui-text-muted);cursor:pointer;display:flex;flex-direction:column;gap:.5rem;justify-content:center;padding:2rem 1.5rem;transition:border-color .15s ease,background-color .15s ease,color .15s ease;width:100%}.cms-dropzone.is-over,.cms-dropzone:hover{background:var(--cms-sage);border-color:var(--cms-fern);color:var(--cms-sage-ink)}.cms-dropzone.is-dense{border-radius:.75rem;flex-direction:row;gap:.625rem;padding:.875rem 1.25rem}.cms-dropzone.is-busy{opacity:.7;pointer-events:none}.cms-pill{background:var(--cms-surface);border:1px solid var(--cms-line);border-radius:9999px;color:var(--ui-text-dimmed);cursor:pointer;font-family:Fragment Mono,ui-monospace,monospace;font-size:.625rem;letter-spacing:.12em;padding:.3rem .8rem;transition:background-color .15s ease,color .15s ease}.cms-pill:hover{background:rgba(38,33,25,.05);color:var(--ui-text)}.cms-pill.is-active{background:var(--cms-sage);color:var(--cms-sage-ink)}@keyframes cms-rise{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}.cms-rise{animation:cms-rise .55s cubic-bezier(.22,1,.36,1) both}@media (prefers-reduced-motion:reduce){.cms-rise{animation:none}}.cms-richtext{border:1px solid var(--cms-line);border-radius:.75rem;overflow:hidden}.cms-richtext-toolbar{align-items:center;border-bottom:1px solid var(--cms-line);display:flex;flex-wrap:wrap;gap:2px;padding:4px}.cms-richtext-body{font-size:.875rem;min-height:9rem;outline:none;padding:.75rem 1rem}.cms-richtext-body h2{font-size:1.25rem;font-weight:600;margin:.75em 0 .35em}.cms-richtext-body h3{font-size:1.1rem;font-weight:600;margin:.75em 0 .35em}.cms-richtext-body p{margin:.35em 0}.cms-richtext-body ul{list-style:disc;padding-left:1.25rem}.cms-richtext-body ol{list-style:decimal;padding-left:1.25rem}.cms-richtext-body blockquote{border-left:3px solid var(--cms-line);color:var(--ui-text-muted);padding-left:.75rem}.cms-btn{align-items:center;border:1px solid transparent;border-radius:var(--ui-radius);display:inline-flex;font-weight:500;gap:.375rem;justify-content:center;line-height:1;transition:background-color .15s ease,border-color .15s ease,color .15s ease,opacity .15s ease;white-space:nowrap}.cms-btn:disabled,.cms-btn[aria-disabled=true]{opacity:.5;pointer-events:none}.cms-btn.is-block{width:100%}.cms-btn-xs{font-size:.75rem;padding:.25rem .5rem}.cms-btn-sm{font-size:.8125rem;padding:.375rem .625rem}.cms-btn-md{font-size:.875rem;padding:.5rem .875rem}.cms-btn-lg{font-size:.9375rem;padding:.625rem 1rem}.cms-btn-primary.cms-btn-solid{background:var(--ui-primary);color:var(--ui-text-inverted)}.cms-btn-primary.cms-btn-solid:hover{background:color-mix(in oklab,var(--ui-primary) 88%,#000)}.cms-btn-primary.cms-btn-subtle{background:var(--cms-sage);color:var(--cms-sage-ink)}.cms-btn-primary.cms-btn-subtle:hover{background:#d6e2d3}.cms-btn-primary.cms-btn-soft{background:var(--cms-sage);color:var(--cms-sage-ink)}.cms-btn-neutral.cms-btn-solid{background:var(--ui-bg-inverted);color:var(--ui-text-inverted)}.cms-btn-neutral.cms-btn-subtle{background:var(--ui-bg-elevated);color:var(--ui-text-highlighted)}.cms-btn-neutral.cms-btn-subtle:hover{background:var(--ui-bg-accented)}.cms-btn-neutral.cms-btn-soft{background:var(--ui-bg-elevated);color:var(--ui-text-highlighted)}.cms-btn-neutral.cms-btn-ghost{color:var(--ui-text-toned)}.cms-btn-neutral.cms-btn-ghost:hover{background:rgba(38,33,25,.06);color:var(--ui-text-highlighted)}.cms-btn-error.cms-btn-solid{background:var(--ui-error);color:#fff}.cms-btn-error.cms-btn-solid:hover{background:color-mix(in oklab,var(--ui-error) 88%,#000)}.cms-btn-error.cms-btn-subtle{background:color-mix(in oklab,var(--ui-error) 12%,transparent);color:var(--ui-error)}.cms-btn-error.cms-btn-ghost{color:var(--ui-error)}.cms-btn-error.cms-btn-ghost:hover{background:color-mix(in oklab,var(--ui-error) 10%,transparent)}.cms-btn-success.cms-btn-solid{background:var(--cms-fern);color:var(--ui-text-inverted)}.cms-field{background:var(--ui-bg);border:1px solid var(--cms-line-strong);border-radius:var(--ui-radius);color:var(--ui-text-highlighted);transition:border-color .15s ease,box-shadow .15s ease;width:100%}.cms-field::-moz-placeholder{color:var(--ui-text-dimmed)}.cms-field::placeholder{color:var(--ui-text-dimmed)}.cms-field:focus,.cms-field:focus-within{border-color:var(--ui-primary);box-shadow:0 0 0 3px color-mix(in oklab,var(--ui-primary) 18%,transparent);outline:none}.cms-field.is-error{border-color:var(--ui-error)}.cms-field-sm{font-size:.8125rem;padding:.375rem .625rem}.cms-field-md{font-size:.875rem;padding:.5rem .75rem}.cms-field-lg{font-size:.9375rem;padding:.625rem .875rem}.cms-input-wrap{align-items:center;display:flex;position:relative}.cms-input-wrap .cms-field{width:100%}.cms-input-lead,.cms-input-trail{align-items:center;color:var(--ui-text-dimmed);display:flex;position:absolute}.cms-input-lead{left:.625rem}.cms-input-trail{right:.375rem}.cms-input-wrap.has-lead .cms-field{padding-left:2rem}.cms-switch{align-items:center;background:var(--cms-line-strong);border-radius:9999px;display:inline-flex;flex-shrink:0;height:1.25rem;position:relative;transition:background-color .15s ease;width:2.25rem}.cms-switch.is-on{background:var(--ui-primary)}.cms-switch:after{background:#fff;border-radius:9999px;content:"";height:.875rem;left:.1875rem;position:absolute;transition:transform .15s ease;width:.875rem}.cms-switch.is-on:after{transform:translateX(1rem)}.cms-overlay{align-items:flex-start;backdrop-filter:blur(2px);background:rgba(38,33,25,.32);display:flex;inset:0;justify-content:center;overflow-y:auto;padding:4rem 1rem 2rem;position:fixed;z-index:60}.cms-modal{background:var(--ui-bg);border:1px solid var(--cms-line);border-radius:1rem;box-shadow:var(--cms-shadow-soft);max-width:32rem;width:100%}.cms-modal-header{padding:1.25rem 1.5rem 0}.cms-modal-body{padding:1.25rem 1.5rem 1.5rem}.cms-menu{background:var(--ui-bg);border:1px solid var(--cms-line);border-radius:.75rem;box-shadow:var(--cms-shadow-soft);margin-top:.375rem;min-width:11rem;padding:.25rem;position:absolute;z-index:50}.cms-menu.align-end{right:0}.cms-menu-item{align-items:center;border-radius:.5rem;color:var(--ui-text);cursor:pointer;display:flex;font-size:.8125rem;gap:.5rem;padding:.4rem .6rem;text-align:left;width:100%}.cms-menu-item:hover{background:var(--ui-bg-elevated)}.cms-menu-check{color:var(--ui-primary);margin-left:auto}.cms-popover,.cms-selectmenu-panel{background:var(--ui-bg);border:1px solid var(--cms-line);border-radius:.75rem;box-shadow:var(--cms-shadow-soft);margin-top:.375rem;position:absolute;z-index:50}.cms-selectmenu-panel{left:0;max-height:15rem;overflow-y:auto;padding:.25rem;right:0}.cms-tag{align-items:center;background:var(--ui-bg-elevated);border-radius:9999px;display:inline-flex;font-size:.75rem;gap:.25rem;padding:.125rem .5rem}.cms-table{border-collapse:collapse;text-align:left;width:100%}.cms-alert{border-radius:.75rem;font-size:.8125rem;padding:.75rem 1rem}.cms-alert-error{background:color-mix(in oklab,var(--ui-error) 10%,transparent);color:var(--ui-error)}.cms-toaster{bottom:1rem;display:flex;flex-direction:column;gap:.5rem;max-width:calc(100vw - 2rem);position:fixed;right:1rem;width:20rem;z-index:80}.cms-toast{background:var(--ui-bg);border:1px solid var(--cms-line);border-radius:.75rem;box-shadow:var(--cms-shadow-soft);padding:.75rem .875rem}.cms-toast.is-success{border-left:3px solid var(--cms-fern)}.cms-toast.is-error{border-left:3px solid var(--ui-error)}.cms-pagination{align-items:center;display:flex;gap:.25rem}.cms-page-btn{border-radius:.5rem;color:var(--ui-text-toned);font-size:.8125rem;height:2rem;min-width:2rem;padding:0 .5rem}.cms-page-btn:hover:not(:disabled){background:var(--ui-bg-elevated)}.cms-page-btn.is-active{background:var(--cms-sage);color:var(--cms-sage-ink);font-weight:600}.cms-page-btn:disabled{opacity:.4}
1
+ @import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600&family=Hanken+Grotesk:ital,wght@0,300..800;1,300..800&family=Fragment+Mono:ital@0;1&display=swap");@layer theme, base, components, utilities;@import "tailwindcss/theme.css" layer(theme);.cms-scope{@import "tailwindcss/preflight.css" layer(base);@import "tailwindcss/utilities.css" layer(utilities) source(none)}@source "../app";.cms-scope,body:has(.cms-scope){--cms-paper:#f5f5f5;--cms-surface:#fff;--cms-field:#f0f0f0;--cms-ink:#171717;--cms-line:#e5e5e5;--cms-line-strong:#d4d4d4;--cms-fern:#171717;--cms-sage:#ebebeb;--cms-sage-ink:#171717;--cms-shadow-soft:none;color-scheme:light;--ui-text-dimmed:#a3a3a3;--ui-text-muted:#737373;--ui-text-toned:#525252;--ui-text:#404040;--ui-text-highlighted:var(--cms-ink);--ui-text-inverted:#fff;--ui-bg:var(--cms-surface);--ui-bg-muted:var(--cms-paper);--ui-bg-elevated:var(--cms-field);--ui-bg-accented:#e0e0e0;--ui-bg-inverted:var(--cms-ink);--ui-border:var(--cms-line);--ui-border-muted:var(--cms-line);--ui-border-accented:var(--cms-line-strong);--ui-border-inverted:var(--cms-ink);--ui-primary:var(--cms-ink);--ui-error:#dc2626;--ui-radius:0.4rem;color:var(--ui-text);font-family:Hanken Grotesk,ui-sans-serif,system-ui,sans-serif;margin:0;padding:0;-webkit-font-smoothing:antialiased}body:has(.cms-scope) ::-moz-selection{background:var(--cms-sage);color:var(--cms-sage-ink)}body:has(.cms-scope) ::selection{background:var(--cms-sage);color:var(--cms-sage-ink)}.cms-display{font-family:Poppins,ui-sans-serif,system-ui,sans-serif;letter-spacing:-.02em}.cms-kicker{color:var(--ui-text-dimmed);font-family:Fragment Mono,ui-monospace,monospace;font-size:.875rem;letter-spacing:.16em;line-height:1.4;text-transform:uppercase}body:has(.cms-scope) :is(button,a,[role=button],[role=menuitem],[role=option],.__clickable):not(:disabled,[aria-disabled=true]){@apply cursor-pointer transition-all duration-200 select-none}body:has(.cms-scope) :is(button,a,[role=button],[role=menuitem],[role=option],.__clickable):not(:disabled,[aria-disabled=true]):active{@apply transition-none;scale:.99}.cms-canvas{background-color:var(--cms-paper)}.cms-grain:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='a'%3E%3CfeTurbulence baseFrequency='.8' numOctaves='2' stitchTiles='stitch' type='fractalNoise'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)'/%3E%3C/svg%3E");content:"";inset:0;mix-blend-mode:multiply;opacity:.035;pointer-events:none;position:fixed;z-index:50}.cms-card{background:var(--cms-surface);border:1px solid var(--cms-line);border-radius:1rem;box-shadow:var(--cms-shadow-soft)}.cms-empty{background:rgba(253,252,250,.5);border:1.5px dashed var(--cms-line-strong);border-radius:1rem}.cms-navlink{align-items:center;border-radius:.75rem;color:var(--ui-text-toned);display:flex;font-size:.875rem;font-weight:500;gap:.625rem;padding:.5rem .75rem;transition:background-color .15s ease,color .15s ease}.cms-navlink:hover{background:rgba(38,33,25,.05);color:var(--ui-text-highlighted)}.cms-navlink.is-active{background:var(--cms-sage);color:var(--cms-sage-ink);font-weight:600}.cms-navlink.is-active .cms-navlink-icon{color:var(--cms-fern)}.cms-badge{border:1px solid transparent;border-radius:9999px;cursor:pointer;font-family:Fragment Mono,ui-monospace,monospace;font-size:.875rem;letter-spacing:.12em;padding:.25rem .625rem;text-transform:uppercase;transition:background-color .15s ease,border-color .15s ease}.cms-badge.is-published{background:var(--cms-sage);color:var(--cms-sage-ink)}.cms-badge.is-published:hover{background:#e0e0e0}.cms-badge.is-draft{background:transparent;border:1px dashed var(--cms-line-strong);color:var(--ui-text-muted)}.cms-badge.is-draft:hover{background:rgba(38,33,25,.04)}.cms-dropzone{align-items:center;background:rgba(253,252,250,.5);border:1.5px dashed var(--cms-line-strong);border-radius:1rem;color:var(--ui-text-muted);cursor:pointer;display:flex;flex-direction:column;gap:.5rem;justify-content:center;padding:2rem 1.5rem;transition:border-color .15s ease,background-color .15s ease,color .15s ease;width:100%}.cms-dropzone.is-over,.cms-dropzone:hover{background:var(--cms-sage);border-color:var(--cms-fern);color:var(--cms-sage-ink)}.cms-dropzone.is-dense{border-radius:.75rem;flex-direction:row;gap:.625rem;padding:.875rem 1.25rem}.cms-dropzone.is-busy{opacity:.7;pointer-events:none}.cms-pill{background:var(--cms-surface);border:1px solid var(--cms-line);border-radius:9999px;color:var(--ui-text-dimmed);cursor:pointer;font-family:Fragment Mono,ui-monospace,monospace;font-size:.875rem;letter-spacing:.12em;padding:.3rem .8rem;transition:background-color .15s ease,color .15s ease}.cms-pill:hover{background:rgba(38,33,25,.05);color:var(--ui-text)}.cms-pill.is-active{background:var(--cms-sage);color:var(--cms-sage-ink)}@keyframes cms-rise{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}.cms-rise{animation:cms-rise .55s cubic-bezier(.22,1,.36,1) both}@media (prefers-reduced-motion:reduce){.cms-rise{animation:none}}.cms-richtext{border:1px solid var(--cms-line);border-radius:.75rem;overflow:hidden}.cms-richtext-toolbar{align-items:center;border-bottom:1px solid var(--cms-line);display:flex;flex-wrap:wrap;gap:2px;padding:4px}.cms-richtext-body{font-size:.875rem;min-height:9rem;outline:none;padding:.75rem 1rem}.cms-richtext-body h2{font-size:1.25rem;font-weight:600;margin:.75em 0 .35em}.cms-richtext-body h3{font-size:1.1rem;font-weight:600;margin:.75em 0 .35em}.cms-richtext-body p{margin:.35em 0}.cms-richtext-body ul{list-style:disc;padding-left:1.25rem}.cms-richtext-body ol{list-style:decimal;padding-left:1.25rem}.cms-richtext-body blockquote{border-left:3px solid var(--cms-line);color:var(--ui-text-muted);padding-left:.75rem}.cms-btn{align-items:center;border:1px solid transparent;border-radius:var(--ui-radius);display:inline-flex;font-weight:500;gap:.375rem;justify-content:center;line-height:1;transition:background-color .15s ease,border-color .15s ease,color .15s ease,opacity .15s ease;white-space:nowrap}.cms-btn:disabled,.cms-btn[aria-disabled=true]{opacity:.5;pointer-events:none}.cms-btn.is-block{width:100%}.cms-btn-xs{font-size:.875rem;padding:.25rem .5rem}.cms-btn-sm{font-size:.875rem;padding:.375rem .625rem}.cms-btn-md{font-size:.875rem;padding:.5rem .875rem}.cms-btn-lg{font-size:.9375rem;padding:.625rem 1rem}.cms-btn-primary.cms-btn-solid{background:var(--ui-primary);color:var(--ui-text-inverted)}.cms-btn-primary.cms-btn-solid:hover{background:color-mix(in oklab,var(--ui-primary) 88%,#000)}.cms-btn-primary.cms-btn-subtle{background:var(--cms-sage);color:var(--cms-sage-ink)}.cms-btn-primary.cms-btn-subtle:hover{background:#e0e0e0}.cms-btn-primary.cms-btn-soft{background:var(--cms-sage);color:var(--cms-sage-ink)}.cms-btn-neutral.cms-btn-solid{background:var(--ui-bg-inverted);color:var(--ui-text-inverted)}.cms-btn-neutral.cms-btn-subtle{background:var(--ui-bg-elevated);color:var(--ui-text-highlighted)}.cms-btn-neutral.cms-btn-subtle:hover{background:var(--ui-bg-accented)}.cms-btn-neutral.cms-btn-soft{background:var(--ui-bg-elevated);color:var(--ui-text-highlighted)}.cms-btn-neutral.cms-btn-ghost{color:var(--ui-text-toned)}.cms-btn-neutral.cms-btn-ghost:hover{background:rgba(38,33,25,.06);color:var(--ui-text-highlighted)}.cms-btn-error.cms-btn-solid{background:var(--ui-error);color:#fff}.cms-btn-error.cms-btn-solid:hover{background:color-mix(in oklab,var(--ui-error) 88%,#000)}.cms-btn-error.cms-btn-subtle{background:color-mix(in oklab,var(--ui-error) 12%,transparent);color:var(--ui-error)}.cms-btn-error.cms-btn-ghost{color:var(--ui-error)}.cms-btn-error.cms-btn-ghost:hover{background:color-mix(in oklab,var(--ui-error) 10%,transparent)}.cms-btn-success.cms-btn-solid{background:var(--cms-fern);color:var(--ui-text-inverted)}.cms-field{background:var(--ui-bg);border:1px solid var(--cms-line-strong);border-radius:var(--ui-radius);color:var(--ui-text-highlighted);transition:border-color .15s ease,box-shadow .15s ease;width:100%}.cms-field::-moz-placeholder{color:var(--ui-text-dimmed)}.cms-field::placeholder{color:var(--ui-text-dimmed)}.cms-field:focus,.cms-field:focus-within{border-color:var(--ui-primary);box-shadow:0 0 0 3px color-mix(in oklab,var(--ui-primary) 18%,transparent);outline:none}.cms-field.is-error{border-color:var(--ui-error)}.cms-field-sm{font-size:.875rem;padding:.375rem .625rem}.cms-field-md{font-size:.875rem;padding:.5rem .75rem}.cms-field-lg{font-size:.9375rem;padding:.625rem .875rem}.cms-input-wrap{align-items:center;display:flex;position:relative}.cms-input-wrap .cms-field{width:100%}.cms-input-lead,.cms-input-trail{align-items:center;color:var(--ui-text-dimmed);display:flex;position:absolute}.cms-input-lead{left:.625rem}.cms-input-trail{right:.375rem}.cms-input-wrap.has-lead .cms-field{padding-left:2rem}.cms-switch{align-items:center;background:var(--cms-line-strong);border-radius:9999px;display:inline-flex;flex-shrink:0;height:1.25rem;position:relative;transition:background-color .15s ease;width:2.25rem}.cms-switch.is-on{background:var(--ui-primary)}.cms-switch:after{background:#fff;border-radius:9999px;content:"";height:.875rem;left:.1875rem;position:absolute;transition:transform .15s ease;width:.875rem}.cms-switch.is-on:after{transform:translateX(1rem)}.cms-overlay{align-items:center;backdrop-filter:blur(2px);background:rgba(38,33,25,.32);display:flex;inset:0;justify-content:center;overflow-y:auto;padding:2rem 1rem;position:fixed;z-index:60}.cms-modal{background:var(--ui-bg);border:1px solid var(--cms-line);border-radius:1rem;box-shadow:var(--cms-shadow-soft);max-width:32rem;width:100%}.cms-modal-header{padding:1.25rem 1.5rem 0}.cms-modal-body{padding:1.25rem 1.5rem 1.5rem}.cms-menu{background:var(--ui-bg);border:1px solid var(--cms-line);border-radius:.75rem;box-shadow:var(--cms-shadow-soft);margin-top:.375rem;min-width:11rem;padding:.25rem;position:absolute;z-index:50}.cms-menu.align-end{right:0}.cms-menu-item{align-items:center;border-radius:.5rem;color:var(--ui-text);cursor:pointer;display:flex;font-size:.875rem;gap:.5rem;padding:.4rem .6rem;text-align:left;width:100%}.cms-menu-item:hover{background:var(--ui-bg-elevated)}.cms-menu-check{color:var(--ui-primary);margin-left:auto}.cms-popover,.cms-selectmenu-panel{background:var(--ui-bg);border:1px solid var(--cms-line);border-radius:.75rem;box-shadow:var(--cms-shadow-soft);margin-top:.375rem;position:absolute;z-index:50}.cms-selectmenu-panel{left:0;max-height:15rem;overflow-y:auto;padding:.25rem;right:0}.cms-tag{align-items:center;background:var(--ui-bg-elevated);border-radius:9999px;display:inline-flex;font-size:.875rem;gap:.25rem;padding:.125rem .5rem}.cms-table{border-collapse:collapse;text-align:left;width:100%}.cms-alert{border-radius:.75rem;font-size:.875rem;padding:.75rem 1rem}.cms-alert-error{background:color-mix(in oklab,var(--ui-error) 10%,transparent);color:var(--ui-error)}.cms-toaster{bottom:1rem;display:flex;flex-direction:column;gap:.5rem;max-width:calc(100vw - 2rem);position:fixed;right:1rem;width:20rem;z-index:80}.cms-toast{background:var(--ui-bg);border:1px solid var(--cms-line);border-radius:.75rem;box-shadow:var(--cms-shadow-soft);padding:.75rem .875rem}.cms-toast.is-success{border-left:3px solid var(--cms-fern)}.cms-toast.is-error{border-left:3px solid var(--ui-error)}.cms-pagination{align-items:center;display:flex;gap:.25rem}.cms-page-btn{border-radius:.5rem;color:var(--ui-text-toned);font-size:.875rem;height:2rem;min-width:2rem;padding:0 .5rem}.cms-page-btn:hover:not(:disabled){background:var(--ui-bg-elevated)}.cms-page-btn.is-active{background:var(--cms-sage);color:var(--cms-sage-ink);font-weight:600}.cms-page-btn:disabled{opacity:.4}
@@ -0,0 +1,5 @@
1
+ export interface GenerateMigrationsOptions {
2
+ root?: string;
3
+ configPath?: string;
4
+ }
5
+ export declare function generateMigrations(opts?: GenerateMigrationsOptions): Promise<void>;
@@ -0,0 +1,22 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import { dirname, join } from "node:path";
5
+ export async function generateMigrations(opts = {}) {
6
+ const root = opts.root ?? process.cwd();
7
+ const configPath = opts.configPath ?? join(root, ".nuxt/cms/drizzle.config.ts");
8
+ if (!existsSync(configPath)) {
9
+ throw new Error(
10
+ `[nuxt-cms] drizzle config not found at ${configPath} \u2014 run \`nuxi prepare\` first.`
11
+ );
12
+ }
13
+ const require = createRequire(import.meta.url);
14
+ const bin = join(dirname(require.resolve("drizzle-kit")), "bin.cjs");
15
+ const code = await new Promise((done) => {
16
+ spawn(process.execPath, [bin, "generate", `--config=${configPath}`], {
17
+ cwd: root,
18
+ stdio: "inherit"
19
+ }).on("close", (c) => done(c ?? 1)).on("error", () => done(1));
20
+ });
21
+ if (code !== 0) throw new Error(`[nuxt-cms] drizzle-kit generate failed (exit ${code})`);
22
+ }
@@ -0,0 +1,27 @@
1
+ import { customId, ID_LENGTH } from './server/utils/custom-id.js';
2
+ export { customId, ID_LENGTH };
3
+ export type SeedDriver = 'sqlite' | 'libsql' | 'postgres';
4
+ export interface CmsSeederOptions {
5
+ driver?: SeedDriver;
6
+ url?: string;
7
+ authToken?: string;
8
+ dbPath?: string;
9
+ migrationsDir?: string;
10
+ root?: string;
11
+ }
12
+ export declare function createCmsSeeder(opts?: CmsSeederOptions): Promise<{
13
+ db: import("drizzle-orm/libsql").LibSQLDatabase<Record<string, never>> & {
14
+ $client: import("@libsql/client").Client;
15
+ };
16
+ migrate: () => Promise<void>;
17
+ } | {
18
+ db: import("drizzle-orm/node-postgres").NodePgDatabase<Record<string, never>> & {
19
+ $client: import("pg").Pool;
20
+ };
21
+ migrate: () => Promise<void>;
22
+ } | {
23
+ db: import("drizzle-orm/better-sqlite3").BetterSQLite3Database<Record<string, never>> & {
24
+ $client: import("better-sqlite3").Database;
25
+ };
26
+ migrate: () => void;
27
+ }>;
@@ -0,0 +1,44 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { dirname, isAbsolute, resolve } from "node:path";
3
+ import { customId, ID_LENGTH } from "./server/utils/custom-id.js";
4
+ export { customId, ID_LENGTH };
5
+ function inferDriver(url) {
6
+ if (/^(libsql|wss?|https?):\/\//.test(url)) return "libsql";
7
+ if (/^postgres(ql)?:\/\//.test(url)) return "postgres";
8
+ return "sqlite";
9
+ }
10
+ export async function createCmsSeeder(opts = {}) {
11
+ const root = opts.root ?? process.cwd();
12
+ const url = opts.url ?? process.env.NUXT_CMS_DATABASE_URL ?? "";
13
+ const authToken = opts.authToken ?? process.env.NUXT_CMS_DATABASE_AUTH_TOKEN ?? "";
14
+ const driver = opts.driver ?? process.env.NUXT_CMS_DATABASE_DRIVER ?? inferDriver(url);
15
+ const dbPath = opts.dbPath ?? process.env.NUXT_CMS_DATABASE_PATH ?? "data/cms.db";
16
+ const resolvedDbPath = isAbsolute(dbPath) ? dbPath : resolve(root, dbPath);
17
+ const migrationsFolder = opts.migrationsDir ?? resolve(root, `server/db/migrations/${driver}`);
18
+ if (driver === "libsql") {
19
+ const { createClient } = await import("@libsql/client");
20
+ const { drizzle: drizzle2 } = await import("drizzle-orm/libsql");
21
+ const { migrate: migrate2 } = await import("drizzle-orm/libsql/migrator");
22
+ const dbUrl = url || `file:${resolvedDbPath}`;
23
+ if (dbUrl.startsWith("file:")) mkdirSync(dirname(resolvedDbPath), { recursive: true });
24
+ const db2 = drizzle2(createClient({ url: dbUrl, authToken: authToken || void 0 }));
25
+ return { db: db2, migrate: () => migrate2(db2, { migrationsFolder }) };
26
+ }
27
+ if (driver === "postgres") {
28
+ if (!url) throw new Error("[nuxt-cms] seed: Postgres needs a url (NUXT_CMS_DATABASE_URL)");
29
+ const { drizzle: drizzle2 } = await import("drizzle-orm/node-postgres");
30
+ const { migrate: migrate2 } = await import("drizzle-orm/node-postgres/migrator");
31
+ const pg = (await import("pg")).default;
32
+ const db2 = drizzle2(new pg.Pool({ connectionString: url }));
33
+ return { db: db2, migrate: () => migrate2(db2, { migrationsFolder }) };
34
+ }
35
+ const Database = (await import("better-sqlite3")).default;
36
+ const { drizzle } = await import("drizzle-orm/better-sqlite3");
37
+ const { migrate } = await import("drizzle-orm/better-sqlite3/migrator");
38
+ mkdirSync(dirname(resolvedDbPath), { recursive: true });
39
+ const sqlite = new Database(resolvedDbPath);
40
+ sqlite.pragma("journal_mode = WAL");
41
+ sqlite.pragma("foreign_keys = ON");
42
+ const db = drizzle(sqlite);
43
+ return { db, migrate: () => migrate(db, { migrationsFolder }) };
44
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xleddyl/nuxt-cms",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Lightweight CMS that ships with your Nuxt app: runs on the Nitro server, content types defined in code, /cms admin panel, GraphQL API, SQLite or Postgres. No external CMS needed!",
5
5
  "license": "MIT",
6
6
  "author": "Edoardo Alberti (https://github.com/xleddyl)",
@@ -38,6 +38,14 @@
38
38
  ".": {
39
39
  "types": "./dist/types.d.mts",
40
40
  "import": "./dist/module.mjs"
41
+ },
42
+ "./seed": {
43
+ "types": "./dist/runtime/seed.d.ts",
44
+ "import": "./dist/runtime/seed.js"
45
+ },
46
+ "./generate": {
47
+ "types": "./dist/runtime/generate.d.ts",
48
+ "import": "./dist/runtime/generate.js"
41
49
  }
42
50
  },
43
51
  "main": "./dist/module.mjs",
@@ -1,16 +0,0 @@
1
- type __VLS_Props = {
2
- items?: string[];
3
- size?: 'sm' | 'md' | 'lg';
4
- required?: boolean;
5
- };
6
- type __VLS_ModelProps = {
7
- modelValue?: string | undefined;
8
- };
9
- type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
- declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
- "update:modelValue": (value: string | undefined) => any;
12
- }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
13
- "onUpdate:modelValue"?: ((value: string | undefined) => any) | undefined;
14
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
- declare const _default: typeof __VLS_export;
16
- export default _default;
@@ -1,19 +0,0 @@
1
- <template>
2
- <select
3
- :value="model ?? ''"
4
- :class="['cms-field', `cms-field-${size ?? 'md'}`]"
5
- @change="model = $event.target.value || void 0"
6
- >
7
- <option v-if="!required" value="">—</option>
8
- <option v-for="option in items" :key="option" :value="option">{{ option }}</option>
9
- </select>
10
- </template>
11
-
12
- <script setup>
13
- defineProps({
14
- items: { type: Array, required: false },
15
- size: { type: String, required: false },
16
- required: { type: Boolean, required: false }
17
- });
18
- const model = defineModel({ type: null });
19
- </script>
@@ -1,16 +0,0 @@
1
- type __VLS_Props = {
2
- items?: string[];
3
- size?: 'sm' | 'md' | 'lg';
4
- required?: boolean;
5
- };
6
- type __VLS_ModelProps = {
7
- modelValue?: string | undefined;
8
- };
9
- type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
- declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
- "update:modelValue": (value: string | undefined) => any;
12
- }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
13
- "onUpdate:modelValue"?: ((value: string | undefined) => any) | undefined;
14
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
- declare const _default: typeof __VLS_export;
16
- export default _default;