Use svg icons

Signed-off-by: Louis Chemineau <louis@chmn.me>
pull/34769/head
Louis Chemineau 2 years ago
parent 8829019101
commit a28838b866

@ -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 // Init Comments tab component
let TabInstance = null let TabInstance = null
const commentTab = new OCA.Files.Sidebar.Tab({ const commentTab = new OCA.Files.Sidebar.Tab({
id: 'comments', id: 'comments',
name: t('comments', 'Comments'), name: t('comments', 'Comments'),
icon: 'icon-comment', iconSvg: MessageReplyText,
async mount(el, fileInfo, context) { async mount(el, fileInfo, context) {
if (TabInstance) { 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) { if (OCA.Files && OCA.Files.Sidebar) {
OCA.Files.Sidebar.registerTab(commentTab) OCA.Files.Sidebar.registerTab(commentTab)
} }

@ -19,12 +19,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
import { sanitizeSVG } from '@skjnldsv/sanitize-svg'
export default class Tab { export default class Tab {
_id _id
_name _name
_icon _icon
_iconSvgSanitized
_mount _mount
_update _update
_destroy _destroy
@ -37,19 +39,20 @@ export default class Tab {
* @param {object} options destructuring object * @param {object} options destructuring object
* @param {string} options.id the unique id of this tab * @param {string} options.id the unique id of this tab
* @param {string} options.name the translated tab name * @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.mount function to mount the tab
* @param {Function} options.update function to update the tab * @param {Function} options.update function to update the tab
* @param {Function} options.destroy function to destroy 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.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 * @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) { if (enabled === undefined) {
enabled = () => true enabled = () => true
} }
if (scrollBottomReached === undefined) { if (scrollBottomReached === undefined) {
scrollBottomReached = () => {} scrollBottomReached = () => { }
} }
// Sanity checks // Sanity checks
@ -59,8 +62,8 @@ export default class Tab {
if (typeof name !== 'string' || name.trim() === '') { if (typeof name !== 'string' || name.trim() === '') {
throw new Error('The name argument is not a valid string') throw new Error('The name argument is not a valid string')
} }
if ((typeof icon !== 'string' || icon.trim() === '') && typeof icon !== 'object') { if ((typeof icon !== 'string' || icon.trim() === '') && typeof iconSvg !== 'string') {
throw new Error('The icon argument is not a valid string or vuejs component') throw new Error('Missing valid string for icon or iconSvg argument')
} }
if (typeof mount !== 'function') { if (typeof mount !== 'function') {
throw new Error('The mount argument should be a function') throw new Error('The mount argument should be a function')
@ -81,12 +84,20 @@ export default class Tab {
this._id = id this._id = id
this._name = name this._name = name
this._icon = icon this._icon = icon
this._iconSvg = iconSvg
this._mount = mount this._mount = mount
this._update = update this._update = update
this._destroy = destroy this._destroy = destroy
this._enabled = enabled this._enabled = enabled
this._scrollBottomReached = scrollBottomReached this._scrollBottomReached = scrollBottomReached
if (typeof iconSvg === 'string') {
sanitizeSVG(iconSvg)
.then(sanitizedSvg => {
this._iconSvgSanitized = sanitizedSvg
})
}
} }
get id() { get id() {
@ -97,14 +108,14 @@ export default class Tab {
return this._name return this._name
} }
get isIconClass() {
return typeof this._icon === 'string'
}
get icon() { get icon() {
return this._icon return this._icon
} }
get iconSvg() {
return this._iconSvgSanitized
}
get mount() { get mount() {
return this._mount return this._mount
} }

@ -67,14 +67,15 @@
:id="tab.id" :id="tab.id"
:key="tab.id" :key="tab.id"
:name="tab.name" :name="tab.name"
:icon="tab.isIconClass ? tab.icon : undefined" :icon="tab.icon"
:on-mount="tab.mount" :on-mount="tab.mount"
:on-update="tab.update" :on-update="tab.update"
:on-destroy="tab.destroy" :on-destroy="tab.destroy"
:on-scroll-bottom-reached="tab.scrollBottomReached" :on-scroll-bottom-reached="tab.scrollBottomReached"
:file-info="fileInfo"> :file-info="fileInfo">
<template #icon v-if="!tab.isIconClass"> <template v-if="tab.iconSvg !== undefined" #icon>
<component :is="tab.icon" /> <!-- eslint-disable-next-line vue/no-v-html -->
<span class="svg-icon" v-html="tab.iconSvg" />
</template> </template>
</SidebarTab> </SidebarTab>
</template> </template>
@ -512,5 +513,13 @@ export default {
top: 0 !important; top: 0 !important;
height: 100% !important; height: 100% !important;
} }
.svg-icon {
::v-deep svg {
width: 20px;
height: 20px;
fill: var(--color-main-text);
}
}
} }
</style> </style>

@ -31,7 +31,7 @@ import ExternalLinkActions from './services/ExternalLinkActions.js'
import ExternalShareActions from './services/ExternalShareActions.js' import ExternalShareActions from './services/ExternalShareActions.js'
import TabSections from './services/TabSections.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 // Init Sharing Tab Service
if (!window.OCA.Sharing) { if (!window.OCA.Sharing) {
@ -50,12 +50,12 @@ Vue.use(VueClipboard)
const View = Vue.extend(SharingTab) const View = Vue.extend(SharingTab)
let TabInstance = null let TabInstance = null
window.addEventListener('DOMContentLoaded', function() { window.addEventListener('DOMContentLoaded', function () {
if (OCA.Files && OCA.Files.Sidebar) { if (OCA.Files && OCA.Files.Sidebar) {
OCA.Files.Sidebar.registerTab(new OCA.Files.Sidebar.Tab({ OCA.Files.Sidebar.registerTab(new OCA.Files.Sidebar.Tab({
id: 'sharing', id: 'sharing',
name: t('files_sharing', 'Sharing'), name: t('files_sharing', 'Sharing'),
icon: ShareVariant, iconSvg: ShareVariant,
async mount(el, fileInfo, context) { async mount(el, fileInfo, context) {
if (TabInstance) { if (TabInstance) {

@ -22,7 +22,7 @@ import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import VersionTab from './views/VersionTab.vue' import VersionTab from './views/VersionTab.vue'
import VTooltip from 'v-tooltip' 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.t = t
Vue.prototype.n = n Vue.prototype.n = n
@ -33,12 +33,12 @@ Vue.use(VTooltip)
const View = Vue.extend(VersionTab) const View = Vue.extend(VersionTab)
let TabInstance = null let TabInstance = null
window.addEventListener('DOMContentLoaded', function() { window.addEventListener('DOMContentLoaded', function () {
if (OCA.Files && OCA.Files.Sidebar) { if (OCA.Files && OCA.Files.Sidebar) {
OCA.Files.Sidebar.registerTab(new OCA.Files.Sidebar.Tab({ OCA.Files.Sidebar.registerTab(new OCA.Files.Sidebar.Tab({
id: 'version_vue', id: 'version_vue',
name: t('files_versions', 'Version'), name: t('files_versions', 'Version'),
icon: BackupRestore, iconSvg: BackupRestore,
async mount(el, fileInfo, context) { async mount(el, fileInfo, context) {
if (TabInstance) { if (TabInstance) {

@ -19,8 +19,8 @@
<div> <div>
<ul> <ul>
<NcListItem v-for="version in versions" <NcListItem v-for="version in versions"
:key="version.dateTime.unix()"
class="version" class="version"
key="version.url"
:title="version.title" :title="version.title"
:href="version.url"> :href="version.url">
<template #icon> <template #icon>
@ -47,7 +47,7 @@
</template> </template>
{{ t('files_versions', 'Download version') }} {{ t('files_versions', 'Download version') }}
</NcActionLink> </NcActionLink>
<NcActionButton @click="restoreVersion(version)" v-if="!version.isCurrent"> <NcActionButton v-if="!version.isCurrent" @click="restoreVersion(version)">
<template #icon> <template #icon>
<BackupRestore :size="22" /> <BackupRestore :size="22" />
</template> </template>
@ -73,7 +73,6 @@ import { generateRemoteUrl, generateUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth' import { getCurrentUser } from '@nextcloud/auth'
import BackupRestore from 'vue-material-design-icons/BackupRestore.vue' import BackupRestore from 'vue-material-design-icons/BackupRestore.vue'
import Download from 'vue-material-design-icons/Download.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 NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js' import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js'
import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js' import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js'
@ -108,6 +107,9 @@ function getDavRequest() {
/** /**
* Format version * Format version
*
* @param version
* @param fileInfo
*/ */
function formatVersion(version, fileInfo) { function formatVersion(version, fileInfo) {
const fileVersion = basename(version.filename) 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', { ? generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0', {
fileId: fileInfo.id, fileId: fileInfo.id,
fileEtag: fileInfo.etag, 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), file: joinPaths(fileInfo.path, fileInfo.name),
fileVersion, fileVersion,
}) })
@ -151,7 +154,6 @@ const client = createClient(remote)
export default { export default {
name: 'VersionTab', name: 'VersionTab',
components: { components: {
NcButton,
NcEmptyContent, NcEmptyContent,
NcActionLink, NcActionLink,
NcActionButton, NcActionButton,
@ -192,7 +194,7 @@ export default {
this.versions = response.map(version => formatVersion(version, this.fileInfo)) this.versions = response.map(version => formatVersion(version, this.fileInfo))
this.loading = false this.loading = false
} catch (exception) { } catch (exception) {
logger.error('Could not fetch version', {exception}) logger.error('Could not fetch version', { exception })
this.loading = false this.loading = false
} }
}, },
@ -205,14 +207,14 @@ export default {
async restoreVersion(version) { async restoreVersion(version) {
try { try {
logger.debug('restoring version', version.url) 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}/versions/${this.fileInfo.id}/${version.fileVersion}`,
`/versions/${getCurrentUser().uid}/restore/target` `/versions/${getCurrentUser().uid}/restore/target`
) )
showSuccess(t('files_versions', 'Version restored')) showSuccess(t('files_versions', 'Version restored'))
await this.fetchVersions() await this.fetchVersions()
} catch (exception) { } catch (exception) {
logger.error('Could not restore version', {exception}) logger.error('Could not restore version', { exception })
showError(t('files_versions', 'Could not restore version')) showError(t('files_versions', 'Could not restore version'))
} }
}, },

54
package-lock.json generated

@ -10,6 +10,7 @@
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@chenfengyuan/vue-qrcode": "^1.0.2", "@chenfengyuan/vue-qrcode": "^1.0.2",
"@mdi/svg": "^7.0.96",
"@nextcloud/auth": "^1.3.0", "@nextcloud/auth": "^1.3.0",
"@nextcloud/axios": "^1.10.0", "@nextcloud/axios": "^1.10.0",
"@nextcloud/browser-storage": "^0.1.1", "@nextcloud/browser-storage": "^0.1.1",
@ -29,6 +30,7 @@
"@nextcloud/sharing": "^0.1.0", "@nextcloud/sharing": "^0.1.0",
"@nextcloud/vue": "^7.1.0-beta.2", "@nextcloud/vue": "^7.1.0-beta.2",
"@nextcloud/vue-dashboard": "^2.0.1", "@nextcloud/vue-dashboard": "^2.0.1",
"@skjnldsv/sanitize-svg": "^1.0.2",
"autosize": "^5.0.1", "autosize": "^5.0.1",
"backbone": "^1.4.1", "backbone": "^1.4.1",
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",
@ -3469,6 +3471,11 @@
"@jridgewell/sourcemap-codec": "^1.4.10" "@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": { "node_modules/@nextcloud/auth": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@nextcloud/auth/-/auth-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@nextcloud/auth/-/auth-1.3.0.tgz",
@ -4433,6 +4440,18 @@
"integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
"dev": true "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": { "node_modules/@socket.io/component-emitter": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", "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" "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": { "node_modules/is-symbol": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
@ -22697,6 +22730,11 @@
"@jridgewell/sourcemap-codec": "^1.4.10" "@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": { "@nextcloud/auth": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@nextcloud/auth/-/auth-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@nextcloud/auth/-/auth-1.3.0.tgz",
@ -23453,6 +23491,14 @@
"integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
"dev": true "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": { "@socket.io/component-emitter": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
@ -28314,6 +28360,14 @@
"has-tostringtag": "^1.0.0" "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": { "is-symbol": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",

@ -30,6 +30,7 @@
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@chenfengyuan/vue-qrcode": "^1.0.2", "@chenfengyuan/vue-qrcode": "^1.0.2",
"@mdi/svg": "^7.0.96",
"@nextcloud/auth": "^1.3.0", "@nextcloud/auth": "^1.3.0",
"@nextcloud/axios": "^1.10.0", "@nextcloud/axios": "^1.10.0",
"@nextcloud/browser-storage": "^0.1.1", "@nextcloud/browser-storage": "^0.1.1",
@ -49,6 +50,7 @@
"@nextcloud/sharing": "^0.1.0", "@nextcloud/sharing": "^0.1.0",
"@nextcloud/vue": "^7.1.0-beta.2", "@nextcloud/vue": "^7.1.0-beta.2",
"@nextcloud/vue-dashboard": "^2.0.1", "@nextcloud/vue-dashboard": "^2.0.1",
"@skjnldsv/sanitize-svg": "^1.0.2",
"autosize": "^5.0.1", "autosize": "^5.0.1",
"backbone": "^1.4.1", "backbone": "^1.4.1",
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",

@ -116,7 +116,10 @@ module.exports = {
test: /\.handlebars/, test: /\.handlebars/,
loader: 'handlebars-loader', loader: 'handlebars-loader',
}, },
{
resourceQuery: /raw/,
type: 'asset/source',
},
], ],
}, },

Loading…
Cancel
Save