diff --git a/.eslintrc.js b/.eslintrc.js index f155eea6c4..dd7b81beac 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,6 @@ +/** + * @type {import('eslint').ESLint.ConfigData} + */ module.exports = { globals: { EMOJIS: true, @@ -9,11 +12,21 @@ module.exports = { appVersion: true, }, extends: [ - '@nextcloud', + '@nextcloud/eslint-config/typescript', ], plugins: [ 'cypress', ], + overrides: [ + { + files: ['**/*.vue', '**/*.ts'], + rules: { + // Note: you must disable the base rule as it can report incorrect errors + 'func-call-spacing': 'off', + '@typescript-eslint/func-call-spacing': 'error', + }, + }, + ], parserOptions: { babelOptions: { plugins: [ diff --git a/package-lock.json b/package-lock.json index 7aedc7ef0e..35f31a904c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "unist-util-visit": "^5.0.0", "vue": "^2.7.14", "vue-color": "^2.8.1", + "vue-frag": "^1.4.3", "vue-material-design-icons": "^5.1.2", "vue2-datepicker": "^3.11.0" }, @@ -26686,6 +26687,17 @@ "node": ">=4.0" } }, + "node_modules/vue-frag": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/vue-frag/-/vue-frag-1.4.3.tgz", + "integrity": "sha512-pQZj03f/j9LRhzz9vKaXTCXUHVYHuAXicshFv76VFqwz4MG3bcb+sPZMAbd0wmw7THjkrTPuoM0EG9TbG8CgMQ==", + "funding": { + "url": "https://github.com/privatenumber/vue-frag?sponsor=1" + }, + "peerDependencies": { + "vue": "^2.6.0" + } + }, "node_modules/vue-hot-reload-api": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz", diff --git a/package.json b/package.json index 16e2dc5136..28c51bb7e5 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "unist-util-visit": "^5.0.0", "vue": "^2.7.14", "vue-color": "^2.8.1", + "vue-frag": "^1.4.3", "vue-material-design-icons": "^5.1.2", "vue2-datepicker": "^3.11.0" }, diff --git a/src/components/NcAppSettingsDialog/NcAppSettingsDialog.vue b/src/components/NcAppSettingsDialog/NcAppSettingsDialog.vue index def3fe08f6..acab5b7e9e 100644 --- a/src/components/NcAppSettingsDialog/NcAppSettingsDialog.vue +++ b/src/components/NcAppSettingsDialog/NcAppSettingsDialog.vue @@ -75,9 +75,39 @@ export default { ``` + + + * ``` + */ + navigationClasses?: string[] +}>(), { + size: 'normal', + container: 'body', + message: '', + buttons: () => [], + canClose: true, + open: true, + outTransition: false, + navigationClasses: () => [], +}) + +const emit = defineEmits<{ + /** Emitted when the dialog is about to close but the out transition did not finished yet */ + (e: 'closing'): void + /** Emitted when the open state changed */ + (e: 'update:open', v: boolean): void +}>() + +const slots = useSlots() + +/** + * The dialog wrapper element + */ +const wrapper = ref() + +/** + * We use the dialog width to decide if we collapse the navigation (flex direction row) + */ +const { width: dialogWidth } = useElementSize(wrapper) + +/** + * Whether the navigation is collapsed due to dialog and window size + * (collapses when modal is below: 900px modal width - 2x 12px margin) + */ +const isNavigationCollapsed = computed(() => dialogWidth.value < 876) + +/** + * Whether a navigation was passed and the element should be displayed + */ +const hasNavigation = computed(() => slots?.navigation !== undefined) + +const showModal = ref(true) + +// Because NcModal does not emit `close` when show prop is changed +const handleButtonClose = () => { + handleClosing() + window.setTimeout(() => handleClosed(), 300) +} + +/** + * Handle closing the dialog, optional out transition did not run yet + */ +const handleClosing = () => { + showModal.value = false + emit('closing') +} + +/** + * Handle dialog closed (out transition finished) + */ +const handleClosed = () => { + console.warn('closed') + showModal.value = true + emit('update:open', false) +} + +/** + * Properties to pass to the underlying NcModal + */ +const modalProps = computed(() => ({ + canClose: props.canClose, + container: props.container, + name: props.name, + size: props.size, + show: props.open && showModal.value, + outTransition: props.outTransition, + class: 'dialog__modal', + enableSlideshow: false, + enableSwipe: false, +})) + + + diff --git a/src/components/NcDialog/NcDialogButton.vue b/src/components/NcDialog/NcDialogButton.vue new file mode 100644 index 0000000000..3017644964 --- /dev/null +++ b/src/components/NcDialog/NcDialogButton.vue @@ -0,0 +1,61 @@ + + +Dialog button component used by NcDialog in the actions slot to display the buttons passed by the `buttons` prop. + + + + diff --git a/src/components/NcDialog/index.ts b/src/components/NcDialog/index.ts new file mode 100644 index 0000000000..dd37a467d6 --- /dev/null +++ b/src/components/NcDialog/index.ts @@ -0,0 +1,26 @@ +/** + * @copyright Copyright (c) 2023 Ferdinand Thiessen + * + * @author Ferdinand Thiessen + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +export { default as NcDialog } from './NcDialog.vue' +export { default as NcDialogButton } from './NcDialogButton.vue' + +export type { IDialogButton } from './types' diff --git a/src/components/NcDialog/types.ts b/src/components/NcDialog/types.ts new file mode 100644 index 0000000000..27d743daad --- /dev/null +++ b/src/components/NcDialog/types.ts @@ -0,0 +1,41 @@ +/** + * @copyright Copyright (c) 2023 Ferdinand Thiessen + * + * @author Ferdinand Thiessen + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +import type { AsyncComponent, Component } from 'vue' + +/** + * Properties of the dialog button + * + * Exported to be used by NcDialog for the `buttons` prop + */ +export interface IDialogButton { + /** Text of the button */ + label: string, + /** Icon component used for the button */ + icon?: Component | AsyncComponent, + /** Callback which is called on click */ + callback: () => void, + /** + * Button type + * @see NcButton + */ + type?: 'primary' | 'secondary' | 'error' | 'warning' | 'success' +} diff --git a/src/components/NcRichText/NcRichText.vue b/src/components/NcRichText/NcRichText.vue index 3cfae8ccb0..ed49f030bd 100644 --- a/src/components/NcRichText/NcRichText.vue +++ b/src/components/NcRichText/NcRichText.vue @@ -138,16 +138,15 @@ export default { }, methods: { renderPlaintext(h) { - const context = this - const placeholders = this.text.split(/(\{[a-z\-_.0-9]+\})/ig).map(function(entry, index, list) { + const placeholders = this.text.split(/(\{[a-z\-_.0-9]+\})/ig).map((entry) => { const matches = entry.match(/^\{([a-z\-_.0-9]+)\}$/i) // just return plain string nodes as text if (!matches) { - return prepareTextNode({ h, context }, entry) + return prepareTextNode({ h, context: this }, entry) } // return component instance if argument is an object const argumentId = matches[1] - const argument = context.arguments[argumentId] + const argument = this.arguments[argumentId] if (typeof argument === 'object') { const { component, props } = argument return h(component, { diff --git a/src/components/index.js b/src/components/index.js index d40c433f15..4a790953ee 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -60,6 +60,7 @@ export { default as NcDashboardWidgetItem } from './NcDashboardWidgetItem/index. export { default as NcDatetime } from './NcDatetime/index.js' export { default as NcDatetimePicker } from './NcDatetimePicker/index.js' export { default as NcDateTimePickerNative } from './NcDateTimePickerNative/index.js' +export { NcDialog, NcDialogButton } from './NcDialog/index.ts' // Not exported on purpose // export { default as NcEllipsisedOption } from './NcEllipsisedOption/index.js' export { default as NcEmojiPicker } from './NcEmojiPicker/index.js' diff --git a/src/components/vue-shims.d.ts b/src/components/vue-shims.d.ts new file mode 100644 index 0000000000..079dded525 --- /dev/null +++ b/src/components/vue-shims.d.ts @@ -0,0 +1,4 @@ +declare module '*.vue' { + import Vue from 'vue' + export default Vue +} diff --git a/src/plugin.ts b/src/plugin.ts index 9429ab4c18..41e7292f63 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -21,7 +21,8 @@ import * as NcDirectives from './directives/index.js' export const NextcloudVuePlugin: PluginObject = { install(Vue) { // Install components - Object.entries(NcComponents as { [key: string]: DefineComponent }).forEach(([name, component]) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Object.entries(NcComponents as any as { [key: string]: DefineComponent }).forEach(([name, component]) => { Vue.component(component.name || name, component) }) diff --git a/tsconfig.json b/tsconfig.json index fa4afcb17e..00d23f6568 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "include": ["./src/**/*.ts"], "exclude": ["./src/**/*.cy.ts"], "compilerOptions": { + "allowJs": true, "allowSyntheticDefaultImports": true, "moduleResolution": "node", "target": "ESNext", diff --git a/webpack.config.js b/webpack.config.js index bae3ec7a18..9e736dbc11 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -110,9 +110,12 @@ webpackRules.RULE_NODE_MJS = { type: 'javascript/auto', resolve: { fullySpecified: false, - } + }, } +// Support vue + Typescript +webpackRules.RULE_TS.use = ['babel-loader', { loader: 'ts-loader', options: { appendTsSuffixTo: [/\.vue$/] } }] + webpackConfig.module.rules = Object.values(webpackRules) module.exports = async () => {