@hywax/cms 3.5.0 → 3.6.1

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,3 +1,6 @@
1
+ interface CmsConfig {
2
+ }
3
+
1
4
  const cmsConfig = {
2
5
  "name": "cms",
3
6
  "formats": {
@@ -15,4 +18,4 @@ const cmsConfig = {
15
18
  }
16
19
  }
17
20
 
18
- export const getCmsConfig = () => cmsConfig
21
+ export const useCmsConfig = () => cmsConfig
@@ -1,5 +1,15 @@
1
1
  export default {
2
2
  "slots": {
3
- "base": "rounded-lg"
3
+ "base": "rounded-lg",
4
+ "overlay": "fixed inset-0 bg-default/75 backdrop-blur-sm will-change-opacity",
5
+ "content": "fixed inset-0 flex items-center justify-center cursor-zoom-out focus:outline-none",
6
+ "zoomedImage": "w-full h-auto max-w-[95vw] max-h-[95vh] object-contain rounded-md"
7
+ },
8
+ "variants": {
9
+ "open": {
10
+ "false": {
11
+ "base": "cursor-zoom-in"
12
+ }
13
+ }
4
14
  }
5
15
  }
package/.nuxt/cms/toc.ts CHANGED
@@ -1,11 +1,14 @@
1
1
  export default {
2
2
  "slots": {
3
3
  "root": "",
4
- "list": "min-w-0",
4
+ "header": "flex items-center gap-2 text-sm font-medium mb-2",
5
+ "icon": "size-4",
6
+ "title": "text-sm font-medium",
7
+ "list": "min-w-0 border-default border-l-2 pl-4 ml-0.5",
5
8
  "listWithChildren": "ms-3",
6
9
  "item": "min-w-0",
7
10
  "itemWithChildren": "",
8
- "link": "group relative text-sm flex items-center focus-visible:outline-primary py-1 text-muted hover:text-default transition-colors",
11
+ "link": "group relative text-sm flex items-center focus-visible:outline-primary py-1 font-medium text-muted hover:text-default transition-colors",
9
12
  "linkText": "truncate"
10
13
  },
11
14
  "variants": {
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hywax/cms",
3
- "version": "3.5.0",
3
+ "version": "3.6.1",
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, camelCase } from 'scule';
7
7
  import { globSync } from 'tinyglobby';
8
8
 
9
9
  const name = "@hywax/cms";
10
- const version = "3.5.0";
10
+ const version = "3.6.1";
11
11
 
12
12
  function createContext(options, nuxt) {
13
13
  const { resolve } = createResolver(import.meta.url);
@@ -114,14 +114,14 @@ async function prepareAutoImports({ resolve, options, nuxt }) {
114
114
  addPlugin(resolve("./runtime/plugins/api"));
115
115
  addPlugin(resolve("./runtime/plugins/zod"));
116
116
  addImports([
117
- { name: "getCmsConfig", from: cmsConfigPath }
117
+ { name: "useCmsConfig", from: cmsConfigPath }
118
118
  ]);
119
119
  addImportsDir([
120
120
  resolve("./runtime/utils"),
121
121
  resolve("./runtime/composables")
122
122
  ]);
123
123
  addServerImports([
124
- { name: "getCmsConfig", from: cmsConfigPath }
124
+ { name: "useCmsConfig", from: cmsConfigPath }
125
125
  ]);
126
126
  addServerImportsDir([
127
127
  resolve("./runtime/utils"),
@@ -182,7 +182,8 @@ const icons = {
182
182
  code: "i-lucide-code",
183
183
  image: "i-lucide-image",
184
184
  editLine: "i-lucide-pen-line",
185
- save: "i-lucide-cloud-check"
185
+ save: "i-lucide-cloud-check",
186
+ toc: "i-lucide-text-align-start"
186
187
  };
187
188
 
188
189
  async function buildComponentDependencyGraph(componentDir, componentPattern) {
@@ -539,11 +540,14 @@ const tableSearchInput = {
539
540
  const toc = {
540
541
  slots: {
541
542
  root: "",
542
- list: "min-w-0",
543
+ header: "flex items-center gap-2 text-sm font-medium mb-2",
544
+ icon: "size-4",
545
+ title: "text-sm font-medium",
546
+ list: "min-w-0 border-default border-l-2 pl-4 ml-0.5",
543
547
  listWithChildren: "ms-3",
544
548
  item: "min-w-0",
545
549
  itemWithChildren: "",
546
- link: "group relative text-sm flex items-center focus-visible:outline-primary py-1 text-muted hover:text-default transition-colors",
550
+ link: "group relative text-sm flex items-center focus-visible:outline-primary py-1 font-medium text-muted hover:text-default transition-colors",
547
551
  linkText: "truncate"
548
552
  },
549
553
  variants: {
@@ -583,7 +587,17 @@ const theme = {
583
587
 
584
588
  const uploraImage = {
585
589
  slots: {
586
- base: "rounded-lg"
590
+ base: "rounded-lg",
591
+ overlay: "fixed inset-0 bg-default/75 backdrop-blur-sm will-change-opacity",
592
+ content: "fixed inset-0 flex items-center justify-center cursor-zoom-out focus:outline-none",
593
+ zoomedImage: "w-full h-auto max-w-[95vw] max-h-[95vh] object-contain rounded-md"
594
+ },
595
+ variants: {
596
+ open: {
597
+ false: {
598
+ base: "cursor-zoom-in"
599
+ }
600
+ }
587
601
  }
588
602
  };
589
603
 
@@ -768,7 +782,7 @@ export {}
768
782
  };
769
783
  return `const cmsConfig = ${JSON.stringify(config, null, 2)}
770
784
 
771
- export const getCmsConfig = () => cmsConfig`;
785
+ export const useCmsConfig = () => cmsConfig`;
772
786
  }
773
787
  });
774
788
  return templates;
@@ -4,8 +4,9 @@ import type { ComponentConfig } from '../types';
4
4
  import theme from '#build/cms/toc';
5
5
  type Toc = ComponentConfig<typeof theme, AppConfig, 'toc'>;
6
6
  export interface TocProps {
7
+ title?: string;
7
8
  links: TocLink[];
8
- containerRef?: HTMLElement | null;
9
+ container?: string;
9
10
  class?: any;
10
11
  ui?: Toc['slots'];
11
12
  }
@@ -17,7 +18,7 @@ declare const __VLS_export: import("vue").DefineComponent<TocProps, {}, {}, {},
17
18
  }, string, import("vue").PublicProps, Readonly<TocProps> & Readonly<{
18
19
  onMove?: ((args_0: string) => any) | undefined;
19
20
  }>, {
20
- containerRef: HTMLElement | null;
21
+ title: string;
21
22
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
22
23
  declare const _default: typeof __VLS_export;
23
24
  export default _default;
@@ -21,6 +21,14 @@
21
21
  </DefineListTemplate>
22
22
 
23
23
  <div :class="ui.root({ class: [props.ui?.root, props.class] })">
24
+ <div :class="ui.header({ class: props.ui?.header })">
25
+ <UIcon :class="ui.icon({ class: props.ui?.icon })" :name="appConfig.ui.icons.toc" />
26
+
27
+ <span :class="ui.title({ class: props.ui?.title })">
28
+ {{ title }}
29
+ </span>
30
+ </div>
31
+
24
32
  <ReuseListTemplate :links="props.links" :level="0" />
25
33
  </div>
26
34
  </template>
@@ -32,8 +40,9 @@ import { createReusableTemplate } from "@vueuse/core";
32
40
  import { computed } from "vue";
33
41
  import { tv } from "../tv";
34
42
  const props = defineProps({
43
+ title: { type: String, required: false, default: "\u0421\u043E\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435" },
35
44
  links: { type: Array, required: true },
36
- containerRef: { type: null, required: false, default: null },
45
+ container: { type: String, required: false },
37
46
  class: { type: null, required: false },
38
47
  ui: { type: null, required: false }
39
48
  });
@@ -55,11 +64,11 @@ function scrollToHeading(id) {
55
64
  const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.toc || {} })());
56
65
  const nuxtApp = useNuxtApp();
57
66
  nuxtApp.hooks.hook("page:loading:end", () => {
58
- const headings = Array.from((props.containerRef || document)?.querySelectorAll("h2, h3") ?? []);
67
+ const headings = Array.from(document.querySelector(props.container ?? "body")?.querySelectorAll("h2, h3") ?? []);
59
68
  updateHeadings(headings);
60
69
  });
61
70
  nuxtApp.hooks.hook("page:transition:finish", () => {
62
- const headings = Array.from((props.containerRef || document)?.querySelectorAll("h2, h3") ?? []);
71
+ const headings = Array.from(document.querySelector(props.container ?? "body")?.querySelectorAll("h2, h3") ?? []);
63
72
  updateHeadings(headings);
64
73
  });
65
74
  </script>
@@ -4,8 +4,9 @@ import type { ComponentConfig } from '../types';
4
4
  import theme from '#build/cms/toc';
5
5
  type Toc = ComponentConfig<typeof theme, AppConfig, 'toc'>;
6
6
  export interface TocProps {
7
+ title?: string;
7
8
  links: TocLink[];
8
- containerRef?: HTMLElement | null;
9
+ container?: string;
9
10
  class?: any;
10
11
  ui?: Toc['slots'];
11
12
  }
@@ -17,7 +18,7 @@ declare const __VLS_export: import("vue").DefineComponent<TocProps, {}, {}, {},
17
18
  }, string, import("vue").PublicProps, Readonly<TocProps> & Readonly<{
18
19
  onMove?: ((args_0: string) => any) | undefined;
19
20
  }>, {
20
- containerRef: HTMLElement | null;
21
+ title: string;
21
22
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
22
23
  declare const _default: typeof __VLS_export;
23
24
  export default _default;
@@ -1,10 +1,11 @@
1
1
  import type { AppConfig } from '@nuxt/schema';
2
2
  import type { ImgHTMLAttributes } from 'vue';
3
3
  import type { ComponentConfig, ImageFormat, ImageSize } from '../types';
4
+ import type { BuildUploraImage } from '../utils';
4
5
  import theme from '#build/cms/uplora-image';
5
6
  export type UploraImage = ComponentConfig<typeof theme, AppConfig, 'uploraImage'>;
6
7
  export interface UploraImageProps {
7
- image: string;
8
+ image: string | BuildUploraImage;
8
9
  alt?: string;
9
10
  formats?: ImageFormat[];
10
11
  sizes?: ImageSize[];
@@ -33,7 +33,7 @@
33
33
 
34
34
  <script>
35
35
  import theme from "#build/cms/uplora-image";
36
- import { getCmsConfig, useAppConfig, useHead, useNuxtApp } from "#imports";
36
+ import { useAppConfig, useCmsConfig, useHead, useNuxtApp } from "#imports";
37
37
  import { computed, onMounted, useTemplateRef } from "vue";
38
38
  import { tv } from "../tv";
39
39
  import { buildUploraImage } from "../utils";
@@ -41,7 +41,7 @@ import { buildUploraImage } from "../utils";
41
41
 
42
42
  <script setup>
43
43
  const props = defineProps({
44
- image: { type: String, required: true },
44
+ image: { type: [String, Object], required: true },
45
45
  alt: { type: String, required: false },
46
46
  formats: { type: Array, required: false },
47
47
  sizes: { type: Array, required: false },
@@ -56,14 +56,14 @@ const props = defineProps({
56
56
  });
57
57
  const emit = defineEmits(["load", "error"]);
58
58
  const appConfig = useAppConfig();
59
- const cmsConfig = getCmsConfig();
59
+ const cmsConfig = useCmsConfig();
60
60
  const nuxtApp = useNuxtApp();
61
61
  const initialLoad = nuxtApp.isHydrating;
62
- const image = computed(() => buildUploraImage({
62
+ const image = computed(() => typeof props.image === "string" ? buildUploraImage({
63
63
  id: props.image,
64
64
  formats: props.formats ?? cmsConfig?.components?.uploraImage?.formats,
65
65
  sizes: props.sizes
66
- }));
66
+ }) : props.image);
67
67
  const sources = computed(() => {
68
68
  if (image.value.sources.length > 1) {
69
69
  return image.value.sources.slice(1);
@@ -1,10 +1,11 @@
1
1
  import type { AppConfig } from '@nuxt/schema';
2
2
  import type { ImgHTMLAttributes } from 'vue';
3
3
  import type { ComponentConfig, ImageFormat, ImageSize } from '../types';
4
+ import type { BuildUploraImage } from '../utils';
4
5
  import theme from '#build/cms/uplora-image';
5
6
  export type UploraImage = ComponentConfig<typeof theme, AppConfig, 'uploraImage'>;
6
7
  export interface UploraImageProps {
7
- image: string;
8
+ image: string | BuildUploraImage;
8
9
  alt?: string;
9
10
  formats?: ImageFormat[];
10
11
  sizes?: ImageSize[];
@@ -1,9 +1,10 @@
1
1
  import type { AppConfig } from '@nuxt/schema';
2
- import type { ComponentConfig, ImageSize } from '../../types';
2
+ import type { ComponentConfig, ImageFormat, ImageSize } from '../../types';
3
3
  import theme from '#build/cms/prose/uplora-image';
4
4
  export type ProseUploraImage = ComponentConfig<typeof theme, AppConfig, 'uploraImage', 'cms.prose'>;
5
5
  export interface ProseUploraImageProps {
6
- image?: string;
6
+ image: string;
7
+ formats?: ImageFormat[];
7
8
  alt?: string;
8
9
  color?: string;
9
10
  sizes?: ImageSize[];
@@ -1,25 +1,61 @@
1
1
  <template>
2
- <BaseUploraImage
3
- v-if="image"
4
- :image="image"
5
- :class="ui.base({ class: [props.ui?.base, props.class] })"
6
- :alt="alt"
7
- :color="color"
8
- :sizes="sizes"
9
- />
2
+ <DialogRoot
3
+ v-slot="{ close }"
4
+ v-model:open="open"
5
+ :modal="false"
6
+ >
7
+ <DialogTrigger as-child>
8
+ <Motion :layout-id="layoutId" as-child :transition="{ type: 'spring', bounce: 0.15, duration: 0.5, ease: 'easeInOut' }">
9
+ <BaseUploraImage
10
+ v-if="image"
11
+ :image="image"
12
+ :class="ui.base({ class: [props.ui?.base, props.class] })"
13
+ :alt="alt"
14
+ :color="color"
15
+ />
16
+ </Motion>
17
+ </DialogTrigger>
18
+
19
+ <DialogPortal>
20
+ <AnimatePresence>
21
+ <Motion
22
+ v-if="open"
23
+ :initial="{ opacity: 0 }"
24
+ :animate="{ opacity: 1 }"
25
+ :exit="{ opacity: 0 }"
26
+ :class="ui.overlay({ class: [props.ui?.overlay] })"
27
+ />
28
+
29
+ <div v-if="open" :class="ui.content({ class: [props.ui?.content] })" @click="close">
30
+ <Motion as-child :layout-id="layoutId" :transition="{ type: 'spring', bounce: 0.15, duration: 0.5, ease: 'easeInOut' }">
31
+ <img
32
+ :src="image.original"
33
+ :alt="alt"
34
+ :class="ui.zoomedImage({ class: props.ui?.zoomedImage })"
35
+ >
36
+ </Motion>
37
+ </div>
38
+ </AnimatePresence>
39
+ </DialogPortal>
40
+ </DialogRoot>
10
41
  </template>
11
42
 
12
43
  <script>
13
44
  import theme from "#build/cms/prose/uplora-image";
14
- import { getCmsConfig, useAppConfig } from "#imports";
15
- import { computed } from "vue";
45
+ import { useAppConfig, useCmsConfig } from "#imports";
46
+ import { useEventListener } from "@vueuse/core";
47
+ import { AnimatePresence, Motion } from "motion-v";
48
+ import { DialogPortal, DialogRoot, DialogTrigger } from "reka-ui";
49
+ import { computed, onMounted, ref, useId } from "vue";
16
50
  import { tv } from "../../tv";
51
+ import { buildUploraImage } from "../../utils";
17
52
  import BaseUploraImage from "../UploraImage.vue";
18
53
  </script>
19
54
 
20
55
  <script setup>
21
56
  const props = defineProps({
22
- image: { type: String, required: false },
57
+ image: { type: String, required: true },
58
+ formats: { type: Array, required: false },
23
59
  alt: { type: String, required: false },
24
60
  color: { type: String, required: false },
25
61
  sizes: { type: Array, required: false },
@@ -27,7 +63,20 @@ const props = defineProps({
27
63
  ui: { type: null, required: false }
28
64
  });
29
65
  const appConfig = useAppConfig();
30
- const cmsConfig = getCmsConfig();
31
- const sizes = props.sizes ?? cmsConfig?.components?.prose?.uploraImage?.sizes;
32
- const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.prose?.uploraImage || {} })());
66
+ const cmsConfig = useCmsConfig();
67
+ const layoutId = computed(() => `${props.image}::${useId()}`);
68
+ const open = ref(false);
69
+ const image = computed(() => buildUploraImage({
70
+ id: props.image,
71
+ formats: props.formats ?? cmsConfig?.components?.uploraImage?.formats,
72
+ sizes: props.sizes ?? cmsConfig?.components?.prose?.uploraImage?.sizes
73
+ }));
74
+ const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.prose?.uploraImage || {} })({
75
+ open: open.value
76
+ }));
77
+ onMounted(() => {
78
+ useEventListener(window, "scroll", () => {
79
+ open.value = false;
80
+ });
81
+ });
33
82
  </script>
@@ -1,9 +1,10 @@
1
1
  import type { AppConfig } from '@nuxt/schema';
2
- import type { ComponentConfig, ImageSize } from '../../types';
2
+ import type { ComponentConfig, ImageFormat, ImageSize } from '../../types';
3
3
  import theme from '#build/cms/prose/uplora-image';
4
4
  export type ProseUploraImage = ComponentConfig<typeof theme, AppConfig, 'uploraImage', 'cms.prose'>;
5
5
  export interface ProseUploraImageProps {
6
- image?: string;
6
+ image: string;
7
+ formats?: ImageFormat[];
7
8
  alt?: string;
8
9
  color?: string;
9
10
  sizes?: ImageSize[];
@@ -1,5 +1,5 @@
1
- import { getCmsConfig } from "#imports";
2
- export const authRedirectToCookie = `${getCmsConfig().name}-auth-redirect`;
1
+ import { useCmsConfig } from "#imports";
2
+ export const authRedirectToCookie = `${useCmsConfig().name}-auth-redirect`;
3
3
  export function getAuthRedirectTo(redirectTo) {
4
4
  return redirectTo ? decodeURIComponent(redirectTo) : "/";
5
5
  }
@@ -1,4 +1,4 @@
1
- import { getCmsConfig } from "#imports";
1
+ import { useCmsConfig } from "#imports";
2
2
  import { formatDate as format, normalizeDate } from "@vueuse/core";
3
3
  export function formatDateTime(date) {
4
4
  return normalizeDate(date).toLocaleString("ru-RU");
@@ -10,6 +10,6 @@ export function formatTime(date) {
10
10
  return normalizeDate(date).toLocaleTimeString("ru-RU");
11
11
  }
12
12
  export function formatServerDateTime(date) {
13
- const { formats } = getCmsConfig();
13
+ const { formats } = useCmsConfig();
14
14
  return format(normalizeDate(date), formats.serverDateTime);
15
15
  }
@@ -1,6 +1,6 @@
1
- import { getCmsConfig } from "#imports";
1
+ import { useCmsConfig } from "#imports";
2
2
  import { kebabCase } from "scule";
3
- const storagePrefix = `${getCmsConfig().name}:`;
3
+ const storagePrefix = `${useCmsConfig().name}:`;
4
4
  export const getCmsStorageKey = (key) => `${storagePrefix}${kebabCase(key)}`;
5
5
  export function clearCmsStorage() {
6
6
  for (const key in localStorage) {
@@ -9,8 +9,9 @@ export interface BuildUploraImageOptions {
9
9
  id: string;
10
10
  formats?: ImageFormat[];
11
11
  sizes?: ImageSize[];
12
+ originalWithFormat?: boolean;
12
13
  }
13
- export interface BuildUploraImageReturn {
14
+ export interface BuildUploraImage {
14
15
  img: string;
15
16
  original: string;
16
17
  sources: ImageSource[];
@@ -19,7 +20,7 @@ export interface BuildUploraImageReturn {
19
20
  /**
20
21
  * Создает URL изображения из Uplora
21
22
  */
22
- export declare function buildUploraImage(options: BuildUploraImageOptions): BuildUploraImageReturn;
23
+ export declare function buildUploraImage(options: BuildUploraImageOptions): BuildUploraImage;
23
24
  export interface GenerateImageSizesOptions {
24
25
  width: number;
25
26
  minWidth?: number;
@@ -14,7 +14,7 @@ export function createUploraImageResolver() {
14
14
  }
15
15
  export function buildUploraImage(options) {
16
16
  const resolver = createUploraImageResolver();
17
- const original = resolver(options.id);
17
+ let original = resolver(options.id);
18
18
  const sizes = options.sizes ?? [{ descriptor: "1x" }];
19
19
  const sources = [];
20
20
  function makeSrcset(format) {
@@ -23,7 +23,7 @@ export function buildUploraImage(options) {
23
23
  }
24
24
  return null;
25
25
  }
26
- if (options.formats) {
26
+ if (options.formats?.length) {
27
27
  for (const format of options.formats) {
28
28
  const srcset = makeSrcset(format);
29
29
  sources.push({
@@ -38,6 +38,9 @@ export function buildUploraImage(options) {
38
38
  type: imagesExtensionsToMimeTypes[format]
39
39
  });
40
40
  }
41
+ if (options.originalWithFormat) {
42
+ original = resolver(options.id, { format: options.formats[0] });
43
+ }
41
44
  } else {
42
45
  const srcset = makeSrcset();
43
46
  sources.push({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hywax/cms",
3
3
  "type": "module",
4
- "version": "3.5.0",
4
+ "version": "3.6.1",
5
5
  "description": "Hywax CMS. ⚠️ This package is intended for internal use only.",
6
6
  "imports": {
7
7
  "#build/cms/*": "./.nuxt/cms/*.ts",