├── .editorconfig ├── .github ├── kirby-intellisense-preview.png └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── build.config.ts ├── eslint.config.mjs ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts └── generatePanelTypes.mjs ├── src ├── composables │ ├── api.ts │ ├── app.ts │ ├── block.ts │ ├── compatibility.ts │ ├── content.ts │ ├── dialog.ts │ ├── i18n.ts │ ├── index.ts │ ├── panel.ts │ ├── section.ts │ └── store.ts ├── index.ts ├── props │ ├── _block.ts │ ├── after.ts │ ├── autocomplete.ts │ ├── autofocus.ts │ ├── before.ts │ ├── disabled.ts │ ├── font.ts │ ├── help.ts │ ├── icon.ts │ ├── id.ts │ ├── index.ts │ ├── label.ts │ ├── layout.ts │ ├── maxlength.ts │ ├── minlength.ts │ ├── name.ts │ ├── options.ts │ ├── pattern.ts │ ├── placeholder.ts │ ├── required.ts │ ├── section.ts │ ├── spellcheck.ts │ └── type.ts ├── types │ └── panel.ts ├── utils │ ├── index.ts │ ├── logger.ts │ └── plugin.ts └── vue.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/kirby-intellisense-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johannschopplich/kirbyuse/ea8f965ecb7794950954ec8f0b70190e9cfcd248/.github/kirby-intellisense-preview.png -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | format: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: pnpm/action-setup@v3 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: 22 21 | cache: pnpm 22 | - run: pnpm install 23 | - run: pnpm run format:check 24 | 25 | lint: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: pnpm/action-setup@v3 30 | - uses: actions/setup-node@v4 31 | with: 32 | node-version: 22 33 | cache: pnpm 34 | - run: pnpm install 35 | - run: pnpm run lint 36 | 37 | typecheck: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v4 41 | - uses: pnpm/action-setup@v3 42 | - uses: actions/setup-node@v4 43 | with: 44 | node-version: 22 45 | cache: pnpm 46 | - run: pnpm install 47 | - run: pnpm run test:types 48 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | - uses: pnpm/action-setup@v3 19 | - uses: actions/setup-node@v4 20 | with: 21 | node-version: 22 22 | registry-url: https://registry.npmjs.org/ 23 | cache: pnpm 24 | 25 | - name: Publish changelog 26 | run: npx changelogithub 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | 30 | - name: Install 31 | run: pnpm install 32 | 33 | - name: Build 34 | run: pnpm run build 35 | 36 | - name: Publish to npm 37 | run: npm publish --access public 38 | env: 39 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Enable ESLint flat config support 3 | "eslint.useFlatConfig": true, 4 | 5 | // Use Prettier as the default formatter 6 | "editor.defaultFormatter": "esbenp.prettier-vscode", 7 | "editor.formatOnSave": true, 8 | 9 | // Auto-fix ESLint errors on save 10 | "editor.codeActionsOnSave": { 11 | "source.fixAll.eslint": "explicit", 12 | "source.organizeImports": "never" 13 | }, 14 | 15 | // Enable ESLint for all supported languages 16 | "eslint.validate": [ 17 | "javascript", 18 | "javascriptreact", 19 | "typescript", 20 | "typescriptreact", 21 | "vue", 22 | "html", 23 | "markdown", 24 | "json", 25 | "jsonc", 26 | "yaml" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-PRESENT Johann Schopplich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kirbyuse 2 | 3 | A collection of Vue Composition utilities and type hints to improve the DX for writing Kirby Panel plugins. It is intended for the Composition API, but also works with the Options API. 4 | 5 | > [!NOTE] 6 | > When Kirby migrates to Vue 3, this package will adapt accordingly. 7 | 8 | ## Features 9 | 10 | - 🧃 IntelliSense support for Kirby's global `window.panel` object 11 | - 🍿 Helpers like `usePanel` to write future-proof Kirby plugins 12 | - 🚀 Aliases for Composition API functions like `ref` and `computed` 13 | - 📇 Ready for Kirby 4 & Kirby 5 14 | 15 | ## Setup 16 | 17 | ```bash 18 | # pnpm 19 | pnpm add -D kirbyuse 20 | 21 | # npm 22 | npm i -D kirbyuse 23 | 24 | # yarn 25 | yarn add -D kirbyuse 26 | ``` 27 | 28 | ## Kirby Panel Type Augmentation 29 | 30 | > [!NOTE] 31 | > This works for Vue components written in the Options API as well as the Composition API. TypeScript is not required. The type hints are provided by the package itself. 32 | 33 | ![Type Hints for `window.panel`](./.github/kirby-intellisense-preview.png) 34 | 35 | Kirby's `window.panel` global object provides the main Panel instance, including all methods and props. This package augments the `window.panel` object to provide type hints out of the box. 36 | 37 | Type augmentations are generated based on the Kirby Panel JavaScript build. Especially types of function arguments and return types cannot be inferred. But for working with the Panel API, this should be sufficient. 38 | 39 | Depending on the component type, you can choose how to import the type hints: 40 | 41 | ### Panel Access with `usePanel` 42 | 43 | In order to benefit from type completions, you can import the `usePanel` function from this package. This function returns a typed `window.panel` object. This works both in the Options API and the Composition API. 44 | 45 | For example, the `notification` service is available on the `panel` object. With each method call, you get IntelliSense support in your editor: 46 | 47 | ```js 48 | import { usePanel } from "kirbyuse"; 49 | 50 | const panel = usePanel(); 51 | panel.notification.success("Kirby is awesome!"); 52 | // ^? (property) PanelNotification.success: (arg1: any) => any 53 | ``` 54 | 55 | If you are writing a Vue component in the Options API, you can use the `panel` instance in methods like `created`: 56 | 57 | ```js 58 | import { usePanel } from "kirbyuse"; 59 | 60 | export default { 61 | mounted() { 62 | const panel = usePanel(); 63 | panel.notification.success("Guten Tag!"); 64 | }, 65 | }; 66 | ``` 67 | 68 | ### Augmenting the `window.panel` Object 69 | 70 | Instead of the explicit `usePanel` import, you can also augment the `window.panel` object directly. In this case, you have to import the `kirbyup` package **once** in your main entry file: 71 | 72 | ```js 73 | import "kirbyuse"; 74 | 75 | window.panel.notification.success("Kirby is awesome!"); 76 | // ^? (property) PanelNotification.success: (arg1: any) => any 77 | ``` 78 | 79 | The import will provide global type augmentations for the `window.panel` object. Every time you access the `panel` object, you get IntelliSense support for all available methods and services. 80 | 81 | ## API 82 | 83 | ### useApi 84 | 85 | Returns Kirby's Panel API for making HTTP requests to the backend. 86 | 87 | This composable is a simple shortcut to `window.panel.api`. 88 | 89 | **Example:** 90 | 91 | ```ts 92 | import { useApi } from "kirbyuse"; 93 | 94 | const api = useApi(); 95 | // Make API calls 96 | await api.get("pages/my-page"); 97 | ``` 98 | 99 | ### useApp 100 | 101 | Returns the main Panel Vue instance. 102 | 103 | This composable is a simple shortcut to `window.panel.app`. 104 | 105 | **Example:** 106 | 107 | ```ts 108 | import { useApp } from "kirbyuse"; 109 | 110 | const app = useApp(); 111 | // Access Vue instance methods and properties 112 | console.log(app.$root); 113 | ``` 114 | 115 | ### useBlock 116 | 117 | Provides block methods for custom block components, including field access and updates. 118 | 119 | **Example:** 120 | 121 | ```ts 122 | import { computed, ref, useApi, useBlock, usePanel, watch } from "kirbyuse"; 123 | 124 | // Will inherit props from extended default block 125 | const props = defineProps({}); 126 | const emit = defineEmits([]); 127 | const { field } = useBlock(props, emit); 128 | 129 | const source = computed(() => props.content?.source?.[0]); 130 | const captionMarks = computed(() => field("caption", { marks: true }).marks); 131 | ``` 132 | 133 | ### useContent 134 | 135 | Provides reactive getters and methods to work with content of the current view. 136 | 137 | > [!TIP] 138 | > Compatible with both Kirby 4 and 5. The returned getters and methods are shimmed for Kirby 4 in a Kirby 4 environment. 139 | 140 | **Example:** 141 | 142 | ```ts 143 | import { useContent } from "kirbyuse"; 144 | 145 | const { currentContent, contentChanges, hasChanges, update } = useContent(); 146 | 147 | // Watch for content changes 148 | watch(currentContent, (newContent) => { 149 | console.log("Content changed:", newContent); 150 | }); 151 | 152 | // Update content of the current view 153 | update({ excerpt: "Hello, Kirby!" }); 154 | ``` 155 | 156 | ### useDialog 157 | 158 | Provides methods to open different types of dialogs. 159 | 160 | **Example:** 161 | 162 | ```ts 163 | import { useDialog } from "kirbyuse"; 164 | 165 | const { openTextDialog, openFieldsDialog } = useDialog(); 166 | 167 | const isOk = await openTextDialog("Are you sure?"); 168 | console.log(isOk); // -> true or false 169 | 170 | const fields = { 171 | email: { 172 | type: "email", 173 | label: "Email", 174 | }, 175 | }; 176 | 177 | const result = await openFieldsDialog({ fields }); 178 | console.log(result); // -> { email: "..." } 179 | ``` 180 | 181 | ### useI18n 182 | 183 | Returns translation utility functions. 184 | 185 | > [!NOTE] 186 | > 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. 187 | 188 | **Example:** 189 | 190 | ```ts 191 | const { t } = useI18n(); 192 | 193 | // Simple string 194 | t("Hello"); // -> "Hello" 195 | 196 | // Translation object 197 | t({ en: "Hello", de: "Hallo" }); // -> Returns value based on current Panel language 198 | ``` 199 | 200 | ### usePanel 201 | 202 | Returns the reactive Kirby Panel object with type hints. 203 | 204 | This composable is a simple shortcut to `window.panel`. 205 | 206 | **Example:** 207 | 208 | ```ts 209 | import { usePanel } from "kirbyuse"; 210 | 211 | const panel = usePanel(); 212 | // Access panel services 213 | panel.notification.success("Success!"); 214 | ``` 215 | 216 | ### useSection 217 | 218 | Provides methods for loading section data. 219 | 220 | See the [section example](#panel-section) for a usage example. 221 | 222 | ### useStore 223 | 224 | Returns the Vuex store of the Panel app (Kirby 4 only, will not work in Kirby 5). 225 | 226 | **Example:** 227 | 228 | ```ts 229 | import { useStore } from "kirbyuse"; 230 | 231 | const store = useStore(); 232 | // Access store 233 | const content = comptued(() => store.getters["content/values"]()); 234 | ``` 235 | 236 | > [!TIP] 237 | > Use the `useContent` composable instead for common use cases, such as getting the current content, content changes, and updating content. 238 | 239 | ## Examples 240 | 241 | ### Panel Section 242 | 243 | ```vue 244 | 266 | 267 | 274 | ``` 275 | 276 |
277 | 👉 How to load section data 278 | 279 | ```vue 280 | 293 | 294 | 311 | 312 | 319 | ``` 320 | 321 |
322 | 323 | ## Background 324 | 325 | Kirby CMS uses Vue 2 and provides the `Vue` constructor via the UMD build in the global scope. The main Kirby Panel instance is accessible at `window.panel.app`. 326 | 327 | Before Vue reached EOL, the Composition API was backported in Vue 2.7. In ESM builds, these APIs are provided as named exports: 328 | 329 | ```js 330 | import Vue, { ref } from "vue"; 331 | 332 | Vue.ref; // `undefined`, use named export instead 333 | ``` 334 | 335 | Since Kirby uses the UMD build, these APIs are not available. Instead, in the **UMD build**, these APIs are exposed as properties on the global Vue object: 336 | 337 | ```js 338 | import Vue from "vue"; 339 | 340 | Vue.ref; // `function` 341 | ``` 342 | 343 | When writing Vue components for Kirby with the Composition API, you have to import the Vue constructor from the global scope and use the Composition API from there: 344 | 345 | ```js 346 | import Vue from "vue"; 347 | 348 | const label = Vue.ref(""); 349 | ``` 350 | 351 | This approach gets tedious quickly, especially when you have to write a lot of components. It also makes it harder to upgrade to Vue 3 in the future. 352 | 353 | This is where this package comes in. It provides aliases to the Composition API that are compatible with the UMD build of Vue. Additionally, some helpers are provided to make working with Kirby easier: 354 | 355 | ```js 356 | // Inside `