@maxlkatze/cms
Version:
A git based Nuxt Module CMS - zero effort, zero cost
361 lines (354 loc) • 11.3 kB
JavaScript
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 };