@hywax/cms 0.0.2 → 0.0.4

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.
Files changed (63) hide show
  1. package/.nuxt/cms/http-codes.ts +8 -0
  2. package/.nuxt/cms/index.ts +2 -1
  3. package/.nuxt/cms/uplora-image.ts +8 -0
  4. package/cli/commands/make/component.mjs +75 -0
  5. package/cli/commands/make/index.mjs +12 -0
  6. package/cli/index.mjs +15 -0
  7. package/cli/package.json +13 -0
  8. package/cli/templates.mjs +101 -0
  9. package/cli/utils.mjs +31 -0
  10. package/dist/module.d.mts +27 -3
  11. package/dist/module.json +1 -1
  12. package/dist/module.mjs +122 -31
  13. package/dist/runtime/components/ButtonClear.vue +1 -2
  14. package/dist/runtime/components/UploraImage.vue +108 -0
  15. package/dist/runtime/components/UploraImage.vue.d.ts +34 -0
  16. package/dist/runtime/components/prose/UploraImage.vue +1 -2
  17. package/dist/runtime/composables/useApi.d.ts +14 -0
  18. package/dist/runtime/composables/useApi.js +16 -0
  19. package/dist/runtime/composables/useSeoStats.d.ts +12 -0
  20. package/dist/runtime/composables/useSeoStats.js +44 -0
  21. package/dist/runtime/plugins/api.d.ts +6 -0
  22. package/dist/runtime/plugins/api.js +23 -0
  23. package/dist/runtime/server/errors/InternalHttpError.d.ts +4 -0
  24. package/dist/runtime/server/errors/InternalHttpError.js +6 -0
  25. package/dist/runtime/server/errors/TimeoutError.d.ts +2 -0
  26. package/dist/runtime/server/errors/TimeoutError.js +2 -0
  27. package/dist/runtime/server/errors/index.d.ts +2 -0
  28. package/dist/runtime/server/errors/index.js +2 -0
  29. package/dist/runtime/server/types/errors.d.ts +8 -0
  30. package/dist/runtime/server/types/errors.js +0 -0
  31. package/dist/runtime/server/types/index.d.ts +1 -0
  32. package/dist/runtime/server/types/index.js +1 -0
  33. package/dist/runtime/server/utils/errors.d.ts +8 -0
  34. package/dist/runtime/server/utils/errors.js +57 -0
  35. package/dist/runtime/server/utils/httpHandler.d.ts +10 -0
  36. package/dist/runtime/server/utils/httpHandler.js +15 -0
  37. package/dist/runtime/server/utils/pagination.d.ts +11 -0
  38. package/dist/runtime/server/utils/pagination.js +12 -0
  39. package/dist/runtime/server/utils/timeout.d.ts +7 -0
  40. package/dist/runtime/server/utils/timeout.js +9 -0
  41. package/dist/runtime/server/utils/validation.d.ts +3 -0
  42. package/dist/runtime/server/utils/validation.js +26 -0
  43. package/dist/runtime/types/image.d.ts +48 -0
  44. package/dist/runtime/types/image.js +0 -0
  45. package/dist/runtime/types/index.d.ts +4 -0
  46. package/dist/runtime/types/index.js +4 -0
  47. package/dist/runtime/types/query.d.ts +20 -0
  48. package/dist/runtime/types/query.js +0 -0
  49. package/dist/runtime/types/seo.d.ts +4 -0
  50. package/dist/runtime/types/seo.js +0 -0
  51. package/dist/runtime/types/utils.d.ts +4 -1
  52. package/dist/runtime/utils/avatar.d.ts +1 -0
  53. package/dist/runtime/utils/avatar.js +9 -0
  54. package/dist/runtime/utils/dictionaries.d.ts +4 -0
  55. package/dist/runtime/utils/dictionaries.js +6 -0
  56. package/dist/runtime/utils/image.d.ts +30 -0
  57. package/dist/runtime/utils/image.js +81 -0
  58. package/dist/runtime/utils/index.d.ts +4 -1
  59. package/dist/runtime/utils/index.js +4 -0
  60. package/dist/runtime/utils/slugify.d.ts +1 -0
  61. package/dist/runtime/utils/slugify.js +12 -0
  62. package/dist/types.d.mts +6 -2
  63. package/package.json +17 -3
