@hywax/cms 3.4.3 → 3.6.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.
@@ -9,5 +9,6 @@ export { default as formUploraImage } from './form-uplora-image'
9
9
  export { default as modalConfirm } from './modal-confirm'
10
10
  export { default as tablePanel } from './table-panel'
11
11
  export { default as tableSearchInput } from './table-search-input'
12
+ export { default as toc } from './toc'
12
13
  export { default as uploraImage } from './uplora-image'
13
14
  export * as prose from './prose'
@@ -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
  }
@@ -0,0 +1,21 @@
1
+ export default {
2
+ "slots": {
3
+ "root": "",
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",
8
+ "listWithChildren": "ms-3",
9
+ "item": "min-w-0",
10
+ "itemWithChildren": "",
11
+ "link": "group relative text-sm flex items-center focus-visible:outline-primary py-1 font-medium text-muted hover:text-default transition-colors",
12
+ "linkText": "truncate"
13
+ },
14
+ "variants": {
15
+ "active": {
16
+ "true": {
17
+ "link": "text-primary"
18
+ }
19
+ }
20
+ }
21
+ }
package/.nuxt/cms.css CHANGED
@@ -1,4 +1,5 @@
1
1
  @source "./cms/prose";
2
+ @source "./cms/toc.ts";
2
3
  @source "./cms/form-panel.ts";
3
4
  @source "./cms/form-panel-section.ts";
4
5
  @source "./cms/form-slug.ts";
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hywax/cms",
3
- "version": "3.4.3",
3
+ "version": "3.6.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, camelCase } from 'scule';
7
7
  import { globSync } from 'tinyglobby';
8
8
 
9
9
  const name = "@hywax/cms";
10
- const version = "3.4.3";
10
+ const version = "3.6.0";
11
11
 
12
12
  function createContext(options, nuxt) {
13
13
  const { resolve } = createResolver(import.meta.url);
@@ -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) {
@@ -536,6 +537,28 @@ const tableSearchInput = {
536
537
  }
537
538
  };
538
539
 
