UNPKG

@maxlkatze/cms

Version:

A git based Nuxt Module CMS - zero effort, zero cost

361 lines (354 loc) 11.3 kB
import { defineNuxtModule, createResolver, installModule, addLayout, addComponentsDir, addImportsDir, addPlugin, addRouteMiddleware, extendPages, addServerHandler } from '@nuxt/kit'; import { useContentStorage } from '../dist/runtime/storage/ContentStorage.js'; function isPlainObject(value) { if (value === null || typeof value !== "object") { return false; } const prototype = Object.getPrototypeOf(value); if (prototype !== null && prototype !== Object.prototype && Object.getPrototypeOf(prototype) !== null) { return false; } if (Symbol.iterator in value) { return false; } if (Symbol.toStringTag in value) { return Object.prototype.toString.call(value) === "[object Module]"; } return true; } function _defu(baseObject, defaults, namespace = ".", merger) { if (!isPlainObject(defaults)) { return _defu(baseObject, {}, namespace, merger); } const object = Object.assign({}, defaults); for (const key in baseObject) { if (key === "__proto__" || key === "constructor") { continue; } const value = baseObject[key]; if (value === null || value === void 0) { continue; } if (merger && merger(object, key, value, namespace)) { continue; } if (Array.isArray(value) && Array.isArray(object[key])) { object[key] = [...value, ...object[key]]; } else if (isPlainObject(value) && isPlainObject(object[key])) { object[key] = _defu( value, object[key], (namespace ? `${namespace}.` : "") + key.toString(), merger ); } else { object[key] = value; } } return object; } function createDefu(merger) { return (...arguments_) => ( // eslint-disable-next-line unicorn/no-array-reduce arguments_.reduce((p, c) => _defu(p, c, "", merger), {}) ); } const defu = createDefu(); const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//; function normalizeWindowsPath(input = "") { if (!input) { return input; } return input.replace(/\\/g, "/").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase()); } const _UNC_REGEX = /^[/\\]{2}/; const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/; const _DRIVE_LETTER_RE = /^[A-Za-z]:$/; const normalize = function(path) { if (path.length === 0) { return "."; } path = normalizeWindowsPath(path); const isUNCPath = path.match(_UNC_REGEX); const isPathAbsolute = isAbsolute(path); const trailingSeparator = path[path.length - 1] === "/"; path = normalizeString(path, !isPathAbsolute); if (path.length === 0) { if (isPathAbsolute) { return "/"; } return trailingSeparator ? "./" : "."; } if (trailingSeparator) { path += "/"; } if (_DRIVE_LETTER_RE.test(path)) { path += "/"; } if (isUNCPath) { if (!isPathAbsolute) { return `//./${path}`; } return `//${path}`; } return isPathAbsolute && !isAbsolute(path) ? `/${path}` : path; }; const join = function(...segments) { let path = ""; for (const seg of segments) { if (!seg) { continue; } if (path.length > 0) { const pathTrailing = path[path.length - 1] === "/"; const segLeading = seg[0] === "/"; const both = pathTrailing && segLeading; if (both) { path += seg.slice(1); } else { path += pathTrailing || segLeading ? seg : `/${seg}`; } } else { path += seg; } } return normalize(path); }; function normalizeString(path, allowAboveRoot) { let res = ""; let lastSegmentLength = 0; let lastSlash = -1; let dots = 0; let char = null; for (let index = 0; index <= path.length; ++index) { if (index < path.length) { char = path[index]; } else if (char === "/") { break; } else { char = "/"; } if (char === "/") { if (lastSlash === index - 1 || dots === 1) ; else if (dots === 2) { if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== "." || res[res.length - 2] !== ".") { if (res.length > 2) { const lastSlashIndex = res.lastIndexOf("/"); if (lastSlashIndex === -1) { res = ""; lastSegmentLength = 0; } else { res = res.slice(0, lastSlashIndex); lastSegmentLength = res.length - 1 - res.lastIndexOf("/"); } lastSlash = index; dots = 0; continue; } else if (res.length > 0) { res = ""; lastSegmentLength = 0; lastSlash = index; dots = 0; continue; } } if (allowAboveRoot) { res += res.length > 0 ? "/.." : ".."; lastSegmentLength = 2; } } else { if (res.length > 0) { res += `/${path.slice(lastSlash + 1, index)}`; } else { res = path.slice(lastSlash + 1, index); } lastSegmentLength = index - lastSlash - 1; } lastSlash = index; dots = 0; } else if (char === "." && dots !== -1) { ++dots; } else { dots = -1; } } return res; } const isAbsolute = function(p) { return _IS_ABSOLUTE_RE.test(p); }; const module = defineNuxtModule({ meta: { name: "CMSKatze", configKey: "katze" }, // Default configuration options of the Nuxt module defaults: { users: [], secret: "secret", projectLocation: "./", storageKey: "katze_content.json" }, async setup(_options, _nuxt) { const resolver = createResolver(import.meta.url); let contentStorage = null; if (_options.users.length === 0) { katzeError("No users found in the configuration adding default user"); _options.users = [ { name: "admin", password: "admin" } ]; } _nuxt.options.runtimeConfig.users = _options.users; _nuxt.options.runtimeConfig.secret = _options.secret; _nuxt.options.runtimeConfig.storageKey = _options.storageKey; _nuxt.options.runtimeConfig.projectLocation = _options.projectLocation + (_options.projectLocation.endsWith("/") ? "" : "/"); _nuxt.options.runtimeConfig.deployHookURL = _options.deployHookURL; if (_options.storage) { _nuxt.options.runtimeConfig.storage = _options.storage; contentStorage = await useContentStorage(_nuxt.options.runtimeConfig); let content = await contentStorage.getItem(_options.storageKey); if (content === null) { content = {}; } katzeLog("Loaded " + Object.entries(content).length + " entries from [" + _options.storage.type + "] storage"); _nuxt.options.runtimeConfig.public.content = content; } else { katzeError("No storage found in the configuration"); } _nuxt.hook("close", async () => { if (contentStorage && typeof contentStorage.close === "function") { try { katzeLog("Closing storage connection"); await contentStorage.close(); katzeLog("Storage connection closed"); } catch (err) { katzeError("Failed to close storage connection: " + err); } } }); _nuxt.hook("tailwindcss:config", (tailwindConfig) => { const contentPathsToAdd = [ resolver.resolve("runtime/client/components/**/*.{vue,mjs,ts}"), resolver.resolve("runtime/client/components/**/**/*.{vue,mjs,ts}"), resolver.resolve("runtime/client/pages/**/*.{vue,mjs,ts}"), resolver.resolve("runtime/client/pages/**/**/*.{vue,mjs,ts}"), resolver.resolve("runtime/client/layouts/**/*.{vue,mjs,ts}"), resolver.resolve("runtime/client/*.{mjs,js,ts}") ]; tailwindConfig.content = tailwindConfig.content ?? { files: [] }; if (Array.isArray(tailwindConfig.content)) { tailwindConfig.content.push(...contentPathsToAdd); } else { tailwindConfig.content.files = tailwindConfig.content.files || []; tailwindConfig.content.files.push(...contentPathsToAdd); } tailwindConfig.theme = tailwindConfig.theme ?? {}; tailwindConfig.theme.extend = tailwindConfig.theme.extend ?? {}; }); const tailwindOptions = { exposeConfig: true, config: { darkMode: "class" }, configPath: [ join(_nuxt.options.rootDir, "tailwind.config") ] }; const userOptions = _nuxt.options.tailwindcss || {}; const mergedOptions = defu(tailwindOptions, userOptions); await installModule("@nuxtjs/tailwindcss", mergedOptions); addLayout({ src: resolver.resolve("runtime/client/layouts/cms.vue") }, "katze-cms-layout"); addLayout({ src: resolver.resolve("runtime/client/layouts/empty.vue") }, "katze-cms-empty"); addComponentsDir({ path: resolver.resolve("runtime/client/components/cms/ui"), prefix: "cms-ui" }); addComponentsDir({ path: resolver.resolve("runtime/client/components/global"), preload: true, global: true }); addComponentsDir({ path: resolver.resolve("runtime/client/components/cms/edit"), prefix: "cms-edit", enabled: true }); addImportsDir(resolver.resolve("runtime/client/composables/global")); addImportsDir(resolver.resolve("runtime/client/pages")); addImportsDir(resolver.resolve("runtime/client/components")); addPlugin(resolver.resolve("runtime/client/plugins/chtml.plugin")); addRouteMiddleware({ name: "auth", path: resolver.resolve("runtime/client/middleware/authentication"), global: true }); extendPages( (pages) => { const pageList = [ { name: "katze-cms", path: "/cms", file: resolver.resolve("runtime/client/pages/login.vue") }, { name: "katze-cms-dashboard", path: "/cms/dashboard", file: resolver.resolve("runtime/client/pages/dashboard.vue") }, { name: "katze-cms-pages", path: "/cms/pages", file: resolver.resolve("runtime/client/pages/pages.vue") }, { name: "katze-cms-page-edit", path: "/cms/page/:id", file: resolver.resolve("runtime/client/pages/page/[id].vue") }, { name: "katze-cms-media", path: "/cms/media", file: resolver.resolve("runtime/client/pages/media.vue") }, { name: "katze-cms-site-settings", path: "/cms/site-settings", file: resolver.resolve("runtime/client/pages/site-settings.vue") } ]; pages.push(...pageList); } ); addServerHandler( { route: "/cms/api/content", handler: resolver.resolve("runtime/server/api/content") } ); addServerHandler( { route: "/cms/api/auth", handler: resolver.resolve("runtime/server/api/authentication") } ); addServerHandler( { route: "/cms/api/lifecycle", handler: resolver.resolve("runtime/server/api/lifecycle") } ); } }); const katzeLog = (message) => { console.log("\x1B[42m\x1B[30m Katze \x1B[0m " + message); }; const katzeError = (message) => { console.log("\x1B[41m\x1B[30m !Katze \x1B[0m " + message); }; export { module as default };