@@ -0,0 +1,8 @@
1
+ export const HTTP_CODE_BAD_REQUEST = '400: Неверный запрос'
2
+ export const HTTP_CODE_UNAUTHORIZED = '401: Не авторизован'
3
+ export const HTTP_CODE_FORBIDDEN = '403: Доступ запрещен'
4
+ export const HTTP_CODE_NOT_FOUND = '404: Не найдено'
5
+ export const HTTP_CODE_REQUEST_TIMEOUT = '408: Время ожидания запроса истекло'
6
+ export const HTTP_CODE_INTERNAL_SERVER_ERROR = '500: Внутренняя ошибка сервера'
7
+ export const HTTP_CODE_BAD_GATEWAY = '502: Ошибка шлюза'
8
+ export const HTTP_CODE_SERVICE_UNAVAILABLE = '503: Сервис недоступен'
@@ -1 +1,2 @@
1
- export { default as buttonClear } from './button-clear'
1
+ export { default as buttonClear } from './button-clear'
2
+ export { default as uploraImage } from './uplora-image'
@@ -0,0 +1,8 @@
1
+ export default {
2
+ "slots": {
3
+ "root": "relative grid grid-cols-[100%] grid-rows-[100%] overflow-hidden",
4
+ "lqip": "bg-cover bg-no-repeat bg-center col-[1] row-[1]",
5
+ "picture": "aspect-3/2 col-[1] row-[1]",
6
+ "img": "w-full h-full block object-cover"
7
+ }
8
+ }
@@ -0,0 +1,75 @@
1
+ import { existsSync, promises as fsp } from 'node:fs'
2
+ import process from 'node:process'
3
+ import { defineCommand } from 'citty'
4
+ import { consola } from 'consola'
5
+ import { resolve } from 'pathe'
6
+ import { camelCase, kebabCase, splitByCase, upperFirst } from 'scule'
7
+ import templates from '../../templates.mjs'
8
+ import { appendFile, sortFile } from '../../utils.mjs'
9
+
10
+ export default defineCommand({
11
+ meta: {
12
+ name: 'component',
13
+ description: 'Создать новый компонент',
14
+ },
15
+ args: {
16
+ name: {
17
+ type: 'positional',
18
+ required: true,
19
+ description: 'Имя компонента',
20
+ },
21
+ prose: {
22
+ type: 'boolean',
23
+ description: 'Создать компонент для контента',
24
+ },
25
+ template: {
26
+ type: 'string',
27
+ description: 'Сгенерировать только шаблон',
28
+ },
29
+ },
30
+ async setup({ args }) {
31
+ const name = args.name
32
+ if (!name) {
33
+ consola.error('`name` argument is missing!')
34
+ process.exit(1)
35
+ }
36
+
37
+ const path = resolve('.')
38
+
39
+ for (const template of Object.keys(templates)) {
40
+ if (args.template && template !== args.template) {
41
+ continue
42
+ }
43
+
44
+ const { filename, contents } = templates[template](args)
45
+ if (!contents) {
46
+ continue
47
+ }
48
+
49
+ const filePath = resolve(path, filename)
50
+
51
+ if (existsSync(filePath)) {
52
+ consola.error(`🚨 ${filePath} уже существует!`)
53
+ continue
54
+ }
55
+
56
+ await fsp.writeFile(filePath, `${contents.trim()}\n`)
57
+
58
+ consola.success(`🪄 ${filePath} создан!`)
59
+ }
60
+
61
+ if (args.template) {
62
+ return
63
+ }
64
+
65
+ const themePath = resolve(path, `src/theme/${args.prose ? 'prose/' : ''}index.ts`)
66
+ await appendFile(themePath, `export { default as ${camelCase(name)} } from './${kebabCase(name)}'`)
67
+ await sortFile(themePath)
68
+
69
+ if (!args.prose) {
70
+ const typesPath = resolve(path, 'src/runtime/types/index.ts')
71
+ await appendFile(typesPath, `export * from '../components/${splitByCase(name).map((p) => upperFirst(p)).join('')}.vue'`)
72
+ await sortFile(typesPath)
73
+ }
74
+ },
75
+ })
@@ -0,0 +1,12 @@
1
+ import { defineCommand } from 'citty'
2
+ import component from './component.mjs'
3
+
4
+ export default defineCommand({
5
+ meta: {
6
+ name: 'make',
7
+ description: 'Команды для создания новых сущностей Hywax CMS',
8
+ },
9
+ subCommands: {
10
+ component,
11
+ },
12
+ })
package/cli/index.mjs ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import { defineCommand, runMain } from 'citty'
3
+ import make from './commands/make/index.mjs'
4
+
5
+ const main = defineCommand({
6
+ meta: {
7
+ name: 'cms',
8
+ description: 'Hywax CMS CLI',
9
+ },
10
+ subCommands: {
11
+ make,
12
+ },
13
+ })
14
+
15
+ runMain(main)
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@hywax/cms-cli",
3
+ "type": "module",
4
+ "exports": {
5
+ ".": "./index.mjs"
6
+ },
7
+ "dependencies": {
8
+ "citty": "^0.1.6",
9
+ "consola": "^3.4.2",
10
+ "pathe": "^2.0.3",
11
+ "scule": "^1.3.0"
12
+ }
13
+ }
@@ -0,0 +1,101 @@
1
+ import { camelCase, kebabCase, splitByCase, upperFirst } from 'scule'
2
+
3
+ function component({ name, prose }) {
4
+ const upperName = splitByCase(name).map((p) => upperFirst(p)).join('')
5
+ const camelName = camelCase(name)
6
+ const kebabName = kebabCase(name)
7
+ const path = 'cms'
8
+
9
+ return {
10
+ filename: `src/runtime/components/${prose ? 'prose/' : ''}${upperName}.vue`,
11
+ contents: `
12
+ <template>
13
+ <Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
14
+ <slot />
15
+ </Primitive>
16
+ </template>
17
+
18
+ <script lang="ts">
19
+ import type { AppConfig } from '@nuxt/schema'
20
+ import type { ComponentConfig } from '${prose ? '../../types' : '../types'}'
21
+ import theme from '#build/${path}/${prose ? 'prose/' : ''}${kebabName}'
22
+ import { computed, useAppConfig } from '#imports'
23
+ import { Primitive } from 'reka-ui'
24
+ import { tv } from '${prose ? '../../utils/tv' : '../utils/tv'}'
25
+
26
+ type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}>
27
+
28
+ export interface ${upperName}Props {
29
+ as?: any
30
+ class?: any
31
+ ui?: ${upperName}['slots']
32
+ }
33
+
34
+ export interface ${upperName}Emits {
35
+ // 'update:modelValue': [string]
36
+ }
37
+
38
+ export interface ${upperName}Slots {
39
+ default?: () => any
40
+ }
41
+ </script>
42
+
43
+ <script setup lang="ts">
44
+ const props = defineProps<${upperName}Props>()
45
+ const emit = defineEmits<${upperName}Emits>()
46
+ defineSlots<${upperName}Slots>()
47
+
48
+ const appConfig = useAppConfig() as ${upperName}['AppConfig']
49
+
50
+ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.cms?.${camelName} || {}) })())
51
+ </script>
52
+ `,
53
+ }
54
+ }
55
+
56
+ function test({ name, prose }) {
57
+ const upperName = splitByCase(name).map((p) => upperFirst(p)).join('')
58
+
59
+ return {
60
+ filename: `test/runtime/components/${upperName}.spec.ts`,
61
+ contents: prose
62
+ ? undefined
63
+ : `
64
+ import type { ${upperName}Props, ${upperName}Slots } from '../../../src/runtime/components/${upperName}.vue'
65
+ import { describe, it, expect } from 'vitest'
66
+ import ${upperName} from '../../../src/runtime/components/${upperName}.vue'
67
+ import ComponentRender from '../../component-render'
68
+
69
+ describe('${upperName}', () => {
70
+ it.each([
71
+ // Props
72
+ ['с алиасом', { props: { as: 'section' } }],
73
+ ['с class', { props: { class: '' } }],
74
+ ['с ui', { props: { ui: {} } }],
75
+ // Slots
76
+ ['с дефолтным слотом', { slots: { default: () => 'Default slot' } }]
77
+ ])('рендерит %s корректно', async (nameOrHtml: string, options: { props?: ${upperName}Props, slots?: Partial<${upperName}Slots> }) => {
78
+ const html = await ComponentRender(nameOrHtml, options, ${upperName})
79
+ expect(html).toMatchSnapshot()
80
+ })
81
+ })
82
+ `,
83
+ }
84
+ }
85
+
86
+ function theme({ name, prose }) {
87
+ const kebabName = kebabCase(name)
88
+
89
+ return {
90
+ filename: `src/theme/${prose ? 'prose/' : ''}${kebabName}.ts`,
91
+ contents: `
92
+ export default {
93
+ slots: {
94
+ root: '',
95
+ },
96
+ }
97
+ `,
98
+ }
99
+ }
100
+
101
+ export default { component, test, theme }
package/cli/utils.mjs ADDED
@@ -0,0 +1,31 @@
1
+ import { promises as fsp } from 'node:fs'
2
+
3
+ export async function sortFile(path) {
4
+ const file = await fsp.readFile(path, 'utf-8')
5
+
6
+ const lines = file.trim().split('\n').sort()
7
+
8
+ await fsp.writeFile(path, `${lines.join('\n')}\n`)
9
+ }
10
+
11
+ export async function appendFile(path, contents) {
12
+ const file = await fsp.readFile(path, 'utf-8')
13
+
14
+ if (!file.includes(contents)) {
15
+ await fsp.writeFile(path, `${file.trim()}\n${contents}\n`)
16
+ }
17
+ }
18
+
19
+ export function normalizeLocale(locale) {
20
+ if (!locale) {
21
+ return ''
22
+ }
23
+
24
+ if (locale.includes('_')) {
25
+ return locale.split('_')
26
+ .map((part, index) => index === 0 ? part.toLowerCase() : part.toUpperCase())
27
+ .join('-')
28
+ }
29
+
30
+ return locale.toLowerCase()
31
+ }
package/dist/module.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
  export * from '../dist/runtime/types/index.js';
