@hywax/cms 3.0.1 → 3.2.0

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.
@@ -1,8 +1,9 @@
1
1
  export default {
2
2
  "slots": {
3
- "root": "",
3
+ "root": "flex flex-col gap-2",
4
+ "header": "",
4
5
  "title": "text-base text-pretty font-semibold text-highlighted",
5
- "description": "text-[15px] text-pretty text-muted mb-2",
6
+ "description": "text-[15px] text-pretty text-muted",
6
7
  "body": "relative rounded-lg bg-elevated/50 ring ring-default grid gap-x-8 gap-y-4 p-4 sm:p-6"
7
8
  }
8
9
  }
@@ -2,7 +2,7 @@ export default {
2
2
  "slots": {
3
3
  "root": "",
4
4
  "form": "flex flex-1 flex-row",
5
- "body": "flex-1 overflow-y-auto w-full lg:max-w-2xl mx-auto p-4 sm:p-6",
5
+ "body": "flex-1 w-full mx-auto p-4 sm:p-6 overflow-y-auto",
6
6
  "sidebar": "flex-1 overflow-y-auto border-l border-default w-full max-w-xs p-4 sm:p-4"
7
7
  },
8
8
  "variants": {
@@ -10,6 +10,11 @@ export default {
10
10
  "true": {
11
11
  "sidebar": "*:not-last:after:absolute *:not-last:after:inset-x-1 *:not-last:after:bottom-0 *:not-last:after:bg-border *:not-last:after:h-px *:not-last:relative *:not-last:pb-4 flex flex-col gap-4"
12
12
  }
13
+ },
14
+ "bodyFit": {
15
+ "true": {
16
+ "body": "lg:max-w-2xl mx-auto"
17
+ }
13
18
  }
14
19
  }
15
20
  }
