*
*/
-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) {
},
})
-window.addEventListener('DOMContentLoaded', function() {
+window.addEventListener('DOMContentLoaded', function () {
if (OCA.Files && OCA.Files.Sidebar) {
OCA.Files.Sidebar.registerTab(commentTab)
}
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
+import { sanitizeSVG } from '@skjnldsv/sanitize-svg'
export default class Tab {
_id
_name
_icon
+ _iconSvgSanitized
_mount
_update
_destroy
* @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
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')
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() {
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
}
: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">
- <template #icon v-if="!tab.isIconClass">
- <component :is="tab.icon" />
+ <template v-if="tab.iconSvg !== undefined" #icon>
+ <!-- eslint-disable-next-line vue/no-v-html -->
+ <span class="svg-icon" v-html="tab.iconSvg" />
</template>
</SidebarTab>
</template>
top: 0 !important;
height: 100% !important;
}
+
+ .svg-icon {
+ ::v-deep svg {
+ width: 20px;
+ height: 20px;
+ fill: var(--color-main-text);
+ }
+ }
}
</style>
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) {
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) {
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
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) {
<div>
<ul>
<NcListItem v-for="version in versions"
+ :key="version.dateTime.unix()"
class="version"
- key="version.url"
:title="version.title"
:href="version.url">
<template #icon>
</template>
{{ t('files_versions', 'Download version') }}
</NcActionLink>
- <NcActionButton @click="restoreVersion(version)" v-if="!version.isCurrent">
+ <NcActionButton v-if="!version.isCurrent" @click="restoreVersion(version)">
<template #icon>
<BackupRestore :size="22" />
</template>
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'
/**
* Format version
+ *
+ * @param version
+ * @param fileInfo
*/
function formatVersion(version, fileInfo) {
const fileVersion = basename(version.filename)
? 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,
})
export default {
name: 'VersionTab',
components: {
- NcButton,
NcEmptyContent,
NcActionLink,
NcActionButton,
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
}
},
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'))
}
},
"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",
"@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",
"@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",
"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",
"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",
"@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",
"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",
"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",
"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",
"@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",
test: /\.handlebars/,
loader: 'handlebars-loader',
},
-
+ {
+ resourceQuery: /raw/,
+ type: 'asset/source',
+ },
],
},