3
3
 
4
- interface ModuleOptions {
4
+ interface CMSOptions {
5
5
  /**
6
6
  * Name of the module
7
7
  * @defaultValue 'cms'
@@ -12,8 +12,32 @@ interface ModuleOptions {
12
12
  * @defaultValue 'C'
13
13
  */
14
14
  prefix?: string;
15
+ /**
16
+ * Fluxor URL
17
+ * @defaultValue 'https://fluxor.uplora.ru'
18
+ */
19
+ fluxorUrl?: string;
20
+ /**
21
+ * Prefix for the environment variables
22
+ * @defaultValue 'APP_'
23
+ */
24
+ envPrefix?: string;
25
+ /**
26
+ * HTTP codes
27
+ * @defaultValue {}
28
+ */
29
+ httpCodes?: Record<string, string>;
30
+ }
31
+
32
+ declare module '@nuxt/schema' {
33
+ interface ConfigSchema {
34
+ public?: {
35
+ fluxorUrl: string;
36
+ version: string;
37
+ };
38
+ }
15
39
  }
16
- declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
40
+
41
+ declare const _default: _nuxt_schema.NuxtModule<CMSOptions, CMSOptions, false>;
17
42
 
18
43
  export { _default as default };
19
- export type { ModuleOptions };
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hywax/cms",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "configKey": "cms",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.1",
package/dist/module.mjs CHANGED
@@ -1,10 +1,75 @@
1
- import { addTypeTemplate, addTemplate, defineNuxtModule, hasNuxtModule, installModule, createResolver, addComponentsDir, addImportsDir } from '@nuxt/kit';
1
+ import { createResolver, addComponentsDir, addImportsDir, addPlugin, addImports, addServerImports, addServerImportsDir, addTypeTemplate, addTemplate, addServerTemplate, defineNuxtModule, hasNuxtModule, installModule } from '@nuxt/kit';
2
2
  import { defu } from 'defu';
3
- import 'node:url';
4
- import { kebabCase } from 'scule';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname } from 'pathe';
5
+ import { snakeCase, kebabCase } from 'scule';
5
6
 