@@ -1,7 +1,7 @@
1
1
  export default {
2
2
  "slots": {
3
3
  "root": "relative w-full gap-1",
4
- "base": "rounded-md overflow-hidden border-1 border-default bg-default aspect-3/2",
4
+ "base": "rounded-md overflow-hidden ring-1 ring-inset ring-accented bg-default aspect-3/2",
5
5
  "uploader": "flex flex-col items-center justify-center w-full h-full",
6
6
  "uploaderPendingIcon": "size-6 animate-spin",
7
7
  "uploaderIdleButton": "cursor-pointer h-full w-full",
package/.nuxt/cms.css CHANGED
@@ -11,6 +11,7 @@
11
11
  @source "./cms/table-panel.ts";
12
12
  @source "./cms/modal-confirm.ts";
13
13
  @source "./cms/table-search-input.ts";
14
+ @source "./cms/table-preview-seo.ts";
14
15
  @source "./cms/ms-core-options.ts";
15
16
  @source "./cms/ms-nuxt-context.ts";
16
17
  @source "./cms/date-picker.ts";
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hywax/cms",
3
- "version": "3.0.1",
3
+ "version": "3.2.0",
4
4
  "configKey": "cms",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
package/dist/module.mjs CHANGED
@@ -7,7 +7,7 @@ import { pascalCase, kebabCase } from 'scule';
7
7
  import { globSync } from 'tinyglobby';
8
8
 
9
9
  const name = "@hywax/cms";
10
- const version = "3.0.1";
10
+ const version = "3.2.0";
11
11
 
12
12
  function createContext(options, nuxt) {
13
13
  const { resolve } = createResolver(import.meta.url);
@@ -437,7 +437,7 @@ const formPanel = {
437
437
  slots: {
438
438
  root: "",
439
439
  form: "flex flex-1 flex-row",
440
- body: "flex-1 overflow-y-auto w-full lg:max-w-2xl mx-auto p-4 sm:p-6",
440
+ body: "flex-1 w-full mx-auto p-4 sm:p-6 overflow-y-auto",
441
441
  sidebar: "flex-1 overflow-y-auto border-l border-default w-full max-w-xs p-4 sm:p-4"
442
442
  },
443
443
  variants: {
@@ -445,6 +445,11 @@ const formPanel = {
445
445
  true: {
446
446
  sidebar: "*:not-last:after:absolute *:not-last:after:inset-x-1 *:not-last:after:bottom-0 *:not-last:after:bg-border *:not-last:after:h-px *:not-last:relative *:not-last:pb-4 flex flex-col gap-4"
447
447
  }
448
+ },
449
+ bodyFit: {
450
+ true: {
451
+ body: "lg:max-w-2xl mx-auto"
452
+ }
448
453
  }
449
454
  }
450
455
  };
@@ -461,9 +466,10 @@ const formPanelAsideSection = {
461
466
 
462
467
  const formPanelSection = {
463
468
  slots: {
464
- root: "",
469
+ root: "flex flex-col gap-2",
470
+ header: "",
465
471
  title: "text-base text-pretty font-semibold text-highlighted",
466
- description: "text-[15px] text-pretty text-muted mb-2",
472
+ description: "text-[15px] text-pretty text-muted",
467
473
  body: "relative rounded-lg bg-elevated/50 ring ring-default grid gap-x-8 gap-y-4 p-4 sm:p-6"
468
474
  }
469
475
  };
@@ -483,7 +489,7 @@ const formSlug = {
483
489
  const formUploraImage = {
484
490
  slots: {
485
491
  root: "relative w-full gap-1",
486
- base: "rounded-md overflow-hidden border-1 border-default bg-default aspect-3/2",
492
+ base: "rounded-md overflow-hidden ring-1 ring-inset ring-accented bg-default aspect-3/2",
487
493
  uploader: "flex flex-col items-center justify-center w-full h-full",
488
494
  uploaderPendingIcon: "size-6 animate-spin",
489
495
  uploaderIdleButton: "cursor-pointer h-full w-full",
@@ -2,6 +2,7 @@ import type { EditorCustomHandlers, EditorEmits, EditorProps } from '@nuxt/ui';
2
2
  import type { HTMLContent } from '@tiptap/vue-3';
3
3
  export interface EditorFullProps extends Omit<EditorProps<HTMLContent>, 'autofocus' | 'contentType' | 'placeholder'> {
4
4
  customHandlers?: EditorCustomHandlers;
5
+ madeRaw?: boolean;
5
6
  }
6
7
  export interface EditorFullEmits extends EditorEmits<HTMLContent> {
7
8
  }
@@ -1,7 +1,19 @@
1
1
  <template>
2
+ <UTextarea
3
+ v-if="madeRaw"
4
+ v-bind="forwardedTextarea"
5
+ :ui="{
6
+ root: 'w-full px-6',
7
+ base: 'text-md text-default min-h-screen'
8
+ }"
9
+ variant="none"
10
+ placeholder="Введите текст..."
11
+ autoresize
12
+ />
2
13
  <UEditor
14
+ v-else
3
15
  v-slot="{ editor, handlers }"
4
- v-bind="forwarded"
16
+ v-bind="forwardedEditor"
5
17
  class="min-h-screen"
6
18
  content-type="markdown"
7
19
  placeholder="Введите '/' для выбора блока..."
@@ -74,7 +86,7 @@
74
86
 
75
87
  <script>
76
88
  import { useAppConfig } from "#imports";
77
- import { reactiveOmit } from "@vueuse/core";
89
+ import { reactiveOmit, reactivePick } from "@vueuse/core";
78
90
  import { useForwardPropsEmits } from "reka-ui";
79
91
  import { computed } from "vue";
80
92
  import { useEditorDragHandle } from "../composables/useEditorDragHandle";
@@ -87,6 +99,7 @@ import EditorLinkPopover from "./EditorLinkPopover.vue";
87
99
  <script setup>
88
100
  const props = defineProps({
89
101
  customHandlers: { type: Object, required: false },
102
+ madeRaw: { type: Boolean, required: false },
90
103
  as: { type: null, required: false },
91
104
  modelValue: { type: String, required: false },
92
105
  starterKit: { type: Object, required: false },
@@ -125,7 +138,8 @@ const props = defineProps({
125
138
  onDelete: { type: Function, required: false }
126
139
  });
127
140
  const emit = defineEmits(["update:modelValue"]);
128
- const forwarded = useForwardPropsEmits(reactiveOmit(props, "extensions", "customHandlers", "handlers"), emit);
141
+ const forwardedEditor = useForwardPropsEmits(reactiveOmit(props, "extensions", "customHandlers", "handlers", "madeRaw"), emit);
142
+ const forwardedTextarea = useForwardPropsEmits(reactivePick(props, "modelValue"), emit);
129
143
  const appConfig = useAppConfig();
130
144
  const extensions = computed(() => [
131
145
  UploraImageExtension,
@@ -2,6 +2,7 @@ import type { EditorCustomHandlers, EditorEmits, EditorProps } from '@nuxt/ui';
2
2
  import type { HTMLContent } from '@tiptap/vue-3';
3
3
  export interface EditorFullProps extends Omit<EditorProps<HTMLContent>, 'autofocus' | 'contentType' | 'placeholder'> {
4
4
  customHandlers?: EditorCustomHandlers;
5
+ madeRaw?: boolean;
5
6
  }
6
7
  export interface EditorFullEmits extends EditorEmits<HTMLContent> {
7
8
  }
@@ -13,6 +13,7 @@ export interface FormPanelProps<S extends FormSchema, T extends FormData<S> = Fo
13
13
  loading?: boolean;
14
14
  handler: (event: FormSubmitEvent<T>) => Promise<void>;
15
15
  asideDivide?: boolean;
16
+ bodyFit?: boolean;
16
17
  showReset?: boolean;
17
18
  class?: any;
18
19
  ui?: FormPanel['slots'];
@@ -60,6 +60,7 @@ const props = defineProps({
60
60
  loading: { type: Boolean, required: false },
61
61
  handler: { type: Function, required: true },
62
62
  asideDivide: { type: Boolean, required: false },
63
+ bodyFit: { type: Boolean, required: false, default: true },
63
64
  showReset: { type: Boolean, required: false },
64
65
  class: { type: null, required: false },
65
66
  ui: { type: null, required: false }
@@ -71,5 +72,8 @@ const formId = computed(() => props.formId ?? `form-${useId()}`);
71
72
  async function submitHandler(event) {
72
73
  await props.handler?.(event);
73
74
  }
74
- const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.formPanel || {} })());
75
+ const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.formPanel || {} })({
76
+ bodyFit: props.bodyFit,
77
+ asideDivide: props.asideDivide
78
+ }));
75
79
  </script>
@@ -13,6 +13,7 @@ export interface FormPanelProps<S extends FormSchema, T extends FormData<S> = Fo
13
13
  loading?: boolean;
14
14
  handler: (event: FormSubmitEvent<T>) => Promise<void>;
15
15
  asideDivide?: boolean;
16
+ bodyFit?: boolean;
16
17
  showReset?: boolean;
17
18
  class?: any;
18
19
  ui?: FormPanel['slots'];
@@ -1,10 +1,12 @@
1
1
  <template>
2
2
  <div :class="ui.root({ class: [props.ui?.root, props.class] })">
3
- <div v-if="title" :class="ui.title({ class: props.ui?.title })">
4
- {{ title }}
5
- </div>
6
- <div v-if="description" :class="ui.description({ class: props.ui?.description })">
7
- {{ description }}
3
+ <div v-if="title || description" :class="ui.header({ class: props.ui?.header })">
4
+ <div v-if="title" :class="ui.title({ class: props.ui?.title })">
5
+ {{ title }}
6
+ </div>
7
+ <div v-if="description" :class="ui.description({ class: props.ui?.description })">
8
+ {{ description }}
9
+ </div>
8
10
  </div>
9
11
  <div :class="ui.body({ class: props.ui?.body })">
10
12
  <slot />
@@ -89,7 +89,8 @@
89
89
  placeholder="Введите описание..."
90
90
  variant="none"
91
91
  size="xs"
92
- :ui="{ base: 'text-center' }"
92
+ :ui="{ base: 'text-center text-toned' }"
93
+ @update:model-value="onAltInput"
93
94
  />
94
95
  </UFormField>
95
96
  </UForm>
@@ -140,6 +141,14 @@ const { open, execute: uploadExecute, status: uploadStatus, reset: resetUpload,
140
141
  accept: "image/*"
141
142
  });
142
143
  const { execute: deleteExecute, onDeleted } = useUploraDelete();
144
+ function onAltInput(alt) {
145
+ modelValue.value = {
146
+ ...modelValue.value,
147
+ alt: alt || void 0
148
+ };
149
+ emitFormChange();
150
+ emitFormInput();
151
+ }
143
152
  function resetState() {
144
153
  modelValue.value = {
145
154
  image: void 0,
@@ -0,0 +1,8 @@
1
+ import type { SEO } from '../types/seo';
2
+ export interface TableCellSeoProps {
3
+ seo: SEO;
4
+ as?: any;
5
+ }
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
8
+ declare const __VLS_export: import("vue").DefineComponent<TableCellSeoProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<TableCellSeoProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <UBadge
3
+ :color="computeColor(rating)"
4
+ :label="`${rating}/100`"
5
+ variant="subtle"
6
+ />
7
+ </template>
8
+
9
+ <script>
10
+ import { computed } from "#imports";
11
+ import { useSeoStats } from "../composables/useSeoStats";
12
+ </script>
13
+
14
+ <script setup>
15
+ const props = defineProps({
16
+ seo: { type: Object, required: true },
17
+ as: { type: null, required: false }
18
+ });
19
+ const { title, description, computeColor } = useSeoStats(props.seo);
20
+ const rating = computed(() => {
21
+ return Math.round((title.value.progress + description.value.progress) / 2);
22
+ });
23
+ </script>
@@ -0,0 +1,8 @@
1
+ import type { SEO } from '../types/seo';
2
+ export interface TableCellSeoProps {
3
+ seo: SEO;
4
+ as?: any;
5
+ }
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
8
+ declare const __VLS_export: import("vue").DefineComponent<TableCellSeoProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<TableCellSeoProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -7,6 +7,7 @@ interface SEOStats {
7
7
  interface UseSeoStatsReturn {
8
8
  title: ComputedRef<SEOStats>;
9
9
  description: ComputedRef<SEOStats>;
10
+ computeColor: (score: number) => 'error' | 'warning' | 'success';
10
11
  }
11
12
  export declare function useSeoStats(options: MaybeRefOrGetter<SEO>): UseSeoStatsReturn;
12
13
  export {};
@@ -39,6 +39,7 @@ export function useSeoStats(options) {
39
39
  });
40
40
  return {
41
41
  title,
42
- description
42
+ description,
43
+ computeColor
43
44
  };
44
45
  }
@@ -16,6 +16,7 @@ export * from '../components/prose/UploraImage.vue';
16
16
  export * from '../components/TableColumnSorting.vue';
17
17
  export * from '../components/TableFilters.vue';
18
18
  export * from '../components/TablePanel.vue';
19
+ export * from '../components/TablePreviewSeo.vue';
19
20
  export * from '../components/TableSearchInput.vue';
20
21
  export * from '../components/UploraImage.vue';
21
22
  export * from '../composables/useAdmin';
@@ -16,6 +16,7 @@ export * from "../components/prose/UploraImage.vue";
16
16
  export * from "../components/TableColumnSorting.vue";
17
17
  export * from "../components/TableFilters.vue";
18
18
  export * from "../components/TablePanel.vue";
19
+ export * from "../components/TablePreviewSeo.vue";
19
20
  export * from "../components/TableSearchInput.vue";
20
21
  export * from "../components/UploraImage.vue";
21
22
  export * from "../composables/useAdmin.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hywax/cms",
3
3
  "type": "module",
4
- "version": "3.0.1",
4
+ "version": "3.2.0",
5
5
  "description": "Hywax CMS. ⚠️ This package is intended for internal use only.",
6
6
  "imports": {
7
7
  "#build/cms/*": "./.nuxt/cms/*.ts",
@@ -62,6 +62,7 @@
62
62
  "dependencies": {
63
63
  "@dicebear/collection": "^9.2.4",
64
64
  "@dicebear/core": "^9.2.4",
65
+ "@iconify-json/lucide": "^1.2.83",
65
66
  "@nuxt/kit": "^4.2.2",
66
67
  "@nuxt/ui": "^4.3.0",
67
68
  "@nuxtjs/mdc": "^0.19.2",
@@ -94,7 +95,7 @@
94
95
  "@vue/test-utils": "^2.4.6",
95
96
  "changelogen-monorepo": "^0.5.0",
96
97
  "eslint": "^9.39.2",
97
- "happy-dom": "^20.0.11",
98
+ "happy-dom": "^20.1.0",
98
99
  "husky": "^9.1.7",
99
100
  "lint-staged": "^16.2.7",
100
101
  "nuxt": "^4.2.2",