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.
+
+
+
+ {{ props.label }}
+
+
+
+
+
+
+
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 () => {