540
+ const toc = {
541
+ slots: {
542
+ root: "",
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",
547
+ listWithChildren: "ms-3",
548
+ item: "min-w-0",
549
+ itemWithChildren: "",
550
+ link: "group relative text-sm flex items-center focus-visible:outline-primary py-1 font-medium text-muted hover:text-default transition-colors",
551
+ linkText: "truncate"
552
+ },
553
+ variants: {
554
+ active: {
555
+ true: {
556
+ link: "text-primary"
557
+ }
558
+ }
559
+ }
560
+ };
561
+
539
562
  const uploraImage$1 = {
540
563
  slots: {
541
564
  root: "relative grid grid-cols-[100%] grid-rows-[100%] overflow-hidden",
@@ -558,12 +581,23 @@ const theme = {
558
581
  modalConfirm: modalConfirm,
559
582
  tablePanel: tablePanel,
560
583
  tableSearchInput: tableSearchInput,
584
+ toc: toc,
561
585
  uploraImage: uploraImage$1
562
586
  };
563
587
 
564
588
  const uploraImage = {
565
589
  slots: {
566
- 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
+ }
567
601
  }
568
602
  };
569
603
 
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="relative flex items-center gap-2">
3
- <div v-if="image" class="overflow-hidden rounded-lg shrink-0">
3
+ <div v-if="image" class="overflow-hidden rounded-lg shrink-0 size-8">
4
4
  <CUploraImage
5
5
  v-bind="image"
6
6
  :sizes="[{ width: 32, height: 32, descriptor: '1x' }]"
@@ -0,0 +1,24 @@
1
+ import type { AppConfig } from '@nuxt/schema';
2
+ import type { TocLink } from '@nuxtjs/mdc';
3
+ import type { ComponentConfig } from '../types';
4
+ import theme from '#build/cms/toc';
5
+ type Toc = ComponentConfig<typeof theme, AppConfig, 'toc'>;
6
+ export interface TocProps {
7
+ title?: string;
8
+ links: TocLink[];
9
+ container?: string;
10
+ class?: any;
11
+ ui?: Toc['slots'];
12
+ }
13
+ export interface TocEmits {
14
+ move: [string];
15
+ }
16
+ declare const __VLS_export: import("vue").DefineComponent<TocProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
17
+ move: (args_0: string) => any;
18
+ }, string, import("vue").PublicProps, Readonly<TocProps> & Readonly<{
19
+ onMove?: ((args_0: string) => any) | undefined;
20
+ }>, {
21
+ title: string;
22
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
+ declare const _default: typeof __VLS_export;
24
+ export default _default;
@@ -0,0 +1,74 @@
1
+ <template>
2
+ <!-- eslint-disable-next-line vue/no-template-shadow -->
3
+ <DefineListTemplate v-slot="{ links, level }">
4
+ <ul :class="level > 0 ? ui.listWithChildren({ class: props.ui?.listWithChildren }) : ui.list({ class: props.ui?.list })">
5
+ <li v-for="(link, index) in links" :key="index" :class="link.children && link.children.length > 0 ? ui.itemWithChildren({ class: props.ui?.itemWithChildren }) : ui.item({ class: props.ui?.item })">
6
+ <a
7
+ :href="`#${link.id}`"
8
+ :class="ui.link({ class: props.ui?.link, active: activeHeadings.includes(link.id) })"
9
+ @click="scrollToHeading(link.id)"
10
+ >
11
+ {{ link.text }}
12
+ </a>
13
+
14
+ <ReuseListTemplate
15
+ v-if="link.children?.length"
16
+ :links="link.children"
17
+ :level="level + 1"
18
+ />
19
+ </li>
20
+ </ul>
21
+ </DefineListTemplate>
22
+
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
+
32
+ <ReuseListTemplate :links="props.links" :level="0" />
33
+ </div>
34
+ </template>
35
+
36
+ <script setup>
37
+ import theme from "#build/cms/toc";
38
+ import { useAppConfig, useNuxtApp, useRouter, useScrollspy } from "#imports";
39
+ import { createReusableTemplate } from "@vueuse/core";
40
+ import { computed } from "vue";
41
+ import { tv } from "../tv";
42
+ const props = defineProps({
43
+ title: { type: String, required: false, default: "\u0421\u043E\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435" },
44
+ links: { type: Array, required: true },
45
+ container: { type: String, required: false },
46
+ class: { type: null, required: false },
47
+ ui: { type: null, required: false }
48
+ });
49
+ const emits = defineEmits(["move"]);
50
+ const router = useRouter();
51
+ const appConfig = useAppConfig();
52
+ const { activeHeadings, updateHeadings } = useScrollspy();
53
+ const [DefineListTemplate, ReuseListTemplate] = createReusableTemplate({
54
+ props: {
55
+ links: Array,
56
+ level: Number
57
+ }
58
+ });
59
+ function scrollToHeading(id) {
60
+ const encodedId = encodeURIComponent(id);
61
+ router.push(`#${encodedId}`);
62
+ emits("move", id);
63
+ }
64
+ const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.toc || {} })());
65
+ const nuxtApp = useNuxtApp();
66
+ nuxtApp.hooks.hook("page:loading:end", () => {
67
+ const headings = Array.from(document.querySelector(props.container ?? "body")?.querySelectorAll("h2, h3") ?? []);
68
+ updateHeadings(headings);
69
+ });
70
+ nuxtApp.hooks.hook("page:transition:finish", () => {
71
+ const headings = Array.from(document.querySelector(props.container ?? "body")?.querySelectorAll("h2, h3") ?? []);
72
+ updateHeadings(headings);
73
+ });
74
+ </script>
@@ -0,0 +1,24 @@
1
+ import type { AppConfig } from '@nuxt/schema';
2
+ import type { TocLink } from '@nuxtjs/mdc';
3
+ import type { ComponentConfig } from '../types';
4
+ import theme from '#build/cms/toc';
5
+ type Toc = ComponentConfig<typeof theme, AppConfig, 'toc'>;
6
+ export interface TocProps {
7
+ title?: string;
8
+ links: TocLink[];
9
+ container?: string;
10
+ class?: any;
11
+ ui?: Toc['slots'];
12
+ }
13
+ export interface TocEmits {
14
+ move: [string];
15
+ }
16
+ declare const __VLS_export: import("vue").DefineComponent<TocProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
17
+ move: (args_0: string) => any;
18
+ }, string, import("vue").PublicProps, Readonly<TocProps> & Readonly<{
19
+ onMove?: ((args_0: string) => any) | undefined;
20
+ }>, {
21
+ title: string;
22
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
+ declare const _default: typeof __VLS_export;
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[];
@@ -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 },
@@ -59,11 +59,11 @@ const appConfig = useAppConfig();
59
59
  const cmsConfig = getCmsConfig();
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
45
  import { getCmsConfig, useAppConfig } from "#imports";
15
- import { computed } from "vue";
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 },
@@ -28,6 +64,19 @@ const props = defineProps({
28
64
  });
29
65
  const appConfig = useAppConfig();
30
66
  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 || {} })());
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[];
@@ -18,6 +18,7 @@ export * from '../components/TableFilters.vue';
18
18
  export * from '../components/TablePanel.vue';
19
19
  export * from '../components/TablePreviewSeo.vue';
20
20
  export * from '../components/TableSearchInput.vue';
21
+ export * from '../components/Toc.vue';
21
22
  export * from '../components/UploraImage.vue';
22
23
  export * from '../composables/useAdmin';
23
24
  export * from '../composables/useApi';
@@ -18,6 +18,7 @@ export * from "../components/TableFilters.vue";
18
18
  export * from "../components/TablePanel.vue";
19
19
  export * from "../components/TablePreviewSeo.vue";
20
20
  export * from "../components/TableSearchInput.vue";
21
+ export * from "../components/Toc.vue";
21
22
  export * from "../components/UploraImage.vue";
22
23
  export * from "../composables/useAdmin.js";
23
24
  export * from "../composables/useApi.js";
@@ -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.4.3",
4
+ "version": "3.6.0",
5
5
  "description": "Hywax CMS. ⚠️ This package is intended for internal use only.",
6
6
  "imports": {
7
7
  "#build/cms/*": "./.nuxt/cms/*.ts",