diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2dc6ecaad6..0acca947dd 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ -## This PR … +## Description + +### Summary of changes + + + +### Reasoning + + + +### Additional context + + + +## Changelog + ### Fixes + ### Breaking changes @@ -23,7 +39,7 @@ How to contribute: https://contribute.getkirby.com ## Docs @@ -32,13 +48,11 @@ to be your best writing, just a few words and/or code snipptets. - [ ] In-code documentation (wherever needed) - [ ] Unit tests for fixed bug/feature -- [ ] Tests and checks all pass +- [ ] Tests and CI checks all pass ### For review team + + {{ $t("files.empty") }} + -
- -
- - - - - - + + @@ -105,19 +55,6 @@ export default { }; } } - }, - methods: { - isPreviewable(mime) { - return [ - "image/jpeg", - "image/jpg", - "image/gif", - "image/png", - "image/webp", - "image/avif", - "image/svg+xml" - ].includes(mime); - } } }; @@ -126,84 +63,4 @@ export default { .k-upload-dialog.k-dialog { --dialog-width: 40rem; } - -.k-upload-items { - display: grid; - gap: 0.25rem; -} -.k-upload-item { - accent-color: var(--color-focus); - display: grid; - grid-template-areas: - "preview input input" - "preview body toggle"; - grid-template-columns: 6rem 1fr auto; - grid-template-rows: var(--input-height) 1fr; - border-radius: var(--rounded); - background: var(--color-white); - box-shadow: var(--shadow); - min-height: 6rem; -} -.k-upload-item-preview { - grid-area: preview; - display: flex; - width: 100%; - height: 100%; - overflow: hidden; - border-start-start-radius: var(--rounded); - border-end-start-radius: var(--rounded); -} -.k-upload-item-preview:focus { - border-radius: var(--rounded); - outline: 2px solid var(--color-focus); - z-index: 1; -} - -.k-upload-item-body { - grid-area: body; - display: flex; - flex-direction: column; - justify-content: space-between; - padding: var(--spacing-2) var(--spacing-3); - min-width: 0; -} -.k-upload-item-input.k-input { - --input-color-border: transparent; - --input-padding: var(--spacing-2) var(--spacing-3); - --input-rounded: 0; - grid-area: input; - font-size: var(--text-sm); - border-bottom: 1px solid var(--color-light); -} -.k-upload-item-input.k-input:focus-within { - outline: 2px solid var(--color-focus); - z-index: 1; - border-radius: var(--rounded); -} -.k-upload-item-input .k-input-after { - color: var(--color-gray-600); -} -.k-upload-item-meta { - font-size: var(--text-xs); - color: var(--color-gray-600); -} -.k-upload-item-error { - font-size: var(--text-xs); - margin-top: 0.25rem; - color: var(--color-red-700); -} -.k-upload-item-progress { - --progress-height: 0.25rem; - --progress-color-back: var(--color-light); -} -.k-upload-item-toggle { - grid-area: toggle; - align-self: end; -} -.k-upload-item-toggle > * { - padding: var(--spacing-3); -} -.k-upload-item[data-completed] .k-upload-item-progress { - --progress-color-value: var(--color-green-400); -} diff --git a/panel/src/components/Dialogs/UploadReplaceDialog.vue b/panel/src/components/Dialogs/UploadReplaceDialog.vue index 8952914b26..3b261c82c3 100644 --- a/panel/src/components/Dialogs/UploadReplaceDialog.vue +++ b/panel/src/components/Dialogs/UploadReplaceDialog.vue @@ -8,76 +8,24 @@ > @@ -101,6 +49,11 @@ export default { }; } } + }, + computed: { + file() { + return this.$panel.upload.files[0]; + } } }; diff --git a/panel/src/components/Dropdowns/DropdownContent.vue b/panel/src/components/Dropdowns/DropdownContent.vue index fafca3f62f..6bf446ec3c 100644 --- a/panel/src/components/Dropdowns/DropdownContent.vue +++ b/panel/src/components/Dropdowns/DropdownContent.vue @@ -187,10 +187,28 @@ export default { onOptionClick(option) { this.close(); + // click handler is a callback function + // which is executed if (typeof option.click === "function") { - option.click.call(this); - } else if (option.click) { - this.$emit("action", option.click); + return option.click.call(this); + } + + // click handler is an action string + // which is emitted to the parent + if (typeof option.click === "string") { + return this.$emit("action", option.click); + } + + // click handler is an object with a name and payload + // and optional flag to also emit globally + if (option.click) { + if (option.click.name) { + this.$emit(option.click.name, option.click.payload); + } + + if (option.click.global) { + this.$events.emit(option.click.global, option.click.payload); + } } }, /** diff --git a/panel/src/components/Dropdowns/DropdownItem.vue b/panel/src/components/Dropdowns/DropdownItem.vue index e161800a9a..165632efb4 100644 --- a/panel/src/components/Dropdowns/DropdownItem.vue +++ b/panel/src/components/Dropdowns/DropdownItem.vue @@ -22,11 +22,8 @@ export default { current: [Boolean, String], disabled: Boolean, icon: String, - image: [String, Object], link: String, - target: String, - theme: String, - upload: String + target: String }, emit: ["click"], methods: { diff --git a/panel/src/components/Forms/Blocks/Types/Fields.vue b/panel/src/components/Forms/Blocks/Types/Fields.vue index 9efdd637f3..fbab57bcd8 100644 --- a/panel/src/components/Forms/Blocks/Types/Fields.vue +++ b/panel/src/components/Forms/Blocks/Types/Fields.vue @@ -99,6 +99,7 @@ export default { .k-block-type-fields-header .k-block-title { padding-block: var(--spacing-3); cursor: pointer; + white-space: nowrap; } .k-block-type-fields-form { diff --git a/panel/src/components/Forms/Field/DateField.vue b/panel/src/components/Forms/Field/DateField.vue index c4176a51a8..72cd514dfa 100644 --- a/panel/src/components/Forms/Field/DateField.vue +++ b/panel/src/components/Forms/Field/DateField.vue @@ -136,10 +136,10 @@ export default { */ isEmpty() { if (this.time) { - return this.iso.date === null && this.iso.time; + return !this.iso.date || !this.iso.time; } - return this.iso.date === null; + return !this.iso.date; } }, watch: { diff --git a/panel/src/components/Forms/Field/ObjectField.vue b/panel/src/components/Forms/Field/ObjectField.vue index f85b2cbbd0..82a70ab847 100644 --- a/panel/src/components/Forms/Field/ObjectField.vue +++ b/panel/src/components/Forms/Field/ObjectField.vue @@ -123,8 +123,6 @@ export default { this.object = {}; this.save(); }, - // TODO: field is not yet used to pre-focus correct field - // eslint-disable-next-line no-unused-vars open(field) { if (this.disabled) { return false; diff --git a/panel/src/components/Forms/FormButtons.vue b/panel/src/components/Forms/FormButtons.vue index 869b20b6fa..ae97c175e6 100644 --- a/panel/src/components/Forms/FormButtons.vue +++ b/panel/src/components/Forms/FormButtons.vue @@ -230,7 +230,7 @@ export default { if (lock === true) { try { await this.$api.patch(...this.api); - } catch (error) { + } catch { // If setting lock failed, a competing lock has been set between // API calls. In that case, discard changes, stop setting lock clearInterval(this.isLocking); diff --git a/panel/src/components/Forms/Input/CalendarInput.vue b/panel/src/components/Forms/Input/CalendarInput.vue index 3acfdaa3cf..c6b102adff 100644 --- a/panel/src/components/Forms/Input/CalendarInput.vue +++ b/panel/src/components/Forms/Input/CalendarInput.vue @@ -99,13 +99,15 @@ import { IsoDateProps } from "./DateInput.vue"; export default { mixins: [InputProps, IsoDateProps], data() { + const today = this.$library.dayjs(); + return { maxdate: null, mindate: null, - month: null, + month: today.month(), selected: null, - today: this.$library.dayjs(), - year: null + today: today, + year: today.year() }; }, computed: { @@ -309,8 +311,10 @@ export default { * @param {number} day * @param {number} month */ - toDate(day = 1, month = this.month) { - return this.$library.dayjs(`${this.year}-${month + 1}-${day}`); + toDate(day = 1, month) { + return this.$library.dayjs( + `${this.year}-${(month ?? this.month) + 1}-${day}` + ); }, /** * Generates select options between min and max diff --git a/panel/src/components/Forms/Input/ColornameInput.vue b/panel/src/components/Forms/Input/ColornameInput.vue index aabaee7eec..eb49af01a9 100644 --- a/panel/src/components/Forms/Input/ColornameInput.vue +++ b/panel/src/components/Forms/Input/ColornameInput.vue @@ -68,7 +68,7 @@ export default { try { // first try to parse the color via the library return this.$library.colors.toString(value, this.format, this.alpha); - } catch (e) { + } catch { // if that fails, // create a new secret tester const test = document.createElement("div"); diff --git a/panel/src/components/Forms/Toolbar/EmailDialog.vue b/panel/src/components/Forms/Toolbar/EmailDialog.vue index f12b3cc920..4caa63b36d 100644 --- a/panel/src/components/Forms/Toolbar/EmailDialog.vue +++ b/panel/src/components/Forms/Toolbar/EmailDialog.vue @@ -3,6 +3,19 @@ import EmailDialog from "@/components/Dialogs/EmailDialog.vue"; export default { extends: EmailDialog, + props: { + // eslint-disable-next-line vue/require-prop-types + fields: { + default: () => { + const fields = EmailDialog.props.fields.default(); + + // change the label to "Link Text" + fields.title.label = window.panel.$t("link.text"); + + return fields; + } + } + }, methods: { submit() { const email = this.values.href ?? ""; diff --git a/panel/src/components/Forms/Writer/Nodes/HardBreak.js b/panel/src/components/Forms/Writer/Nodes/HardBreak.js index dab4d5e5f5..9b146c06d3 100644 --- a/panel/src/components/Forms/Writer/Nodes/HardBreak.js +++ b/panel/src/components/Forms/Writer/Nodes/HardBreak.js @@ -6,10 +6,7 @@ export default class HardBreak extends Node { } createHardBreak(utils, type) { - return utils.chainCommands(utils.exitCode, (state, dispatch) => { - dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView()); - return true; - }); + return utils.chainCommands(utils.exitCode, utils.insertNode(type)); } get defaults() { diff --git a/panel/src/components/Forms/Writer/Nodes/HorizontalRule.js b/panel/src/components/Forms/Writer/Nodes/HorizontalRule.js index 298e0940d7..9966035f1c 100644 --- a/panel/src/components/Forms/Writer/Nodes/HorizontalRule.js +++ b/panel/src/components/Forms/Writer/Nodes/HorizontalRule.js @@ -1,13 +1,20 @@ import Node from "../Node"; export default class HorizontalRule extends Node { - commands({ type }) { - return () => (state, dispatch) => - dispatch(state.tr.replaceSelectionWith(type.create())); + commands({ type, utils }) { + return () => utils.insertNode(type); } inputRules({ type, utils }) { - return [utils.nodeInputRule(/^(?:---|___\s|\*\*\*\s)$/, type)]; + // create regular input rule for horizontal rule + const rule = utils.nodeInputRule(/^(?:---|___\s|\*\*\*\s)$/, type); + + // extend handler to remove the leftover empty line + const handler = rule.handler; + rule.handler = (state, match, start, end) => + handler(state, match, start, end).replaceWith(start - 1, start, ""); + + return [rule]; } get name() { diff --git a/panel/src/components/Forms/Writer/Utils/index.js b/panel/src/components/Forms/Writer/Utils/index.js index e3c7f2bb77..e4f09ea93a 100644 --- a/panel/src/components/Forms/Writer/Utils/index.js +++ b/panel/src/components/Forms/Writer/Utils/index.js @@ -25,6 +25,7 @@ import { // custom import getMarkAttrs from "./getMarkAttrs"; import getNodeAttrs from "./getNodeAttrs"; +import insertNode from "./insertNode"; import markInputRule from "./markInputRule"; import markIsActive from "./markIsActive"; import markPasteRule from "./markPasteRule"; @@ -61,6 +62,7 @@ export default { // custom getMarkAttrs, getNodeAttrs, + insertNode, markInputRule, markIsActive, markPasteRule, diff --git a/panel/src/components/Forms/Writer/Utils/insertNode.js b/panel/src/components/Forms/Writer/Utils/insertNode.js new file mode 100644 index 0000000000..01eeb53a66 --- /dev/null +++ b/panel/src/components/Forms/Writer/Utils/insertNode.js @@ -0,0 +1,9 @@ +export default function insertNode(type, attrs, content, marks) { + return (state, dispatch) => { + dispatch( + state.tr + .replaceSelectionWith(type.create(attrs, content, marks)) + .scrollIntoView() + ); + }; +} diff --git a/panel/src/components/Forms/Writer/Utils/markPasteRule.js b/panel/src/components/Forms/Writer/Utils/markPasteRule.js index b2df111409..a281986156 100644 --- a/panel/src/components/Forms/Writer/Utils/markPasteRule.js +++ b/panel/src/components/Forms/Writer/Utils/markPasteRule.js @@ -13,7 +13,6 @@ export default function (regexp, type, getAttrs) { const isLink = !!marks.filter((x) => x.type.name === "link")[0]; - // eslint-disable-next-line while (!isLink && (match = regexp.exec(text)) !== null) { if (parent?.type?.allowsMarkType(type) && match[1]) { const start = match.index; diff --git a/panel/src/components/Forms/Writer/Utils/nodeInputRule.js b/panel/src/components/Forms/Writer/Utils/nodeInputRule.js index 067d903cde..2352238e6a 100644 --- a/panel/src/components/Forms/Writer/Utils/nodeInputRule.js +++ b/panel/src/components/Forms/Writer/Utils/nodeInputRule.js @@ -6,7 +6,7 @@ export default function (regexp, type, getAttrs) { const { tr } = state; if (match[0]) { - tr.replaceWith(start - 1, end, type.create(attrs)); + tr.replaceWith(start, end, type.create(attrs)); } return tr; diff --git a/panel/src/components/Forms/Writer/Writer.vue b/panel/src/components/Forms/Writer/Writer.vue index 4063da1402..fe529bf28e 100644 --- a/panel/src/components/Forms/Writer/Writer.vue +++ b/panel/src/components/Forms/Writer/Writer.vue @@ -285,11 +285,6 @@ export default { enter: this.inline }); - // inline fields only get the hard break - if (this.inline === true) { - return [hardBreak]; - } - return this.filterExtensions( { bulletList: new BulletList(), @@ -310,6 +305,11 @@ export default { installed.push(new ListItem()); } + // inline fields should not have non-inline nodes + if (this.inline === true) { + installed = installed.filter((node) => node.schema.inline === true); + } + // always install the hard break installed.push(hardBreak); diff --git a/panel/src/components/Forms/index.js b/panel/src/components/Forms/index.js index 5676a1c0b2..1cfaaafea4 100644 --- a/panel/src/components/Forms/index.js +++ b/panel/src/components/Forms/index.js @@ -6,13 +6,8 @@ import FormButtons from "./FormButtons.vue"; import Field from "./Field.vue"; import Fieldset from "./Fieldset.vue"; import Input from "./Input.vue"; -import Login from "./Login.vue"; -import LoginCode from "./LoginCode.vue"; import Upload from "./Upload.vue"; -/** Form Helpers */ -import LoginAlert from "./LoginAlert.vue"; - /* Form parts */ import Blocks from "./Blocks/index.js"; import Fields from "./Field/index.js"; @@ -31,12 +26,8 @@ export default { app.component("k-field", Field); app.component("k-fieldset", Fieldset); app.component("k-input", Input); - app.component("k-login", Login); - app.component("k-login-code", LoginCode); app.component("k-upload", Upload); - app.component("k-login-alert", LoginAlert); - app.use(Blocks); app.use(Inputs); app.use(Fields); diff --git a/panel/src/components/Layout/Tabs.vue b/panel/src/components/Layout/Tabs.vue index e8dfd22bdc..63f845c572 100644 --- a/panel/src/components/Layout/Tabs.vue +++ b/panel/src/components/Layout/Tabs.vue @@ -1,19 +1,18 @@