6
7
  const name = "@hywax/cms";
7
- const version = "0.0.2";
8
+ const version = "0.0.4";
9
+
10
+ function createContext(options, nuxt) {
11
+ const { resolve } = createResolver(import.meta.url);
12
+ const distDir = dirname(fileURLToPath(import.meta.url));
13
+ const runtimeDir = fileURLToPath(new URL("./runtime", import.meta.url));
14
+ return {
15
+ options,
16
+ resolve,
17
+ distDir,
18
+ runtimeDir,
19
+ nuxt
20
+ };
21
+ }
22
+
23
+ function getDefaultCMSConfig() {
24
+ return {};
25
+ }
26
+ const defaultModuleOptions = {
27
+ name: "cms",
28
+ prefix: "C",
29
+ fluxorUrl: "https://fluxor.uplora.ru",
30
+ envPrefix: "APP_",
31
+ httpCodes: {
32
+ badRequest: "400: \u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0437\u0430\u043F\u0440\u043E\u0441",
33
+ unauthorized: "401: \u041D\u0435 \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u043E\u0432\u0430\u043D",
34
+ forbidden: "403: \u0414\u043E\u0441\u0442\u0443\u043F \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D",
35
+ notFound: "404: \u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E",
36
+ requestTimeout: "408: \u0412\u0440\u0435\u043C\u044F \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F \u0437\u0430\u043F\u0440\u043E\u0441\u0430 \u0438\u0441\u0442\u0435\u043A\u043B\u043E",
37
+ internalServerError: "500: \u0412\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u044F\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
38
+ badGateway: "502: \u041E\u0448\u0438\u0431\u043A\u0430 \u0448\u043B\u044E\u0437\u0430",
39
+ serviceUnavailable: "503: \u0421\u0435\u0440\u0432\u0438\u0441 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D"
40
+ }
41
+ };
42
+
43
+ function transformHttpCodes(httpCodes) {
44
+ return Object.entries(httpCodes).map(([code, value]) => ({
45
+ code: `HTTP_CODE_${snakeCase(code).toUpperCase()}`,
46
+ value
47
+ }));
48
+ }
49
+
50
+ function prepareAutoImports({ resolve, options, nuxt }) {
51
+ const httpCodesPath = resolve(nuxt.options.buildDir, "cms/http-codes.ts");
52
+ const httpCodesImports = transformHttpCodes(options.httpCodes).map(({ code }) => ({ name: code, from: httpCodesPath }));
53
+ addComponentsDir({
54
+ path: resolve("./runtime/components"),
55
+ pathPrefix: false,
56
+ prefix: options?.prefix,
57
+ ignore: ["prose/**"]
58
+ });
59
+ addComponentsDir({
60
+ path: resolve("./runtime/components/prose"),
61
+ prefix: "Prose",
62
+ pathPrefix: false,
63
+ global: true
64
+ });
65
+ addImportsDir(resolve("./runtime/composables"));
66
+ addPlugin(resolve("./runtime/plugins/api.ts"));
67
+ addImports(httpCodesImports);
68
+ addServerImports(httpCodesImports);
69
+ addServerImportsDir(resolve("./runtime/server/utils"));
70
+ nuxt.options.nitro.alias ||= {};
71
+ nuxt.options.nitro.alias["#cms/http-codes"] = httpCodesPath;
72
+ }
8
73
 
