From a28838b8667645fda47df8a0d3911614fd601c35 Mon Sep 17 00:00:00 2001 From: Louis Chemineau Date: Wed, 9 Nov 2022 13:10:42 +0100 Subject: [PATCH] Use svg icons Signed-off-by: Louis Chemineau --- apps/comments/src/comments-tab.js | 6 +-- apps/files/src/models/Tab.js | 29 ++++++---- apps/files/src/views/Sidebar.vue | 15 ++++-- apps/files_sharing/src/files_sharing_tab.js | 6 +-- apps/files_versions/src/files_versions_tab.js | 6 +-- apps/files_versions/src/views/VersionTab.vue | 18 ++++--- package-lock.json | 54 +++++++++++++++++++ package.json | 2 + webpack.common.js | 5 +- 9 files changed, 111 insertions(+), 30 deletions(-) diff --git a/apps/comments/src/comments-tab.js b/apps/comments/src/comments-tab.js index 7b12de6de4f..ef17ce984a4 100644 --- a/apps/comments/src/comments-tab.js +++ b/apps/comments/src/comments-tab.js @@ -20,14 +20,14 @@ * */ -import MessageReplyText from 'vue-material-design-icons/MessageReplyText.vue' +import MessageReplyText from '@mdi/svg/svg/message-reply-text.svg?raw' // Init Comments tab component let TabInstance = null const commentTab = new OCA.Files.Sidebar.Tab({ id: 'comments', name: t('comments', 'Comments'), - icon: 'icon-comment', + iconSvg: MessageReplyText, async mount(el, fileInfo, context) { if (TabInstance) { @@ -53,7 +53,7 @@ const commentTab = new OCA.Files.Sidebar.Tab({ }, }) -window.addEventListener('DOMContentLoaded', function() { +window.addEventListener('DOMContentLoaded', function () { if (OCA.Files && OCA.Files.Sidebar) { OCA.Files.Sidebar.registerTab(commentTab) } diff --git a/apps/files/src/models/Tab.js b/apps/files/src/models/Tab.js index 9fd38f71bd7..cbf35c77dcb 100644 --- a/apps/files/src/models/Tab.js +++ b/apps/files/src/models/Tab.js @@ -19,12 +19,14 @@ * along with this program. If not, see . * */ +import { sanitizeSVG } from '@skjnldsv/sanitize-svg' export default class Tab { _id _name _icon + _iconSvgSanitized _mount _update _destroy @@ -37,19 +39,20 @@ export default class Tab { * @param {object} options destructuring object * @param {string} options.id the unique id of this tab * @param {string} options.name the translated tab name - * @param {string} options.icon the vue component + * @param {?string} options.icon the icon css class + * @param {?string} options.iconSvg the icon in svg format * @param {Function} options.mount function to mount the tab * @param {Function} options.update function to update the tab * @param {Function} options.destroy function to destroy the tab * @param {Function} [options.enabled] define conditions whether this tab is active. Must returns a boolean * @param {Function} [options.scrollBottomReached] executed when the tab is scrolled to the bottom */ - constructor({ id, name, icon, mount, update, destroy, enabled, scrollBottomReached } = {}) { + constructor({ id, name, icon, iconSvg, mount, update, destroy, enabled, scrollBottomReached } = {}) { if (enabled === undefined) { enabled = () => true } if (scrollBottomReached === undefined) { - scrollBottomReached = () => {} + scrollBottomReached = () => { } } // Sanity checks @@ -59,8 +62,8 @@ export default class Tab { if (typeof name !== 'string' || name.trim() === '') { throw new Error('The name argument is not a valid string') } - if ((typeof icon !== 'string' || icon.trim() === '') && typeof icon !== 'object') { - throw new Error('The icon argument is not a valid string or vuejs component') + if ((typeof icon !== 'string' || icon.trim() === '') && typeof iconSvg !== 'string') { + throw new Error('Missing valid string for icon or iconSvg argument') } if (typeof mount !== 'function') { throw new Error('The mount argument should be a function') @@ -81,12 +84,20 @@ export default class Tab { this._id = id this._name = name this._icon = icon + this._iconSvg = iconSvg this._mount = mount this._update = update this._destroy = destroy this._enabled = enabled this._scrollBottomReached = scrollBottomReached + if (typeof iconSvg === 'string') { + sanitizeSVG(iconSvg) + .then(sanitizedSvg => { + this._iconSvgSanitized = sanitizedSvg + }) + } + } get id() { @@ -97,14 +108,14 @@ export default class Tab { return this._name } - get isIconClass() { - return typeof this._icon === 'string' - } - get icon() { return this._icon } + get iconSvg() { + return this._iconSvgSanitized + } + get mount() { return this._mount } diff --git a/apps/files/src/views/Sidebar.vue b/apps/files/src/views/Sidebar.vue index 7c5ac8f0fdb..6c7d391b3c1 100644 --- a/apps/files/src/views/Sidebar.vue +++ b/apps/files/src/views/Sidebar.vue @@ -67,14 +67,15 @@ :id="tab.id" :key="tab.id" :name="tab.name" - :icon="tab.isIconClass ? tab.icon : undefined" + :icon="tab.icon" :on-mount="tab.mount" :on-update="tab.update" :on-destroy="tab.destroy" :on-scroll-bottom-reached="tab.scrollBottomReached" :file-info="fileInfo"> - @@ -512,5 +513,13 @@ export default { top: 0 !important; height: 100% !important; } + + .svg-icon { + ::v-deep svg { + width: 20px; + height: 20px; + fill: var(--color-main-text); + } + } } diff --git a/apps/files_sharing/src/files_sharing_tab.js b/apps/files_sharing/src/files_sharing_tab.js index 9694e2a9539..ed515e86e01 100644 --- a/apps/files_sharing/src/files_sharing_tab.js +++ b/apps/files_sharing/src/files_sharing_tab.js @@ -31,7 +31,7 @@ import ExternalLinkActions from './services/ExternalLinkActions.js' import ExternalShareActions from './services/ExternalShareActions.js' import TabSections from './services/TabSections.js' -import ShareVariant from 'vue-material-design-icons/ShareVariant.vue' +import ShareVariant from '@mdi/svg/svg/share-variant.svg?raw' // Init Sharing Tab Service if (!window.OCA.Sharing) { @@ -50,12 +50,12 @@ Vue.use(VueClipboard) const View = Vue.extend(SharingTab) let TabInstance = null -window.addEventListener('DOMContentLoaded', function() { +window.addEventListener('DOMContentLoaded', function () { if (OCA.Files && OCA.Files.Sidebar) { OCA.Files.Sidebar.registerTab(new OCA.Files.Sidebar.Tab({ id: 'sharing', name: t('files_sharing', 'Sharing'), - icon: ShareVariant, + iconSvg: ShareVariant, async mount(el, fileInfo, context) { if (TabInstance) { diff --git a/apps/files_versions/src/files_versions_tab.js b/apps/files_versions/src/files_versions_tab.js index d293a68510c..b4ab075b7a8 100644 --- a/apps/files_versions/src/files_versions_tab.js +++ b/apps/files_versions/src/files_versions_tab.js @@ -22,7 +22,7 @@ import { translate as t, translatePlural as n } from '@nextcloud/l10n' import VersionTab from './views/VersionTab.vue' import VTooltip from 'v-tooltip' -import BackupRestore from 'vue-material-design-icons/BackupRestore.vue' +import BackupRestore from '@mdi/svg/svg/backup-restore.svg?raw' Vue.prototype.t = t Vue.prototype.n = n @@ -33,12 +33,12 @@ Vue.use(VTooltip) const View = Vue.extend(VersionTab) let TabInstance = null -window.addEventListener('DOMContentLoaded', function() { +window.addEventListener('DOMContentLoaded', function () { if (OCA.Files && OCA.Files.Sidebar) { OCA.Files.Sidebar.registerTab(new OCA.Files.Sidebar.Tab({ id: 'version_vue', name: t('files_versions', 'Version'), - icon: BackupRestore, + iconSvg: BackupRestore, async mount(el, fileInfo, context) { if (TabInstance) { diff --git a/apps/files_versions/src/views/VersionTab.vue b/apps/files_versions/src/views/VersionTab.vue index 119dc95a60d..90664491941 100644 --- a/apps/files_versions/src/views/VersionTab.vue +++ b/apps/files_versions/src/views/VersionTab.vue @@ -19,8 +19,8 @@
    {{ t('files_versions', 'Download version') }} - + @@ -73,7 +73,6 @@ import { generateRemoteUrl, generateUrl } from '@nextcloud/router' import { getCurrentUser } from '@nextcloud/auth' import BackupRestore from 'vue-material-design-icons/BackupRestore.vue' import Download from 'vue-material-design-icons/Download.vue' -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js' import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js' @@ -108,6 +107,9 @@ function getDavRequest() { /** * Format version + * + * @param version + * @param fileInfo */ function formatVersion(version, fileInfo) { const fileVersion = basename(version.filename) @@ -117,7 +119,8 @@ function formatVersion(version, fileInfo) { ? generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0', { fileId: fileInfo.id, fileEtag: fileInfo.etag, - }) : generateUrl('/apps/files_versions/preview?file={file}&version={fileVersion}', { + }) + : generateUrl('/apps/files_versions/preview?file={file}&version={fileVersion}', { file: joinPaths(fileInfo.path, fileInfo.name), fileVersion, }) @@ -151,7 +154,6 @@ const client = createClient(remote) export default { name: 'VersionTab', components: { - NcButton, NcEmptyContent, NcActionLink, NcActionButton, @@ -192,7 +194,7 @@ export default { this.versions = response.map(version => formatVersion(version, this.fileInfo)) this.loading = false } catch (exception) { - logger.error('Could not fetch version', {exception}) + logger.error('Could not fetch version', { exception }) this.loading = false } }, @@ -205,14 +207,14 @@ export default { async restoreVersion(version) { try { logger.debug('restoring version', version.url) - const response = await client.moveFile( + await client.moveFile( `/versions/${getCurrentUser().uid}/versions/${this.fileInfo.id}/${version.fileVersion}`, `/versions/${getCurrentUser().uid}/restore/target` ) showSuccess(t('files_versions', 'Version restored')) await this.fetchVersions() } catch (exception) { - logger.error('Could not restore version', {exception}) + logger.error('Could not restore version', { exception }) showError(t('files_versions', 'Could not restore version')) } }, diff --git a/package-lock.json b/package-lock.json index a27c887e51b..91136685f19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "AGPL-3.0-or-later", "dependencies": { "@chenfengyuan/vue-qrcode": "^1.0.2", + "@mdi/svg": "^7.0.96", "@nextcloud/auth": "^1.3.0", "@nextcloud/axios": "^1.10.0", "@nextcloud/browser-storage": "^0.1.1", @@ -29,6 +30,7 @@ "@nextcloud/sharing": "^0.1.0", "@nextcloud/vue": "^7.1.0-beta.2", "@nextcloud/vue-dashboard": "^2.0.1", + "@skjnldsv/sanitize-svg": "^1.0.2", "autosize": "^5.0.1", "backbone": "^1.4.1", "blueimp-md5": "^2.19.0", @@ -3469,6 +3471,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mdi/svg": { + "version": "7.0.96", + "resolved": "https://registry.npmjs.org/@mdi/svg/-/svg-7.0.96.tgz", + "integrity": "sha512-5DC+w7Kl2C82j4aTWCUf6wtHzgY60WBf1gT1qrpkLaMNcH6Vj9FpYPAXdSmtdkmSMvVMs8i1Rtv9cXWcHFQYpw==" + }, "node_modules/@nextcloud/auth": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@nextcloud/auth/-/auth-1.3.0.tgz", @@ -4433,6 +4440,18 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "node_modules/@skjnldsv/sanitize-svg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@skjnldsv/sanitize-svg/-/sanitize-svg-1.0.2.tgz", + "integrity": "sha512-blfdQZ9jr4K9IOhifF0FVhKf9LCFH0L8wWR/vEgdA53q8DGNEbjUGMNo4VU1QugglaoQdFy65O2abODRFflsSg==", + "dependencies": { + "is-svg": "^4.3.2" + }, + "engines": { + "node": "^14.0.0", + "npm": "^7.0.0" + } + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -10711,6 +10730,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-svg": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-4.3.2.tgz", + "integrity": "sha512-mM90duy00JGMyjqIVHu9gNTjywdZV+8qNasX8cm/EEYZ53PHDgajvbBwNVvty5dwSAxLUD3p3bdo+7sR/UMrpw==", + "dependencies": { + "fast-xml-parser": "^3.19.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-symbol": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", @@ -22697,6 +22730,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@mdi/svg": { + "version": "7.0.96", + "resolved": "https://registry.npmjs.org/@mdi/svg/-/svg-7.0.96.tgz", + "integrity": "sha512-5DC+w7Kl2C82j4aTWCUf6wtHzgY60WBf1gT1qrpkLaMNcH6Vj9FpYPAXdSmtdkmSMvVMs8i1Rtv9cXWcHFQYpw==" + }, "@nextcloud/auth": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@nextcloud/auth/-/auth-1.3.0.tgz", @@ -23453,6 +23491,14 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@skjnldsv/sanitize-svg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@skjnldsv/sanitize-svg/-/sanitize-svg-1.0.2.tgz", + "integrity": "sha512-blfdQZ9jr4K9IOhifF0FVhKf9LCFH0L8wWR/vEgdA53q8DGNEbjUGMNo4VU1QugglaoQdFy65O2abODRFflsSg==", + "requires": { + "is-svg": "^4.3.2" + } + }, "@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -28314,6 +28360,14 @@ "has-tostringtag": "^1.0.0" } }, + "is-svg": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-4.3.2.tgz", + "integrity": "sha512-mM90duy00JGMyjqIVHu9gNTjywdZV+8qNasX8cm/EEYZ53PHDgajvbBwNVvty5dwSAxLUD3p3bdo+7sR/UMrpw==", + "requires": { + "fast-xml-parser": "^3.19.0" + } + }, "is-symbol": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", diff --git a/package.json b/package.json index f61edbd7d23..0f0e4d5e5c7 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "license": "AGPL-3.0-or-later", "dependencies": { "@chenfengyuan/vue-qrcode": "^1.0.2", + "@mdi/svg": "^7.0.96", "@nextcloud/auth": "^1.3.0", "@nextcloud/axios": "^1.10.0", "@nextcloud/browser-storage": "^0.1.1", @@ -49,6 +50,7 @@ "@nextcloud/sharing": "^0.1.0", "@nextcloud/vue": "^7.1.0-beta.2", "@nextcloud/vue-dashboard": "^2.0.1", + "@skjnldsv/sanitize-svg": "^1.0.2", "autosize": "^5.0.1", "backbone": "^1.4.1", "blueimp-md5": "^2.19.0", diff --git a/webpack.common.js b/webpack.common.js index a064cd2a2a6..c28bd764e4e 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -116,7 +116,10 @@ module.exports = { test: /\.handlebars/, loader: 'handlebars-loader', }, - + { + resourceQuery: /raw/, + type: 'asset/source', + }, ], },