UNPKG

kirbyuse

Version:

Collection of Vue Composition utilities for Kirby CMS

371 lines (370 loc) 12.1 kB
//#region src/vue.ts const globalVue = window.Vue; //#endregion //#region src/composables/panel.ts /** * Returns the reactive Kirby Panel object. * * @remarks * This composable is a simple shortcut to `window.panel`. */ function usePanel() { return window.panel; } //#endregion //#region src/composables/api.ts /** * Returns Kirby's Panel API for making HTTP requests to the backend. * * @remarks * This composable is a simple shortcut to `window.panel.api`. */ function useApi() { return usePanel().api; } //#endregion //#region src/composables/app.ts /** * Returns the main Panel Vue instance. * * @remarks * This composable is a simple shortcut to `window.panel.app`. */ function useApp() { return usePanel().app; } //#endregion //#region src/composables/block.ts /** * Provides block methods exposed by the default block component (Options API), which all custom blocks are extending. * Since Options API methods are not avaiable in Composition API, we need to extract them into a composable. * * @see https://github.com/getkirby/kirby/blob/main/panel/src/components/Forms/Blocks/Types/Default.vue * * @example * ```vue * <script setup> * import { computed, ref, useApi, usePanel, watch } from "kirbyuse"; * * // Will inherit props from extended default block * const props = defineProps({}); * const emit = defineEmits([]); * * const { field, open, update } = useBlock(props, emit); * ``` */ function useBlock(props, emit) { const field = (name, fallback) => { let result; for (const tab of Object.values(props.fieldset.tabs ?? {})) if (tab.fields?.[name]) result = tab.fields[name]; return result ?? fallback; }; const open = () => { emit?.("open"); }; const update = (content) => { emit?.("update", { ...props.content, ...content }); }; return { field, open, update }; } //#endregion //#region src/composables/compatibility.ts const isKirby5 = () => window.panel.plugins.viewButtons !== void 0; const isKirby4 = () => !isKirby5(); //#endregion //#region src/composables/store.ts /** * Returns the Vuex store of the Panel app. * * @deprecated The Vuex store is removed in Kirby 5. Use the `useContent` composable instead. */ function useStore() { if (isKirby5()) return new Proxy({}, { get() { throw new Error("Vuex store is not available. Are you using Kirby 5? Use the `useContent` composable instead."); } }); return usePanel().app.$store; } //#endregion //#region src/composables/content.ts /** * Reactive getters and methods to work with content of the current view. * * @remarks * This composable follows the Kirby 5 content API while staying compatible with Kirby 4 (falling back to the Vuex store of Kirby 4). */ function useContent() { const panel = usePanel(); const store = useStore(); const _isKirby5 = isKirby5(); if (_isKirby5 && !("diff" in panel.content)) throw new Error("This plugin requires Kirby 5.0.0-rc.1 or higher. Please update your Kirby installation."); const currentContent = _isKirby5 ? computed(() => panel.content.version("changes")) : computed(() => store.getters["content/values"]()); const contentChanges = _isKirby5 ? computed(() => panel.content.diff()) : computed(() => store.getters["content/changes"]()); const hasChanges = _isKirby5 ? computed(() => panel.content.hasDiff()) : computed(() => store.getters["content/hasChanges"]()); const content = _isKirby5 ? panel.content : new Proxy({}, { get() { return () => {}; } }); /** * Updates the form values of the current view. * * @remarks * In Kirby 5, the native `window.panel.content.update()` method immediately saves the changes to the backend storage. This can be prevented by passing `false` as the second argument. * In Kirby 4, content changes are only stored in the Vuex store and do not need to be saved explicitly. */ const update = async (values, save = true) => { if (!_isKirby5 && values) for (const [key, value] of Object.entries(values)) store.dispatch("content/update", [key, value]); const viewContent = content.merge(values); if (save) await content.save(viewContent); }; return { content, currentContent, contentChanges, hasChanges, update }; } //#endregion //#region src/composables/dialog.ts /** * Provides methods to open different types of dialogs. */ function useDialog() { const _isKirby5 = isKirby5(); /** * Returns a promise that resolves when the dialog is closed. * * @example * ```ts * const { openTextDialog } = useDialog() * * const result = await openTextDialog("Are you sure?") * console.log(result) // -> true or false * ``` */ function openTextDialog(text) { let result = false; return new Promise((resolve) => { const panel = usePanel(); panel.dialog.open({ component: "k-text-dialog", props: { text }, on: { submit: () => { result = true; panel.dialog.close(); }, ..._isKirby5 ? { closed: () => { resolve(result); } } : { close: () => { setTimeout(() => resolve(result), 25); } } } }); }); } function openFieldsDialog(props) { let result; const { onSubmit, ...dialogProps } = props; return new Promise((resolve) => { const panel = usePanel(); panel.dialog.open({ component: "k-form-dialog", props: dialogProps, on: { submit: async (event) => { const value = event; if (onSubmit) try { const submitResult = await onSubmit(value); if (submitResult === false) return; result = submitResult; } catch { return; } else result = value; panel.dialog.close(); }, ..._isKirby5 ? { closed: () => { resolve(result); } } : { close: () => { setTimeout(() => resolve(result), 0); } } } }); }); } return { openTextDialog, openFieldsDialog }; } //#endregion //#region src/composables/helpers.ts /** * Returns the internal Fiber helpers. * * @see https://lab.getkirby.com/public/lab/internals/helpers/ * * @remarks * This composable is a simple shortcut to `window.panel.app.$helper`. */ function useHelpers() { return useApp().$helper; } //#endregion //#region src/composables/i18n.ts /** * Returns translation utility functions. * * @remarks * In most cases, use `window.panel.t` for Kirby's built-in translation function. This composable is useful for custom translation objects, such as those returned by a section's `label` property. * * @example * ```ts * const { t } = useI18n() * * // Simple string * t("Hello") // -> "Hello" * * // Translation object * t({ en: "Hello", de: "Hallo" }) // -> Returns value based on current Panel language * ``` */ function useI18n() { const panel = usePanel(); function t(value) { if (!value || typeof value === "string") return value; return value[panel.translation.code] ?? Object.values(value)[0]; } return { t }; } //#endregion //#region src/composables/library.ts /** * Returns the internal Kirby Panel libraries (dayjs, colors and autosize). * * @see https://lab.getkirby.com/public/lab/internals/library.colors * @see https://lab.getkirby.com/public/lab/internals/library.dayjs * * @remarks * This composable is a simple shortcut to `window.panel.app.$library`. */ function useLibrary() { return useApp().$library; } //#endregion //#region src/composables/section.ts /** * Provides section methods for loading section data. */ function useSection() { const api = useApi(); const load = ({ parent, name }) => api.get(`${parent}/sections/${name}`); return { load }; } //#endregion //#region src/utils/env.ts const LOCALHOST_HOSTNAMES = [ "localhost", "127.0.0.1", "[::1]" ]; const LOCAL_TLD_SUFFIXES = [ "localhost", "local", "test", "ddev.site" ]; function isLocalDev() { const { hostname } = window.location; const isLocalHostname = LOCALHOST_HOSTNAMES.includes(hostname); const isLocalTld = LOCAL_TLD_SUFFIXES.some((suffix) => hostname.endsWith(`.${suffix}`)); return isLocalHostname || isLocalTld; } //#endregion //#region src/utils/logger.ts const LOG_LEVELS = { error: 0, warn: 1, log: 2, info: 3, success: 3 }; function createLogger(tag) { const reporter = new BrowserReporter(); return new Proxy({}, { get(target, prop) { return (...args) => { reporter.log({ level: LOG_LEVELS[prop], type: prop, tag, args }); }; } }); } var BrowserReporter = class { constructor() { this.defaultColor = "#7f8c8d"; this.levelColorMap = { 0: "#c0392b", 1: "#f39c12", 3: "#00BCD4" }; this.typeColorMap = { success: "#2ecc71" }; } log(logEvent) { const consoleLogFn = resolveLogFn(logEvent.level); const type = logEvent.type === "log" ? "" : logEvent.type; const tag = logEvent.tag || ""; const style = ` background: ${this.typeColorMap[logEvent.type] || this.levelColorMap[logEvent.level] || this.defaultColor}; border-radius: 0.5em; color: white; font-weight: bold; padding: 2px 0.5em; `.trimStart(); const badge = `%c${[tag, type].filter(Boolean).join(":")}`; if (typeof logEvent.args[0] === "string") consoleLogFn(`${badge}%c ${logEvent.args[0]}`, style, "", ...logEvent.args.slice(1)); else consoleLogFn(badge, style, ...logEvent.args); } }; function resolveLogFn(level) { if (level < 1) return console.error; return console.log; } //#endregion //#region src/utils/plugin.ts const registeredAssets = []; const moduleCache = /* @__PURE__ */ new Map(); async function registerPluginAssets(assets) { if (!Array.isArray(assets)) throw new TypeError(`Expected an array, got ${typeof assets}`); for (const asset of assets) if (!registeredAssets.some((existing) => existing.filename === asset.filename)) registeredAssets.push(asset); } function resolvePluginAsset(filename) { if (registeredAssets.length === 0) throw new Error("Plugin assets not registered"); const asset = registeredAssets.find((asset) => asset.filename === filename); if (!asset) throw new Error(`Plugin asset "${filename}" not found`); return asset; } async function loadPluginModule(filename) { if (!filename.endsWith(".js")) filename += ".js"; if (moduleCache.has(filename)) return moduleCache.get(filename); const mod = await import( /* @vite-ignore */ resolvePluginAsset(filename).url ); moduleCache.set(filename, mod); return mod; } //#endregion //#region src/index.ts const { computed, customRef, defineAsyncComponent, defineComponent, effectScope, getCurrentInstance, getCurrentScope, h, inject, isProxy, isReactive, isReadonly, isRef, isShallow, markRaw, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onDeactivated, onErrorCaptured, onMounted, onRenderTracked, onRenderTriggered, onScopeDispose, onServerPrefetch, onUnmounted, onUpdated, provide, proxyRefs, reactive, readonly, ref, shallowReactive, shallowReadonly, shallowRef, toRaw, toRef, toRefs, triggerRef, unref, useAttrs, useCssModule, useCssVars, useListeners, useSlots, watch, watchEffect, watchPostEffect, watchSyncEffect } = globalVue; //#endregion export { registerPluginAssets as $, provide as A, toRefs as B, onMounted as C, onServerPrefetch as D, onScopeDispose as E, shallowReactive as F, useCssVars as G, unref as H, shallowReadonly as I, watch as J, useListeners as K, shallowRef as L, reactive as M, readonly as N, onUnmounted as O, ref as P, loadPluginModule as Q, toRaw as R, onErrorCaptured as S, onRenderTriggered as T, useAttrs as U, triggerRef as V, useCssModule as W, watchPostEffect as X, watchEffect as Y, watchSyncEffect as Z, onActivated as _, globalVue as _t, effectScope as a, useLibrary as at, onBeforeUpdate as b, h as c, useDialog as ct, isReactive as d, isKirby4 as dt, resolvePluginAsset as et, isReadonly as f, isKirby5 as ft, nextTick as g, usePanel as gt, markRaw as h, useApi as ht, defineComponent as i, useSection as it, proxyRefs as j, onUpdated as k, inject as l, useContent as lt, isShallow as m, useApp as mt, customRef as n, createLogger as nt, getCurrentInstance as o, useI18n as ot, isRef as p, useBlock as pt, useSlots as q, defineAsyncComponent as r, isLocalDev as rt, getCurrentScope as s, useHelpers as st, computed as t, LOG_LEVELS as tt, isProxy as u, useStore as ut, onBeforeMount as v, onRenderTracked as w, onDeactivated as x, onBeforeUnmount as y, toRef as z };