9
74
  const buttonClear = {
10
75
  slots: {
@@ -12,9 +77,19 @@ const buttonClear = {
12
77
  }
13
78
  };
14
79
 
80
+ const uploraImage$1 = {
81
+ slots: {
82
+ root: "relative grid grid-cols-[100%] grid-rows-[100%] overflow-hidden",
83
+ lqip: "bg-cover bg-no-repeat bg-center col-[1] row-[1]",
84
+ picture: "aspect-3/2 col-[1] row-[1]",
85
+ img: "w-full h-full block object-cover"
86
+ }
87
+ };
88
+
15
89
  const theme = {
16
90
  __proto__: null,
17
- buttonClear: buttonClear
91
+ buttonClear: buttonClear,
92
+ uploraImage: uploraImage$1
18
93
  };
19
94
 
20
95
  const uploraImage = {
@@ -28,7 +103,7 @@ const themeProse = {
28
103
  uploraImage: uploraImage
29
104
  };
30
105
 
31
- function getTemplates(options) {
106
+ function getAppTemplates({ options }) {
32
107
  const templates = [];
33
108
  function writeThemeTemplate(theme2, path) {
34
109
  for (const component in theme2) {
@@ -87,8 +162,11 @@ function getTemplates(options) {
87
162
  filename: "types/cms.d.ts",
88
163
  getContents: () => `import * as cms from '#build/cms'
89
164
  import type { TVConfig } from '@nuxt/ui'
165
+ import type { RouteLocationRaw } from 'vue-router'
166
+
167
+ type AppConfigCMS = {
90
168
 
91
- type AppConfigCMS = TVConfig<typeof cms>
169
+ } & TVConfig<typeof cms>
92
170
 
93
171
  declare module '@nuxt/schema' {
94
172
  interface AppConfigInput {
@@ -102,17 +180,39 @@ declare module '@nuxt/schema' {
102
180
  export {}
103
181
  `
104
182
  });
183
+ templates.push({
184
+ filename: "cms/http-codes.ts",
185
+ write: true,
186
+ getContents: () => {
187
+ const httpCodes = transformHttpCodes(options.httpCodes);
188
+ const content = httpCodes.map(({ code, value }) => `export const ${code} = '${value}'`).join("\n");
189
+ return `${content}
190
+ `;
191
+ }
192
+ });
193
+ return templates;
194
+ }
195
+ function getServerTemplates(_context) {
196
+ const templates = [];
105
197
  return templates;
106
198
  }
107
- function addTemplates(options) {
108
- const templates = getTemplates(options);
109
- for (const template of templates) {
199
+ function prepareTemplates(context) {
200
+ const appTemplates = getAppTemplates(context);
201
+ const serverTemplates = getServerTemplates();
202
+ for (const template of appTemplates) {
110
203
  if (template.filename.endsWith(".d.ts")) {
111
204
  addTypeTemplate(template);
112
205
  } else {
113
206
  addTemplate(template);
114
207
  }
115
208
  }
209
+ for (const template of serverTemplates) {
210
+ if (template.filename.endsWith(".d.ts")) {
211
+ addTypeTemplate(template, { nitro: true, nuxt: false });
212
+ } else {
213
+ addServerTemplate(template);
214
+ }
215
+ }
116
216
  }
117
217
 
118
218
  const icons = {};
@@ -123,11 +223,15 @@ const module = defineNuxtModule({
123
223
  version,
124
224
  configKey: "cms"
125
225
  },
126
- defaults: {
127
- name: "cms",
128
- prefix: "C"
129
- },
226
+ defaults: defaultModuleOptions,
130
227
  async setup(options, nuxt) {
228
+ const context = createContext(options, nuxt);
229
+ nuxt.options.runtimeConfig.public = defu(nuxt.options.runtimeConfig.public || {}, {
230
+ fluxorUrl: options.fluxorUrl,
231
+ version: ""
232
+ });
233
+ nuxt.options.runtimeConfig.nitro ||= {};
234
+ nuxt.options.runtimeConfig.nitro.envPrefix = options.envPrefix;
131
235
  nuxt.options.appConfig.ui = defu(nuxt.options.appConfig.ui || {}, {
132
236
  icons
133
237
  });
@@ -160,23 +264,10 @@ const module = defineNuxtModule({
160
264
  if (!hasNuxtModule("nuxt-auth-utils")) {
161
265
  await installModule("nuxt-auth-utils");
162
266
  }
163
- const { resolve } = createResolver(import.meta.url);
164
- nuxt.options.alias["#cms"] = resolve("./runtime");
165
- nuxt.options.appConfig.cms = defu(nuxt.options.appConfig.cms || {}, {});
166
- addComponentsDir({
167
- path: resolve("./runtime/components"),
168
- pathPrefix: false,
169
- prefix: options?.prefix || "C",
170
- ignore: ["prose/**"]
171
- });
172
- addComponentsDir({
173
- path: resolve("./runtime/components/prose"),
174
- prefix: "Prose",
175
- pathPrefix: false,
176
- global: true
177
- });
178
- addImportsDir(resolve("./runtime/composables"));
179
- addTemplates(options);
267
+ nuxt.options.alias["#cms"] = context.resolve("./runtime");
268
+ nuxt.options.appConfig.cms = defu(nuxt.options.appConfig.cms || {}, getDefaultCMSConfig());
269
+ prepareAutoImports(context);
270
+ prepareTemplates(context);
180
271
  }
181
272
  });
182
273
 
@@ -13,8 +13,7 @@
13
13
 
14
14
  <script>
15
15
  import theme from "#build/cms/button-clear";
16
- import { useAppConfig } from "#imports";
17
- import { computed } from "vue";
16
+ import { computed, useAppConfig } from "#imports";
18
17
  import { tv } from "../utils/tv";
19
18
  </script>
20
19
 
@@ -0,0 +1,108 @@
1
+ <template>
2
+ <figure :class="ui.root({ class: [props.ui?.root, props.class] })">
3
+ <div
4
+ v-if="lqip"
5
+ :class="ui.lqip({ class: props.ui?.lqip })"
6
+ :style="{
7
+ backgroundImage: `url(${lqip})`
8
+ }"
9
+ />
10
+ <picture :class="ui.picture({ class: props.ui?.picture })">
11
+ <template v-if="sources.length">
12
+ <source
13
+ v-for="(source, key) in sources"
14
+ :key="key"
15
+ :srcset="source.srcset"
16
+ :type="source.type"
17
+ >
18
+ </template>
19
+
20
+ <img
21
+ ref="imageRef"
22
+ v-bind="imageAttrs"
23
+ :src="image.img"
24
+ :srcset="image?.srcset"
25
+ :alt="alt"
26
+ :loading="loading"
27
+ :class="ui.img({ class: props.ui?.img })"
28
+ >
29
+ </picture>
30
+ </figure>
31
+ </template>
32
+
33
+ <script>
34
+ import theme from "#build/cms/uplora-image";
35
+ import { computed, onMounted, useAppConfig, useHead, useNuxtApp, useTemplateRef } from "#imports";
36
+ import { buildUploraImage } from "../utils";
37
+ import { tv } from "../utils/tv";
38
+ </script>
39
+
40
+ <script setup>
41
+ const props = defineProps({
42
+ id: { type: String, required: true },
43
+ alt: { type: String, required: true },
44
+ formats: { type: Array, required: false },
45
+ sizes: { type: Array, required: false },
46
+ lqip: { type: String, required: false },
47
+ loading: { type: String, required: false, default: "lazy" },
48
+ preload: { type: [Boolean, Object], required: false },
49
+ nonce: { type: String, required: false },
50
+ imgAttrs: { type: Object, required: false },
51
+ class: { type: null, required: false },
52
+ ui: { type: null, required: false }
53
+ });
54
+ const emit = defineEmits(["load", "error"]);
55
+ const nuxtApp = useNuxtApp();
56
+ const initialLoad = nuxtApp.isHydrating;
57
+ const image = computed(() => buildUploraImage({
58
+ id: props.id,
59
+ formats: props.formats,
60
+ sizes: props.sizes
61
+ }));
62
+ const sources = computed(() => {
63
+ if (image.value.sources.length > 1) {
64
+ return image.value.sources.slice(1);
65
+ }
66
+ return [];
67
+ });
68
+ if (import.meta.server && props.preload) {
69
+ useHead({ link: () => {
70
+ if (!image.value.img) {
71
+ return [];
72
+ }
73
+ const link = {
74
+ rel: "preload",
75
+ as: "image",
76
+ imagesrcset: image.value.srcset,
77
+ nonce: props.nonce,
78
+ ...typeof props.preload !== "boolean" && props.preload?.fetchPriority ? { fetchpriority: props.preload.fetchPriority } : {}
79
+ };
80
+ return [link];
81
+ } });
82
+ }
83
+ const imageRef = useTemplateRef("imageRef");
84
+ const imageAttrs = computed(() => ({
85
+ ...props.imgAttrs || {},
86
+ ...import.meta.server ? { onerror: "this.setAttribute('data-error', 1)" } : {}
87
+ }));
88
+ onMounted(() => {
89
+ if (!imageRef.value) {
90
+ return;
91
+ }
92
+ if (imageRef.value.complete && initialLoad) {
93
+ if (imageRef.value.getAttribute("data-error")) {
94
+ emit("error", new Event("error"));
95
+ } else {
96
+ emit("load", new Event("load"));
97
+ }
98
+ }
99
+ imageRef.value.onload = (event) => {
100
+ emit("load", event);
101
+ };
102
+ imageRef.value.onerror = (event) => {
103
+ emit("error", event);
104
+ };
105
+ });
106
+ const appConfig = useAppConfig();
107
+ const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.uploraImage || {} })());
108
+ </script>