1 line
80 KiB
XML
1 line
80 KiB
XML
{"version":3,"mappings":"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EnB,EAKD,aAAc,CACb,KAAK,MAAM,kBAAmB,EAAI,CAClC,EAKD,cAAe,CACd,KAAK,MAAM,kBAAmB,EAAK,CACnC,EAKD,kBAAmB,CACd,KAAK,aACR,SAAS,eAAc,EAEvB,KAAK,IAAI,kBAAiB,CAE3B,CACD,CACF,ECrMAlD,GAAA,CACA,2BACA,gBACA,OACA,OACA,WACA,EACA,WACA,YACA,sBACA,EACA,MACA,YACA,UACA,CACA,CACA,2nBChBAA,GAAA,CACA,yBACA,gBACA,OACA,OACA,WACA,EACA,WACA,YACA,sBACA,EACA,MACA,YACA,UACA,CACA,CACA,+pBChBAA,GAAA,CACA,sBACA,gBACA,OACA,OACA,WACA,EACA,WACA,YACA,sBACA,EACA,MACA,YACA,UACA,CACA,CACA,8lBChBAA,GAAA,CACA,0BACA,gBACA,OACA,OACA,WACA,EACA,WACA,YACA,sBACA,EACA,MACA,YACA,UACA,CACA,CACA,kmBChBAA,GAAA,CACA,yBACA,gBACA,OACA,OACA,WACA,EACA,WACA,YACA,sBACA,EACA,MACA,YACA,UACA,CACA,CACA,8uBC4KAqD,GAAA,IAAAC,EAAA,qDAAAC,KAAA,4CACAC,GAAA,IAAAF,EAAA,8FACAG,GAAA,IAAAH,EAAA,gGAEAtD,GAAA0D,GAAA,CACA,cAEA,YACA,OAAAC,GACA,SAAAC,GACA,MAAAC,GACA,WAAAC,GACA,eAAAC,GACA,eAAAN,GACA,aAAAD,GACA,QAAAH,GACA,OAAAW,EACA,EAEA,QAAAC,GAAAC,EAAA,EAEA,OACA,OAEA,kBACA,aACA,sCAGA,cACA,cACA,sBAGA,eACA,gBACA,eACA,oBACA,YACA,YACA,mBAGA,YACA,aACA,WAGA,yBACA,2BAGA,kBACA,kBACA,oBACA,YACA,gBACA,WACA,mBACA,KAAAC,EACA,aAEA,eACA,CACA,EAEA,UACA,eACA,wDACA,EACA,cACA,gCACA,kCACA,EACA,UACA,gCACA,gCACA,EACA,OACA,uBACA,EACA,WACA,2BACA,EACA,qBACA,kCACA,EACA,QACA,wBACA,E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","names":["index","isFullscreenState","isMobileState","canDownload","fileInfo","shareAttributes","scope","key","CancelableRequest","request","controller","url","options","_sfc_main","fetchNode","path","client","getClient","propfindPayload","getDefaultPropfind","result","getRootPath","resultToNode","File","mime","component","data","getDavPath","getFileList","getDavNameSpaces","getDavProperties","genFileInfo","getSortingConfig","viewConfigs","getViewConfigs","asc","isPublicShare","generateUrl","axios","response","PreviewUrl","getPreviewIfAny","isWindows","process","splitWindowsRe","win32","win32SplitPath","filename","pathString","allParts","splitPathRe","posix","posixSplitPath","pathParseModule","Mime","parsePath","val","old","e","debounce","modalWrapper","modalContainer","parentHeight","parentWidth","heightRatio","widthRatio","NcModal","__vitePreload","n","NcActionLink","NcActionButton","defineComponent","Delete","Download","Error","Fullscreen","FullscreenExit","Pencil","isFullscreen","isMobile","davRemoteURL","davRootPath","loadState","element","logger","viewerRoot","el","fileList","currentIndex","file","isEndOfList","list","handler","subscribe","unsubscribe","isOpen","toggleEditor","event","overrideHandlerId","fileRequest","cancelRequestFile","cancelableRequest","getFileInfo","fileName","extractFilePaths","title","error","showError","source","extractFilePathFromSource","node","emit","alias","h","defaultThemeIsLight","group","mimes","folderRequest","cancelRequestFolder","dirPath","filteredFiles","nodes","NcFile","davGetRootPath","sortedNodes","sortNodes","prev","next","Vue","sidebar","currentFileId","fileid","a","t","ViewerRoot","VideoControls","ViewerComponent"],"ignoreList":[0,1,10,12,13,14,15,16],"sources":["../node_modules/@nextcloud/vue/dist/Mixins/isFullscreen.mjs","../node_modules/@nextcloud/vue/dist/Mixins/isMobile.mjs","../src/utils/canDownload.ts","../src/utils/CancelableRequest.js","../src/components/Error.vue","../src/services/FetchFile.ts","../src/models/file.js","../src/services/FileList.ts","../src/services/FileSortingConfig.ts","../src/mixins/PreviewUrl.js","../node_modules/path-parse/index.js","../src/mixins/Mime.js","../node_modules/vue-material-design-icons/TrashCanOutline.vue","../node_modules/vue-material-design-icons/TrayArrowDown.vue","../node_modules/vue-material-design-icons/Fullscreen.vue","../node_modules/vue-material-design-icons/FullscreenExit.vue","../node_modules/vue-material-design-icons/PencilOutline.vue","../src/views/Viewer.vue","../src/main.js"],"sourcesContent":["import { isFullscreenState } from \"../Composables/useIsFullscreen.mjs\";\nconst index = {\n computed: {\n /**\n * @deprecated Is to be removed in v9.0.0 with Vue 3 migration.\n * Use `composables/useIsFullscreen` instead.\n */\n isFullscreen() {\n return isFullscreenState.value;\n }\n }\n};\nexport {\n index as default\n};\n//# sourceMappingURL=isFullscreen.mjs.map\n","import { isMobileState } from \"../Composables/useIsMobile.mjs\";\nconst index = {\n computed: {\n /**\n * @deprecated Is to be removed in v9.0.0 with Vue 3 migration.\n * Use `composables/useIsMobile` instead.\n */\n isMobile() {\n return isMobileState.value;\n }\n }\n};\nexport {\n index as default\n};\n//# sourceMappingURL=isMobile.mjs.map\n","/*!\n * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { FileInfo } from './fileUtils'\n\n/**\n * Check if download permissions are granted for a file\n * @param fileInfo The file info to check\n */\nexport function canDownload(fileInfo: FileInfo) {\n\tif (fileInfo.hideDownload) {\n\t\treturn false\n\t}\n\n\t// TODO: This should probably be part of `@nextcloud/sharing`\n\t// check share attributes\n\tconst shareAttributes = typeof fileInfo?.shareAttributes === 'string' ? JSON.parse(fileInfo.shareAttributes || '[]') : fileInfo?.shareAttributes\n\n\tif (shareAttributes && shareAttributes.length > 0) {\n\t\tconst downloadAttribute = shareAttributes.find(({ scope, key }) => scope === 'permissions' && key === 'download')\n\t\t// We only forbid download if the attribute is *explicitly* set to 'false'\n\t\treturn downloadAttribute?.value !== false\n\t}\n\t// otherwise return true (as the file needs read permission otherwise we would not have opened it)\n\treturn true\n}\n","/**\n * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/**\n * Creates a cancelable axios 'request object'.\n *\n * @param {Function} request the axios promise request\n * @return {object}\n */\nconst CancelableRequest = function(request) {\n\tconst controller = new AbortController()\n\n\t/**\n\t * Execute the request\n\t *\n\t * @param {string} url the url to send the request to\n\t * @param {object} [options] optional config for the request\n\t */\n\tconst fetch = async function(url, options) {\n\t\treturn request(\n\t\t\turl,\n\t\t\t{ ...options, signal: controller.signal },\n\t\t)\n\t}\n\treturn {\n\t\trequest: fetch,\n\t\tcancel: () => controller.abort(),\n\t}\n}\n\nexport default CancelableRequest\n","<!--\n - SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors\n - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n\n<template>\n\t<div id=\"emptycontent\">\n\t\t<div class=\"icon-error\" />\n\t\t<h2>\n\t\t\t<slot>{{ t('viewer', 'Error loading {name}', { name }) }}</slot>\n\t\t</h2>\n\t</div>\n</template>\n\n<script>\nexport default {\n\tname: 'Error',\n\n\tprops: {\n\t\tname: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n#emptycontent {\n\tmargin: 0;\n\tpadding: 10% 5%;\n\tbackground-color: var(--color-main-background);\n}\n</style>\n","/**\n * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { getClient, getDefaultPropfind, getRootPath, resultToNode } from '@nextcloud/files/dav'\nimport type { FileStat, ResponseDataDetailed } from 'webdav'\nimport type { Node } from '@nextcloud/files'\n\nexport default async (path: string): Promise<Node> => {\n\tif (!path.startsWith('/')) {\n\t\tpath = `/${path}`\n\t}\n\tconst client = getClient()\n\tconst propfindPayload = getDefaultPropfind()\n\tconst result = await client.stat(`${getRootPath()}${path}`, {\n\t\tdetails: true,\n\t\tdata: propfindPayload,\n\t}) as ResponseDataDetailed<FileStat>\n\treturn resultToNode(result.data)\n}\n","/**\n * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\nimport { getDavPath } from '../utils/fileUtils.ts'\n\n/**\n * @param {object} fileInfo a FileInfo object\n * @param {string} mime the file mime type\n * @param {object} component the component to render\n */\nexport default function(fileInfo, mime, component) {\n\tconst data = {\n\t\tmime,\n\t\tmodal: component,\n\t\tfailed: false,\n\t\tloaded: false,\n\t\tdavPath: getDavPath(fileInfo),\n\t\tsource: fileInfo.source ?? getDavPath(fileInfo),\n\t}\n\n\treturn Object.assign({}, fileInfo, data)\n}\n","/**\n * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { getDavNameSpaces, getDavProperties } from '@nextcloud/files'\nimport { client } from './WebdavClient'\nimport { genFileInfo, type FileInfo } from '../utils/fileUtils'\nimport type { FileStat, ResponseDataDetailed } from 'webdav'\n\n/**\n * Retrieve the files list\n * @param path\n * @param options\n */\nexport default async function(path: string, options = {}): Promise<FileInfo[]> {\n\tconst response = await client.getDirectoryContents(path, Object.assign({\n\t\tdata: `<?xml version=\"1.0\"?>\n\t\t\t<d:propfind ${getDavNameSpaces()}>\n\t\t\t\t<d:prop>\n\t\t\t\t\t<oc:tags />\n\t\t\t\t\t${getDavProperties()}\n\t\t\t\t</d:prop>\n\t\t\t</d:propfind>`,\n\t\tdetails: true,\n\t}, options)) as ResponseDataDetailed<FileStat[]>\n\n\treturn response.data.map(genFileInfo)\n}\n","/**\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport axios from '@nextcloud/axios'\nimport { generateUrl } from '@nextcloud/router'\nimport { isPublicShare } from '@nextcloud/sharing/public'\n\n/**\n * @return {object}\n */\nexport default async function getSortingConfig() {\n\tconst viewConfigs = await getViewConfigs()\n\n\tif (!viewConfigs) {\n\t\treturn { key: 'basename', asc: true }\n\t}\n\n\tconst keyMap = { mtime: 'lastmod' }\n\tconst key = keyMap[viewConfigs.sorting_mode] || viewConfigs.sorting_mode || 'basename'\n\tconst asc = viewConfigs.sorting_direction === 'asc' || !viewConfigs.sorting_direction\n\n\treturn { key, asc }\n}\n\n/**\n * @return {object}\n */\nasync function getViewConfigs() {\n\tif (isPublicShare()) {\n\t\treturn null\n\t}\n\tconst url = generateUrl('apps/files/api/v1/views')\n\treturn await axios.get(url)\n\t\t.then((response) => {\n\t\t\treturn response.data.data?.files\n\t\t})\n\t\t.catch(() => {\n\t\t\treturn null\n\t\t})\n}\n","/**\n * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\nimport { getPreviewIfAny } from '../utils/previewUtils.ts'\nimport { getDavPath } from '../utils/fileUtils.ts'\n\nexport default {\n\tcomputed: {\n\t\t/**\n\t\t * Link to the preview path if the file have a preview\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tpreviewPath() {\n\t\t\treturn this.getPreviewIfAny({\n\t\t\t\tfileid: this.fileid,\n\t\t\t\tfilename: this.filename,\n\t\t\t\tpreviewUrl: this.previewUrl,\n\t\t\t\thasPreview: this.hasPreview,\n\t\t\t\tdavPath: this.davPath,\n\t\t\t\tetag: this.$attrs.etag,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Absolute dav remote path of the file\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tdavPath() {\n\t\t\treturn getDavPath({\n\t\t\t\tfilename: this.filename,\n\t\t\t\tbasename: this.basename,\n\t\t\t})\n\t\t},\n\t},\n\tmethods: {\n\t\t/**\n\t\t * Return the preview url if the file have an existing\n\t\t * preview or the absolute dav remote path if none.\n\t\t *\n\t\t * @param {object} data destructuring object\n\t\t * @param {string} data.fileid the file id\n\t\t * @param {string} [data.previewUrl] URL of the file preview\n\t\t * @param {boolean} data.hasPreview have the file an existing preview ?\n\t\t * @param {string} data.davPath the absolute dav path\n\t\t * @param {string} data.filename the file name\n\t\t * @param {string|null} data.etag the etag of the file\n\t\t * @return {string} the absolute url\n\t\t */\n\t\tgetPreviewIfAny(data) {\n\t\t\treturn getPreviewIfAny(data)\n\t\t},\n\t},\n}\n","'use strict';\n\nvar isWindows = process.platform === 'win32';\n\n// Regex to split a windows path into into [dir, root, basename, name, ext]\nvar splitWindowsRe =\n /^(((?:[a-zA-Z]:|[\\\\\\/]{2}[^\\\\\\/]+[\\\\\\/]+[^\\\\\\/]+)?[\\\\\\/]?)(?:[^\\\\\\/]*[\\\\\\/])*)((\\.{1,2}|[^\\\\\\/]+?|)(\\.[^.\\/\\\\]*|))[\\\\\\/]*$/;\n\nvar win32 = {};\n\nfunction win32SplitPath(filename) {\n return splitWindowsRe.exec(filename).slice(1);\n}\n\nwin32.parse = function(pathString) {\n if (typeof pathString !== 'string') {\n throw new TypeError(\n \"Parameter 'pathString' must be a string, not \" + typeof pathString\n );\n }\n var allParts = win32SplitPath(pathString);\n if (!allParts || allParts.length !== 5) {\n throw new TypeError(\"Invalid path '\" + pathString + \"'\");\n }\n return {\n root: allParts[1],\n dir: allParts[0] === allParts[1] ? allParts[0] : allParts[0].slice(0, -1),\n base: allParts[2],\n ext: allParts[4],\n name: allParts[3]\n };\n};\n\n\n\n// Split a filename into [dir, root, basename, name, ext], unix version\n// 'root' is just a slash, or nothing.\nvar splitPathRe =\n /^((\\/?)(?:[^\\/]*\\/)*)((\\.{1,2}|[^\\/]+?|)(\\.[^.\\/]*|))[\\/]*$/;\nvar posix = {};\n\n\nfunction posixSplitPath(filename) {\n return splitPathRe.exec(filename).slice(1);\n}\n\n\nposix.parse = function(pathString) {\n if (typeof pathString !== 'string') {\n throw new TypeError(\n \"Parameter 'pathString' must be a string, not \" + typeof pathString\n );\n }\n var allParts = posixSplitPath(pathString);\n if (!allParts || allParts.length !== 5) {\n throw new TypeError(\"Invalid path '\" + pathString + \"'\");\n }\n \n return {\n root: allParts[1],\n dir: allParts[0].slice(0, -1),\n base: allParts[2],\n ext: allParts[4],\n name: allParts[3],\n };\n};\n\n\nif (isWindows)\n module.exports = win32.parse;\nelse /* posix */\n module.exports = posix.parse;\n\nmodule.exports.posix = posix.parse;\nmodule.exports.win32 = win32.parse;\n","/**\n * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\nimport debounce from 'debounce'\nimport PreviewUrl from '../mixins/PreviewUrl.js'\nimport parsePath from 'path-parse'\n\nexport default {\n\tinheritAttrs: false,\n\tmixins: [PreviewUrl],\n\tprops: {\n\t\t// Is the current component shown\n\t\tactive: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t\t// file name\n\t\tbasename: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\t\t// file path relative to user folder\n\t\tfilename: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\t\t// file source to fetch contents from\n\t\tsource: {\n\t\t\ttype: String,\n\t\t\tdefault: undefined,\n\t\t},\n\t\t// URL the file preview\n\t\tpreviewUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: undefined,\n\t\t},\n\t\t// should the standard core preview be used?\n\t\thasPreview: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t\t// unique file id\n\t\tfileid: {\n\t\t\ttype: [Number, String],\n\t\t\trequired: false,\n\t\t},\n\t\t// list of all the visible files\n\t\tfileList: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\t\t// file mime (aliased if specified in the model)\n\t\tmime: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\t\t// can the user swipe\n\t\tcanSwipe: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\t\tcanZoom: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t\t// is the content loaded?\n\t\t// synced with parent\n\t\tloaded: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t\t// is the sidebar currently opened ?\n\t\tisSidebarShown: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t\t// are we in fullscreen mode ?\n\t\tisFullScreen: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t\t// The file id of the peer live photo file\n\t\tmetadataFilesLivePhoto: {\n\t\t\ttype: Number,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\theight: null,\n\t\t\twidth: null,\n\t\t\tnaturalHeight: null,\n\t\t\tnaturalWidth: null,\n\t\t\tisLoaded: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tname() {\n\t\t\treturn parsePath(this.basename).name\n\t\t},\n\t\text() {\n\t\t\treturn parsePath(this.basename).ext\n\t\t},\n\t\tsrc() {\n\t\t\treturn this.source ?? this.davPath\n\t\t},\n\t},\n\n\twatch: {\n\t\tactive(val, old) {\n\t\t\t// the item was hidden before and is now the current view\n\t\t\tif (val === true && old === false) {\n\t\t\t\t// just in case the file was preloaded, let's warn the viewer\n\t\t\t\tif (this.isLoaded) {\n\t\t\t\t\tthis.doneLoading()\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// update image size on sidebar toggle\n\t\tisSidebarShown() {\n\t\t\t// wait for transition to complete (100ms)\n\t\t\tsetTimeout(this.updateHeightWidth, 200)\n\t\t},\n\t},\n\n\tmounted() {\n\t\t// detect error and let the viewer know\n\t\tthis.$el.addEventListener('error', e => {\n\t\t\tconsole.error('Error loading', this.filename, e)\n\t\t\tthis.$emit('error', e)\n\t\t})\n\n\t\t// update image size on window resize\n\t\twindow.addEventListener('resize', debounce(() => {\n\t\t\tthis.updateHeightWidth()\n\t\t}, 100))\n\t},\n\n\tmethods: {\n\n\t\t/**\n\t\t * This is used to make the viewer know this file is complete or ready\n\t\t * ! you NEED to use it to make the viewer aware of the current loading state\n\t\t */\n\t\tdoneLoading() {\n\t\t\t// send the current state\n\t\t\tthis.$emit('update:loaded', true)\n\t\t\t// save the current state\n\t\t\tthis.isLoaded = true\n\t\t},\n\n\t\t/**\n\t\t * Updates the current height and width data\n\t\t * based on the viewer maximum size\n\t\t */\n\t\tupdateHeightWidth() {\n\t\t\tconst modalWrapper = this.$parent.$el.querySelector('.modal-wrapper')\n\t\t\tif (modalWrapper && this.naturalHeight > 0 && this.naturalWidth > 0) {\n\t\t\t\tconst modalContainer = modalWrapper.querySelector('.modal-container')\n\n\t\t\t\tconst parentHeight = modalContainer.clientHeight\n\t\t\t\tconst parentWidth = modalContainer.clientWidth\n\n\t\t\t\tconst heightRatio = parentHeight / this.naturalHeight\n\t\t\t\tconst widthRatio = parentWidth / this.naturalWidth\n\n\t\t\t\t// if the video height is capped by the parent height\n\t\t\t\t// AND the video is bigger than the parent\n\t\t\t\tif (heightRatio < widthRatio && heightRatio < 1) {\n\t\t\t\t\tthis.height = parentHeight\n\t\t\t\t\tthis.width = Math.round(this.naturalWidth / this.naturalHeight * parentHeight)\n\n\t\t\t\t// if the video width is capped by the parent width\n\t\t\t\t// AND the video is bigger than the parent\n\t\t\t\t} else if (heightRatio > widthRatio && widthRatio < 1) {\n\t\t\t\t\tthis.width = parentWidth\n\t\t\t\t\tthis.height = Math.round(this.naturalHeight / this.naturalWidth * parentWidth)\n\n\t\t\t\t// RESET\n\t\t\t\t} else {\n\t\t\t\t\tthis.height = this.naturalHeight\n\t\t\t\t\tthis.width = this.naturalWidth\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.height = this.naturalHeight\n\t\t\t\tthis.width = this.naturalWidth\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Enable the viewer swiping previous/next capability\n\t\t */\n\t\tenableSwipe() {\n\t\t\tthis.$emit('update:canSwipe', true)\n\t\t},\n\n\t\t/**\n\t\t * Disable the viewer swiping previous/next capability\n\t\t */\n\t\tdisableSwipe() {\n\t\t\tthis.$emit('update:canSwipe', false)\n\t\t},\n\n\t\t/**\n\t\t * Toggle the fullscreen on the current visible element\n\t\t */\n\t\ttoggleFullScreen() {\n\t\t\tif (this.isFullScreen) {\n\t\t\t\tdocument.exitFullscreen()\n\t\t\t} else {\n\t\t\t\tthis.$el.requestFullscreen()\n\t\t\t}\n\t\t},\n\t},\n}\n","<template>\n <span v-bind=\"$attrs\"\n :aria-hidden=\"title ? null : 'true'\"\n :aria-label=\"title\"\n class=\"material-design-icon trash-can-outline-icon\"\n role=\"img\"\n @click=\"$emit('click', $event)\">\n <svg :fill=\"fillColor\"\n class=\"material-design-icon__svg\"\n :width=\"size\"\n :height=\"size\"\n viewBox=\"0 0 24 24\">\n <path d=\"M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z\">\n <title v-if=\"title\">{{ title }}</title>\n </path>\n </svg>\n </span>\n</template>\n\n<script>\nexport default {\n name: \"TrashCanOutlineIcon\",\n emits: ['click'],\n props: {\n title: {\n type: String,\n },\n fillColor: {\n type: String,\n default: \"currentColor\"\n },\n size: {\n type: Number,\n default: 24\n }\n }\n}\n</script>","<template>\n <span v-bind=\"$attrs\"\n :aria-hidden=\"title ? null : 'true'\"\n :aria-label=\"title\"\n class=\"material-design-icon tray-arrow-down-icon\"\n role=\"img\"\n @click=\"$emit('click', $event)\">\n <svg :fill=\"fillColor\"\n class=\"material-design-icon__svg\"\n :width=\"size\"\n :height=\"size\"\n viewBox=\"0 0 24 24\">\n <path d=\"M2 12H4V17H20V12H22V17C22 18.11 21.11 19 20 19H4C2.9 19 2 18.11 2 17V12M12 15L17.55 9.54L16.13 8.13L13 11.25V2H11V11.25L7.88 8.13L6.46 9.55L12 15Z\">\n <title v-if=\"title\">{{ title }}</title>\n </path>\n </svg>\n </span>\n</template>\n\n<script>\nexport default {\n name: \"TrayArrowDownIcon\",\n emits: ['click'],\n props: {\n title: {\n type: String,\n },\n fillColor: {\n type: String,\n default: \"currentColor\"\n },\n size: {\n type: Number,\n default: 24\n }\n }\n}\n</script>","<template>\n <span v-bind=\"$attrs\"\n :aria-hidden=\"title ? null : 'true'\"\n :aria-label=\"title\"\n class=\"material-design-icon fullscreen-icon\"\n role=\"img\"\n @click=\"$emit('click', $event)\">\n <svg :fill=\"fillColor\"\n class=\"material-design-icon__svg\"\n :width=\"size\"\n :height=\"size\"\n viewBox=\"0 0 24 24\">\n <path d=\"M5,5H10V7H7V10H5V5M14,5H19V10H17V7H14V5M17,14H19V19H14V17H17V14M10,17V19H5V14H7V17H10Z\">\n <title v-if=\"title\">{{ title }}</title>\n </path>\n </svg>\n </span>\n</template>\n\n<script>\nexport default {\n name: \"FullscreenIcon\",\n emits: ['click'],\n props: {\n title: {\n type: String,\n },\n fillColor: {\n type: String,\n default: \"currentColor\"\n },\n size: {\n type: Number,\n default: 24\n }\n }\n}\n</script>","<template>\n <span v-bind=\"$attrs\"\n :aria-hidden=\"title ? null : 'true'\"\n :aria-label=\"title\"\n class=\"material-design-icon fullscreen-exit-icon\"\n role=\"img\"\n @click=\"$emit('click', $event)\">\n <svg :fill=\"fillColor\"\n class=\"material-design-icon__svg\"\n :width=\"size\"\n :height=\"size\"\n viewBox=\"0 0 24 24\">\n <path d=\"M14,14H19V16H16V19H14V14M5,14H10V19H8V16H5V14M8,5H10V10H5V8H8V5M19,8V10H14V5H16V8H19Z\">\n <title v-if=\"title\">{{ title }}</title>\n </path>\n </svg>\n </span>\n</template>\n\n<script>\nexport default {\n name: \"FullscreenExitIcon\",\n emits: ['click'],\n props: {\n title: {\n type: String,\n },\n fillColor: {\n type: String,\n default: \"currentColor\"\n },\n size: {\n type: Number,\n default: 24\n }\n }\n}\n</script>","<template>\n <span v-bind=\"$attrs\"\n :aria-hidden=\"title ? null : 'true'\"\n :aria-label=\"title\"\n class=\"material-design-icon pencil-outline-icon\"\n role=\"img\"\n @click=\"$emit('click', $event)\">\n <svg :fill=\"fillColor\"\n class=\"material-design-icon__svg\"\n :width=\"size\"\n :height=\"size\"\n viewBox=\"0 0 24 24\">\n <path d=\"M14.06,9L15,9.94L5.92,19H5V18.08L14.06,9M17.66,3C17.41,3 17.15,3.1 16.96,3.29L15.13,5.12L18.88,8.87L20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18.17,3.09 17.92,3 17.66,3M14.06,6.19L3,17.25V21H6.75L17.81,9.94L14.06,6.19Z\">\n <title v-if=\"title\">{{ title }}</title>\n </path>\n </svg>\n </span>\n</template>\n\n<script>\nexport default {\n name: \"PencilOutlineIcon\",\n emits: ['click'],\n props: {\n title: {\n type: String,\n },\n fillColor: {\n type: String,\n default: \"currentColor\"\n },\n size: {\n type: Number,\n default: 24\n }\n }\n}\n</script>","<!--\n - SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors\n - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n\n<template>\n\t<!-- Single-file rendering -->\n\t<div v-if=\"el\"\n\t\tid=\"viewer\"\n\t\t:data-handler=\"handlerId\">\n\t\t<component :is=\"currentFile.modal\"\n\t\t\tv-if=\"!currentFile.failed\"\n\t\t\t:key=\"uniqueKey(currentFile)\"\n\t\t\tref=\"content\"\n\t\t\t:active=\"true\"\n\t\t\t:can-swipe=\"false\"\n\t\t\t:can-zoom=\"false\"\n\t\t\tv-bind=\"currentFile\"\n\t\t\t:file-list=\"[currentFile]\"\n\t\t\t:is-full-screen=\"false\"\n\t\t\t:loaded.sync=\"currentFile.loaded\"\n\t\t\t:is-sidebar-shown=\"false\"\n\t\t\tclass=\"viewer__file viewer__file--active\"\n\t\t\t@error=\"currentFailed\" />\n\t\t<Error v-else\n\t\t\t:name=\"currentFile.basename\" />\n\t</div>\n\n\t<!-- Modal view rendering -->\n\t<NcModal v-else-if=\"initiated || currentFile.modal\"\n\t\tid=\"viewer\"\n\t\t:additional-trap-elements=\"trapElements\"\n\t\t:class=\"modalClass\"\n\t\t:clear-view-delay=\"-1 /* disable fade-out because of accessibility reasons */\"\n\t\t:close-button-contained=\"false\"\n\t\t:dark=\"true\"\n\t\t:light-backdrop=\"lightBackdrop\"\n\t\t:data-handler=\"handlerId\"\n\t\t:enable-slideshow=\"hasPrevious || hasNext\"\n\t\t:slideshow-paused=\"editing\"\n\t\t:enable-swipe=\"canSwipe && !editing\"\n\t\t:has-next=\"hasNext\"\n\t\t:has-previous=\"hasPrevious\"\n\t\t:inline-actions=\"canEdit ? 1 : 0\"\n\t\t:spread-navigation=\"true\"\n\t\t:style=\"{ width: isSidebarShown ? `${sidebarPosition}px` : null }\"\n\t\t:name=\"currentFile.basename\"\n\t\tclass=\"viewer\"\n\t\tsize=\"full\"\n\t\t@close=\"close\"\n\t\t@previous=\"previous\"\n\t\t@next=\"next\">\n\t\t<!-- ACTIONS -->\n\t\t<template #actions>\n\t\t\t<!-- Inline items -->\n\t\t\t<NcActionButton v-if=\"canEdit\"\n\t\t\t\t:close-after-click=\"true\"\n\t\t\t\t@click=\"onEdit\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Pencil :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('viewer', 'Edit') }}\n\t\t\t</NcActionButton>\n\t\t\t<!-- Menu items -->\n\t\t\t<NcActionButton :close-after-click=\"true\"\n\t\t\t\t@click=\"toggleFullScreen\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Fullscreen v-if=\"!isFullscreenMode\" :size=\"20\" />\n\t\t\t\t\t<FullscreenExit v-else :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ isFullscreenMode ? t('viewer', 'Exit full screen') : t('viewer', 'Full screen') }}\n\t\t\t</NcActionButton>\n\t\t\t<NcActionButton v-if=\"enableSidebar && Sidebar && sidebarOpenFilePath && !isSidebarShown\"\n\t\t\t\t:close-after-click=\"true\"\n\t\t\t\ticon=\"icon-menu-sidebar\"\n\t\t\t\t@click=\"showSidebar\">\n\t\t\t\t{{ t('viewer', 'Open sidebar') }}\n\t\t\t</NcActionButton>\n\t\t\t<NcActionButton v-if=\"canDownload\"\n\t\t\t\t:close-after-click=\"true\"\n\t\t\t\t@click=\"onDownload\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Download :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('viewer', 'Download') }}\n\t\t\t</NcActionButton>\n\t\t\t<NcActionButton v-if=\"canDelete\"\n\t\t\t\t:close-after-click=\"true\"\n\t\t\t\t@click=\"onDelete\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Delete :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('viewer', 'Delete') }}\n\t\t\t</NcActionButton>\n\t\t</template>\n\n\t\t<div class=\"viewer__content\"\n\t\t\t:class=\"contentClass\"\n\t\t\t@click.self.exact=\"close\"\n\t\t\t@contextmenu=\"preventContextMenu\">\n\t\t\t<!-- COMPARE FILE -->\n\t\t\t<div v-if=\"comparisonFile && !comparisonFile.failed && showComparison\" class=\"viewer__file-wrapper\">\n\t\t\t\t<component :is=\"comparisonFile.modal\"\n\t\t\t\t\t:key=\"uniqueKey(comparisonFile)\"\n\t\t\t\t\tref=\"comparison-content\"\n\t\t\t\t\tv-bind=\"comparisonFile\"\n\t\t\t\t\t:active=\"true\"\n\t\t\t\t\t:can-swipe=\"false\"\n\t\t\t\t\t:can-zoom=\"false\"\n\t\t\t\t\t:editing=\"false\"\n\t\t\t\t\t:is-full-screen=\"isFullscreen\"\n\t\t\t\t\t:is-sidebar-shown=\"isSidebarShown\"\n\t\t\t\t\t:loaded.sync=\"comparisonFile.loaded\"\n\t\t\t\t\tclass=\"viewer__file viewer__file--active\"\n\t\t\t\t\t@error=\"comparisonFailed\" />\n\t\t\t</div>\n\n\t\t\t<!-- PREVIOUS -->\n\t\t\t<div v-if=\"hasPreviousFile\"\n\t\t\t\t:key=\"uniqueKey(previousFile)\"\n\t\t\t\tclass=\"viewer__file-wrapper viewer__file-wrapper--hidden\"\n\t\t\t\taria-hidden=\"true\"\n\t\t\t\tinert>\n\t\t\t\t<component :is=\"previousFile.modal\"\n\t\t\t\t\tv-if=\"!previousFile.failed\"\n\t\t\t\t\tref=\"previous-content\"\n\t\t\t\t\tv-bind=\"previousFile\"\n\t\t\t\t\t:file-list=\"fileList\"\n\t\t\t\t\tclass=\"viewer__file\"\n\t\t\t\t\t@error=\"previousFailed\" />\n\t\t\t\t<Error v-else\n\t\t\t\t\t:name=\"previousFile.basename\" />\n\t\t\t</div>\n\n\t\t\t<!-- CURRENT -->\n\t\t\t<div :key=\"uniqueKey(currentFile)\" class=\"viewer__file-wrapper\">\n\t\t\t\t<component :is=\"currentFile.modal\"\n\t\t\t\t\tv-if=\"!currentFile.failed\"\n\t\t\t\t\tref=\"content\"\n\t\t\t\t\tv-bind=\"currentFile\"\n\t\t\t\t\t:active=\"true\"\n\t\t\t\t\t:can-swipe.sync=\"canSwipe\"\n\t\t\t\t\t:can-zoom=\"true\"\n\t\t\t\t\t:editing.sync=\"editing\"\n\t\t\t\t\t:file-list=\"fileList\"\n\t\t\t\t\t:is-full-screen=\"isFullscreen\"\n\t\t\t\t\t:is-sidebar-shown=\"isSidebarShown\"\n\t\t\t\t\t:loaded.sync=\"currentFile.loaded\"\n\t\t\t\t\tclass=\"viewer__file viewer__file--active\"\n\t\t\t\t\t@update:editing=\"toggleEditor\"\n\t\t\t\t\t@error=\"currentFailed\" />\n\t\t\t\t<Error v-else\n\t\t\t\t\t:name=\"currentFile.basename\" />\n\t\t\t</div>\n\n\t\t\t<!-- NEXT -->\n\t\t\t<div v-if=\"hasNextFile\"\n\t\t\t\t:key=\"uniqueKey(nextFile)\"\n\t\t\t\tclass=\"viewer__file-wrapper viewer__file-wrapper--hidden\"\n\t\t\t\taria-hidden=\"true\"\n\t\t\t\tinert>\n\t\t\t\t<component :is=\"nextFile.modal\"\n\t\t\t\t\tv-if=\"!nextFile.failed\"\n\t\t\t\t\tref=\"next-content\"\n\t\t\t\t\tv-bind=\"nextFile\"\n\t\t\t\t\t:file-list=\"fileList\"\n\t\t\t\t\tclass=\"viewer__file\"\n\t\t\t\t\t@error=\"nextFailed\" />\n\t\t\t\t<Error v-else\n\t\t\t\t\t:name=\"nextFile.basename\" />\n\t\t\t</div>\n\t\t</div>\n\t</NcModal>\n</template>\n\n<script>\nimport '@nextcloud/dialogs/style.css'\nimport Vue, { defineComponent } from 'vue'\n\nimport { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'\nimport { loadState } from '@nextcloud/initial-state'\nimport { File as NcFile, Node, davRemoteURL, davRootPath, davGetRootPath, sortNodes } from '@nextcloud/files'\nimport { showError } from '@nextcloud/dialogs'\nimport axios from '@nextcloud/axios'\n\nimport isFullscreen from '@nextcloud/vue/dist/Mixins/isFullscreen.js'\nimport isMobile from '@nextcloud/vue/dist/Mixins/isMobile.js'\n\nimport { canDownload } from '../utils/canDownload.ts'\nimport { extractFilePaths, extractFilePathFromSource } from '../utils/fileUtils.ts'\nimport { toggleEditor } from '../files_actions/viewerAction.ts'\nimport cancelableRequest from '../utils/CancelableRequest.js'\nimport Error from '../components/Error.vue'\nimport fetchNode from '../services/FetchFile.ts'\nimport File from '../models/file.js'\nimport getFileInfo from '../services/FileInfo.ts'\nimport getFileList from '../services/FileList.ts'\nimport getSortingConfig from '../services/FileSortingConfig.ts'\nimport logger from '../services/logger.js'\nimport Mime from '../mixins/Mime.js'\n\nimport Delete from 'vue-material-design-icons/TrashCanOutline.vue'\nimport Download from 'vue-material-design-icons/TrayArrowDown.vue'\nimport Fullscreen from 'vue-material-design-icons/Fullscreen.vue'\nimport FullscreenExit from 'vue-material-design-icons/FullscreenExit.vue'\nimport Pencil from 'vue-material-design-icons/PencilOutline.vue'\n\n// Dynamic loading\nconst NcModal = () => import('@nextcloud/vue/dist/Components/NcModal.js')\nconst NcActionLink = () => import('@nextcloud/vue/dist/Components/NcActionLink.js')\nconst NcActionButton = () => import('@nextcloud/vue/dist/Components/NcActionButton.js')\n\nexport default defineComponent({\n\tname: 'Viewer',\n\n\tcomponents: {\n\t\tDelete,\n\t\tDownload,\n\t\tError,\n\t\tFullscreen,\n\t\tFullscreenExit,\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcModal,\n\t\tPencil,\n\t},\n\n\tmixins: [isFullscreen, isMobile],\n\n\tdata() {\n\t\treturn {\n\t\t\t// Reactivity bindings\n\t\t\tViewer: OCA.Viewer,\n\t\t\tSidebar: null,\n\t\t\thandlers: OCA.Viewer.availableHandlers,\n\n\t\t\t// Viewer variables\n\t\t\tcomponents: {},\n\t\t\tmimeGroups: {},\n\t\t\tregisteredHandlers: {},\n\n\t\t\t// Files variables\n\t\t\tcurrentIndex: 0,\n\t\t\tpreviousFile: {},\n\t\t\tcurrentFile: {},\n\t\t\tcomparisonFile: null,\n\t\t\tnextFile: {},\n\t\t\tfileList: [],\n\t\t\tsortingConfig: null,\n\n\t\t\t// States\n\t\t\tisLoaded: false,\n\t\t\tinitiated: false,\n\t\t\tediting: false,\n\n\t\t\t// cancellable requests\n\t\t\tcancelRequestFile: () => {},\n\t\t\tcancelRequestFolder: () => {},\n\n\t\t\t// Flags\n\t\t\tsidebarPosition: 0,\n\t\t\tisSidebarShown: false,\n\t\t\tisFullscreenMode: false,\n\t\t\tcanSwipe: true,\n\t\t\tisStandalone: false,\n\t\t\ttheme: null,\n\t\t\tlightBackdrop: null,\n\t\t\troot: davRemoteURL,\n\t\t\thandlerId: '',\n\n\t\t\ttrapElements: [],\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tdownloadPath() {\n\t\t\treturn this.currentFile.source ?? this.currentFile.davPath\n\t\t},\n\t\thasPrevious() {\n\t\t\treturn this.fileList.length > 1\n\t\t\t\t&& (this.canLoop || !this.isStartOfList)\n\t\t},\n\t\thasNext() {\n\t\t\treturn this.fileList.length > 1\n\t\t\t\t&& (this.canLoop || !this.isEndOfList)\n\t\t},\n\t\tfile() {\n\t\t\treturn this.Viewer.file\n\t\t},\n\t\tfileInfo() {\n\t\t\treturn this.Viewer.fileInfo\n\t\t},\n\t\tcomparisonFileInfo() {\n\t\t\treturn this.Viewer.compareFileInfo\n\t\t},\n\t\tfiles() {\n\t\t\treturn this.Viewer.files\n\t\t},\n\t\tenableSidebar() {\n\t\t\treturn this.Viewer.enableSidebar\n\t\t},\n\t\tel() {\n\t\t\treturn this.Viewer.el\n\t\t},\n\t\tloadMore() {\n\t\t\treturn this.Viewer.loadMore\n\t\t},\n\t\tcanLoop() {\n\t\t\treturn this.Viewer.canLoop\n\t\t},\n\t\tisStartOfList() {\n\t\t\treturn this.currentIndex === 0\n\t\t},\n\t\tisEndOfList() {\n\t\t\treturn this.currentIndex === this.fileList.length - 1\n\t\t},\n\n\t\thasPreviousFile() {\n\t\t\t// Check if empty object\n\t\t\treturn Object.keys(this.previousFile).length > 0\n\t\t},\n\t\thasNextFile() {\n\t\t\t// Check if empty object\n\t\t\treturn Object.keys(this.nextFile).length > 0\n\t\t},\n\n\t\tisImage() {\n\t\t\treturn ['image/jpeg', 'image/png', 'image/webp'].includes(this.currentFile?.mime)\n\t\t},\n\n\t\t/**\n\t\t * Returns the path to the current opened file in the sidebar.\n\t\t *\n\t\t * If the sidebar is available but closed an empty string is returned.\n\t\t * If the sidebar is not available null is returned.\n\t\t *\n\t\t * @return {string|null} the path to the current opened file in the\n\t\t * sidebar, if any.\n\t\t */\n\t\tsidebarFile() {\n\t\t\treturn this.Sidebar && this.Sidebar.file\n\t\t},\n\t\tsidebarOpenFilePath() {\n\t\t\ttry {\n\t\t\t\tconst relativePath = this.currentFile?.davPath?.split(davRootPath)[1]\n\t\t\t\treturn relativePath?.split('/')?.map(decodeURIComponent)?.join('/')\n\t\t\t} catch (e) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Is the current user allowed to delete the file?\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tcanDelete() {\n\t\t\treturn this.currentFile?.permissions?.includes('D')\n\t\t},\n\n\t\t/**\n\t\t * Is the current user allowed to download the file\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tcanDownload() {\n\t\t\t// download not possible for comparison\n\t\t\tif (this.comparisonFile) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn this.currentFile && canDownload(this.currentFile)\n\t\t},\n\n\t\t/**\n\t\t * Is the current user allowed to edit the file ?\n\t\t * https://github.com/nextcloud/server/blob/7718c9776c5903474b8f3cf958cdd18a53b2449e/apps/dav/lib/Connector/Sabre/Node.php#L357-L387\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tcanEdit() {\n\t\t\treturn !this.isMobile\n\t\t\t\t&& this.canDownload\n\t\t\t\t&& this.currentFile?.permissions?.includes('W')\n\t\t\t\t&& this.isImage\n\t\t\t\t&& !this.comparisonFile\n\t\t\t\t&& (loadState('core', 'config', [])['enable_non-accessible_features'] ?? true)\n\t\t},\n\n\t\tmodalClass() {\n\t\t\treturn {\n\t\t\t\t'icon-loading': !this.currentFile.loaded && !this.currentFile.failed,\n\t\t\t\t'theme--undefined': this.theme === null,\n\t\t\t\t'theme--dark': this.theme === 'dark',\n\t\t\t\t'theme--light': this.theme === 'light',\n\t\t\t\t'theme--default': this.theme === 'default',\n\t\t\t\t'image--fullscreen': this.isImage && this.isFullscreenMode,\n\t\t\t}\n\t\t},\n\n\t\tshowComparison() {\n\t\t\treturn !this.isMobile\n\t\t},\n\n\t\tcontentClass() {\n\t\t\treturn {\n\t\t\t\t'viewer--split': this.comparisonFile,\n\t\t\t}\n\t\t},\n\n\t\tisSameFile() {\n\t\t\treturn (fileInfo = null, path = null) => {\n\t\t\t\tif (\n\t\t\t\t\tpath && path === this.currentFile.path\n\t\t\t\t\t&& !this.currentFile.source\n\t\t\t\t) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\n\t\t\t\tif (path === this.currentFile.filename) {\n\t\t\t\t\t// if the path is the same as the current file, we can assume it's the same file\n\t\t\t\t\treturn true\n\t\t\t\t}\n\n\t\t\t\tif (\n\t\t\t\t\tfileInfo && fileInfo.fileid === this.currentFile.fileid\n\t\t\t\t\t&& fileInfo.mtime && fileInfo.mtime === this.currentFile.mtime\n\t\t\t\t\t&& fileInfo.source && fileInfo.source === this.currentFile.source\n\t\t\t\t) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\t},\n\n\twatch: {\n\t\tel(element) {\n\t\t\tlogger.info(element)\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tconst viewerRoot = document.getElementById('viewer')\n\t\t\t\tif (element) {\n\t\t\t\t\tconst el = document.querySelector(element)\n\t\t\t\t\tif (el) {\n\t\t\t\t\t\tel.appendChild(viewerRoot)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogger.warn('Could not find element ', { element })\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tdocument.body.appendChild(viewerRoot)\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\tfile(path) {\n\t\t\t// we got a valid path! Load file...\n\t\t\tif (path && path.trim() !== '') {\n\t\t\t\tlogger.info('Opening viewer for file ', { path })\n\t\t\t\tthis.openFile(path, OCA.Viewer.overrideHandlerId)\n\t\t\t} else {\n\t\t\t\t// path is empty, we're closing!\n\t\t\t\tthis.cleanup()\n\t\t\t}\n\t\t},\n\n\t\tfileInfo(fileInfo) {\n\t\t\tif (fileInfo) {\n\t\t\t\tlogger.info('Opening viewer for fileInfo ', { fileInfo })\n\t\t\t\tthis.openFileInfo(fileInfo, OCA.Viewer.overrideHandlerId)\n\t\t\t} else {\n\t\t\t\t// object is undefined, we're closing!\n\t\t\t\tthis.cleanup()\n\t\t\t}\n\t\t},\n\n\t\tcomparisonFileInfo(fileInfo) {\n\t\t\tif (fileInfo) {\n\t\t\t\tlogger.info('Opening viewer for comparisonFileInfo ', { fileInfo })\n\t\t\t\tthis.compareFile(fileInfo)\n\t\t\t} else {\n\t\t\t\t// object is undefined, we're closing!\n\t\t\t\tthis.cleanup()\n\t\t\t}\n\t\t},\n\n\t\tfiles(fileList) {\n\t\t\tif (!fileList || !Array.isArray(fileList) || fileList.length === 0) {\n\t\t\t\tlogger.warn('No files provided, skipping update')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// the files list changed, let's update the current opened index\n\t\t\tconst currentIndex = fileList.findIndex(file => file.filename === this.currentFile.filename)\n\t\t\tif (currentIndex > -1) {\n\t\t\t\tthis.currentIndex = currentIndex\n\t\t\t\tlogger.debug('The files list changed, new current file index is ' + currentIndex)\n\t\t\t}\n\n\t\t\t// finally replace the fileList\n\t\t\tthis.fileList = fileList\n\t\t},\n\n\t\t// user reached the end of list\n\t\tasync isEndOfList(isEndOfList) {\n\t\t\tif (!isEndOfList || this.el) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// if we have a loadMore handler, let's fetch more files\n\t\t\tif (this.loadMore && typeof this.loadMore === 'function') {\n\t\t\t\tlogger.debug('Fetching additional files...')\n\t\t\t\tconst list = await this.loadMore()\n\n\t\t\t\tif (Array.isArray(list) && list.length > 0) {\n\t\t\t\t\tthis.fileList.push(...list)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t},\n\n\tbeforeMount() {\n\t\tthis.isStandalone = window.OCP?.Files === undefined\n\t\tif (this.isStandalone) {\n\t\t\tlogger.info('No OCP.Files app found, viewer is now in standalone mode')\n\t\t}\n\n\t\t// register on load\n\t\tdocument.addEventListener('DOMContentLoaded', () => {\n\t\t\t// load all init handlers\n\t\t\tif (window._oca_viewer_handlers) {\n\t\t\t\twindow._oca_viewer_handlers.forEach((handler) => {\n\t\t\t\t\tOCA.Viewer.registerHandler(handler)\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// register all primary components mimes\n\t\t\tthis.handlers.forEach(handler => {\n\t\t\t\tthis.registerHandler(handler)\n\t\t\t})\n\n\t\t\t// then register aliases. We need to have the components\n\t\t\t// first so we can bind the alias to them.\n\t\t\tthis.handlers.forEach(handler => {\n\t\t\t\tthis.registerHandlerAlias(handler)\n\t\t\t})\n\t\t\tthis.isLoaded = true\n\n\t\t\t// bind Sidebar if available\n\t\t\tif (OCA?.Files?.Sidebar) {\n\t\t\t\tthis.Sidebar = OCA.Files.Sidebar.state\n\t\t\t}\n\n\t\t\tlogger.info(`${this.handlers.length} viewer handlers registered`, { handlers: this.handlers })\n\t\t})\n\n\t\twindow.addEventListener('resize', this.onResize)\n\t},\n\n\tmounted() {\n\t\t// React to Files' Sidebar events.\n\t\tsubscribe('files:sidebar:opened', this.handleAppSidebarOpen)\n\t\tsubscribe('files:sidebar:closed', this.handleAppSidebarClose)\n\t\tsubscribe('files:node:updated', this.handleFileUpdated)\n\t\tsubscribe('viewer:trapElements:changed', this.handleTrapElementsChange)\n\t\tsubscribe('editor:toggle', this.toggleEditor)\n\t\tsubscribe('editor:file:created', this.handleNewFile)\n\t\twindow.addEventListener('keydown', this.keyboardDeleteFile)\n\t\twindow.addEventListener('keydown', this.keyboardDownloadFile)\n\t\twindow.addEventListener('keydown', this.keyboardEditFile)\n\t\tthis.addFullscreenEventListeners()\n\t},\n\n\tbeforeDestroy() {\n\t\twindow.removeEventListener('resize', this.onResize)\n\t},\n\n\tdestroyed() {\n\t\t// Unsubscribe to Files Sidebar events.\n\t\tunsubscribe('files:sidebar:opened', this.handleAppSidebarOpen)\n\t\tunsubscribe('files:sidebar:closed', this.handleAppSidebarClose)\n\t\tunsubscribe('viewer:trapElements:changed', this.handleTrapElementsChange)\n\t\tunsubscribe('editor:toggle', this.toggleEditor)\n\t\twindow.removeEventListener('keydown', this.keyboardDeleteFile)\n\t\twindow.removeEventListener('keydown', this.keyboardDownloadFile)\n\t\twindow.removeEventListener('keydown', this.keyboardEditFile)\n\t\tthis.removeFullscreenEventListeners()\n\t},\n\n\tmethods: {\n\t\tuniqueKey(file) {\n\t\t\treturn '' + file.fileid + file.source\n\t\t},\n\n\t\ttoggleEditor(isOpen) {\n\t\t\ttoggleEditor(isOpen)\n\t\t\tthis.editing = isOpen\n\t\t},\n\n\t\t/**\n\t\t * If there is no download permission also hide the context menu.\n\t\t * @param {MouseEvent} event The mouse click event\n\t\t */\n\t\tpreventContextMenu(event) {\n\t\t\tif (this.canDownload) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t},\n\n\t\tasync beforeOpen() {\n\t\t\t// initial loading start\n\t\t\tthis.initiated = true\n\n\t\t\tif (OCA?.Files?.Sidebar?.setFullScreenMode) {\n\t\t\t\tOCA.Files.Sidebar.setFullScreenMode(true)\n\t\t\t}\n\t\t\tthis.sortingConfig = await getSortingConfig()\n\n\t\t\t// Load Roboto font for visual regression tests\n\t\t\tif (window.loadRoboto) {\n\t\t\t\tlogger.debug('⚠️ Loading roboto font for visual regression tests')\n\t\t\t\timport('@fontsource/roboto/index.css')\n\t\t\t\tdelete window.loadRoboto\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Open the view and display the clicked file\n\t\t *\n\t\t * @param {string} path the file path to open\n\t\t * @param {string|null} overrideHandlerId the ID of the handler with which to view the files, if any\n\t\t */\n\t\tasync openFile(path, overrideHandlerId = null) {\n\t\t\tawait this.beforeOpen()\n\n\t\t\t// cancel any previous request\n\t\t\tthis.cancelRequestFile()\n\n\t\t\t// do not open the same file again\n\t\t\tif (this.isSameFile(null, path)) {\n\t\t\t\tlogger.debug('Viewer already opened with the same path, ignoring', { path })\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst { request: fileRequest, cancel: cancelRequestFile } = cancelableRequest(getFileInfo)\n\t\t\tthis.cancelRequestFile = cancelRequestFile\n\n\t\t\t// extract needed info from path\n\t\t\tconst [, fileName] = extractFilePaths(path)\n\n\t\t\t// prevent scrolling while opened\n\t\t\tif (!this.el) {\n\t\t\t\tdocument.body.style.overflow = 'hidden'\n\t\t\t\tdocument.documentElement.style.overflow = 'hidden'\n\t\t\t}\n\n\t\t\t// swap title with original one\n\t\t\tconst title = document.getElementsByTagName('head')[0].getElementsByTagName('title')[0]\n\t\t\tif (title && !title.dataset.old && fileName !== '') {\n\t\t\t\ttitle.dataset.old = document.title\n\t\t\t\tthis.updateTitle(fileName)\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// retrieve and store the file info\n\t\t\t\tconst fileInfo = await fileRequest(path)\n\t\t\t\tconsole.debug('File info for ' + path + ' fetched', fileInfo)\n\t\t\t\tawait this.openFileInfo(fileInfo, overrideHandlerId)\n\t\t\t\tif (!this.isStandalone && this.canEdit\n\t\t\t\t\t&& window.OCP?.Files?.Router?.query?.editing === 'true') {\n\t\t\t\t\tthis.toggleEditor(true)\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tif (error?.response?.status === 404) {\n\t\t\t\t\tlogger.error('The file no longer exists, error: ', { error })\n\t\t\t\t\tshowError(t('viewer', 'This file no longer exists'))\n\t\t\t\t\tthis.close()\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error('Could not open file ' + path, error)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tasync handleNewFile(source) {\n\t\t\tlet path\n\t\t\ttry {\n\t\t\t\tpath = extractFilePathFromSource(source)\n\t\t\t\tthis.openFile(path)\n\n\t\t\t} catch (e) {\n\t\t\t\tlogger.error('Could not extract file path from source', { source, e })\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst node = await fetchNode('/' + path)\n\t\t\t\temit('files:node:created', node)\n\t\t\t} catch (e) {\n\t\t\t\tlogger.error('Could not fetch new file', { path, e })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Open the view and display the clicked file from a known file info object\n\t\t *\n\t\t * @param {object} fileInfo the file info object to open\n\t\t * @param {string|null} overrideHandlerId the ID of the handler with which to view the files, if any\n\t\t */\n\t\tasync openFileInfo(fileInfo, overrideHandlerId = null) {\n\t\t\tthis.beforeOpen()\n\t\t\t// cancel any previous request\n\t\t\tthis.cancelRequestFolder()\n\n\t\t\t// do not open the same file info again\n\t\t\tif (this.isSameFile(fileInfo)) {\n\t\t\t\tlogger.debug('Viewer already opened with the same fileInfo, ignoring', { fileInfo })\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// get original mime and alias\n\t\t\tconst mime = fileInfo.mime\n\t\t\tconst alias = mime.split('/')[0]\n\n\t\t\tlet handler\n\t\t\t// Try provided handler, if any\n\t\t\tif (overrideHandlerId !== null) {\n\t\t\t\tconst overrideHandler = Object.values(this.registeredHandlers).find(h => h.id === overrideHandlerId)\n\t\t\t\thandler = overrideHandler ?? handler\n\t\t\t}\n\t\t\t// If no provided handler, or provided handler not found: try a supported handler with mime/mime-alias\n\t\t\tif (!handler) {\n\t\t\t\thandler = this.registeredHandlers[mime] ?? this.registeredHandlers[alias]\n\t\t\t}\n\n\t\t\t// if we don't have a handler for this mime, abort\n\t\t\tif (!handler) {\n\t\t\t\tlogger.error('The following file could not be displayed', { fileInfo })\n\t\t\t\tshowError(t('viewer', 'There is no plugin available to display this file type'))\n\t\t\t\tthis.close()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.theme = handler.theme ?? 'dark'\n\t\t\tconst defaultThemeIsLight = window.getComputedStyle(document.body).getPropertyValue('--background-invert-if-dark') !== 'invert(100%)'\n\t\t\tthis.lightBackdrop = handler.theme === 'light' || (handler.theme === 'default' && defaultThemeIsLight)\n\t\t\tthis.handlerId = handler.id\n\n\t\t\tthis.currentFile = new File(fileInfo, mime, handler.component)\n\t\t\tthis.comparisonFile = null\n\t\t\tthis.updatePreviousNext()\n\n\t\t\t// check if part of a group, if so retrieve full files list\n\t\t\tconst group = this.mimeGroups[mime]\n\t\t\tif (this.files && this.files.length > 0) {\n\t\t\t\tlogger.debug('A files list have been provided. No folder content will be fetched.')\n\t\t\t\t// we won't sort files here, let's use the order the array has\n\t\t\t\tthis.fileList = this.files\n\n\t\t\t\t// store current position\n\t\t\t\tthis.currentIndex = this.fileList.findIndex(file => file.filename === fileInfo.filename)\n\t\t\t} else if (group && this.el === null) {\n\t\t\t\tconst mimes = this.mimeGroups[group]\n\t\t\t\t\t? this.mimeGroups[group]\n\t\t\t\t\t: [mime]\n\n\t\t\t\t// retrieve folder list\n\t\t\t\tconst { request: folderRequest, cancel: cancelRequestFolder } = cancelableRequest(getFileList)\n\t\t\t\tthis.cancelRequestFolder = cancelRequestFolder\n\t\t\t\tconst [dirPath] = extractFilePaths(fileInfo.filename)\n\n\t\t\t\tthis.currentIndex = 0\n\t\t\t\tthis.fileList = [fileInfo]\n\n\t\t\t\tconst fileList = await folderRequest(dirPath)\n\n\t\t\t\t// filter out the unwanted mimes\n\t\t\t\tconst filteredFiles = fileList.filter(file => file.mime && mimes.indexOf(file.mime) !== -1)\n\n\t\t\t\t// sort like the files list\n\t\t\t\t// TODO: implement global sorting API\n\t\t\t\t// https://github.com/nextcloud/server/blob/a83b79c5f8ab20ed9b4d751167417a65fa3c42b8/apps/files/lib/Controller/ApiController.php#L247\n\t\t\t\tconst nodes = filteredFiles.map(\n\t\t\t\t\tfile => new NcFile({\n\t\t\t\t\t\tsource: davRemoteURL + davGetRootPath() + file.filename,\n\t\t\t\t\t\tid: file.fileid,\n\t\t\t\t\t\tdisplayname: file.displayname,\n\t\t\t\t\t\tmime: file.mime,\n\t\t\t\t\t\tmtime: new Date(file.lastmod),\n\t\t\t\t\t\towner: this.currentFile.ownerId,\n\t\t\t\t\t\troot: davGetRootPath(),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t\tconst sortedNodes = sortNodes(nodes, {\n\t\t\t\t\tsortingMode: this.sortingConfig.key,\n\t\t\t\t\tsortingOrder: this.sortingConfig.asc ? 'asc' : 'desc',\n\t\t\t\t})\n\n\t\t\t\tthis.fileList = sortedNodes.map(node => {\n\t\t\t\t\treturn filteredFiles.find(file => file.filename === node.path)\n\t\t\t\t})\n\t\t\t\t// store current position\n\t\t\t\tthis.currentIndex = this.fileList.findIndex(file => file.filename === fileInfo.filename)\n\t\t\t\tthis.updatePreviousNext()\n\t\t\t} else {\n\t\t\t\tthis.currentIndex = 0\n\t\t\t\tthis.fileList = [fileInfo]\n\t\t\t}\n\n\t\t\t// if sidebar was opened before, let's update the file\n\t\t\tthis.changeSidebar()\n\t\t},\n\n\t\t/**\n\t\t * Open the view and display the file from the file list\n\t\t *\n\t\t * @param {object} fileInfo the opened file info\n\t\t */\n\t\topenFileFromList(fileInfo) {\n\t\t\t// override mimetype if existing alias\n\t\t\tconst mime = fileInfo.mime\n\t\t\tthis.currentFile = new File(fileInfo, mime, this.components[mime])\n\t\t\tthis.changeSidebar()\n\t\t\tthis.updatePreviousNext()\n\t\t},\n\n\t\tasync compareFile(fileInfo) {\n\t\t\tthis.comparisonFile = new File(fileInfo, fileInfo.mime, this.components[fileInfo.mime])\n\t\t},\n\n\t\t/**\n\t\t * Show sidebar if available and a file is already opened\n\t\t */\n\t\tchangeSidebar() {\n\t\t\tif (this.sidebarFile) {\n\t\t\t\tthis.showSidebar()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Update the previous and next file components\n\t\t */\n\t\tupdatePreviousNext() {\n\t\t\tconst prev = this.fileList[this.currentIndex - 1]\n\t\t\tconst next = this.fileList[this.currentIndex + 1]\n\n\t\t\tif (prev) {\n\t\t\t\tconst mime = prev.mime\n\t\t\t\tif (this.components[mime]) {\n\t\t\t\t\tthis.previousFile = new File(prev, mime, this.components[mime])\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// RESET\n\t\t\t\tthis.previousFile = {}\n\t\t\t}\n\n\t\t\tif (next) {\n\t\t\t\tconst mime = next.mime\n\t\t\t\tif (this.components[mime]) {\n\t\t\t\t\tthis.nextFile = new File(next, mime, this.components[mime])\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// RESET\n\t\t\t\tthis.nextFile = {}\n\t\t\t}\n\n\t\t},\n\n\t\tupdateTitle(fileName) {\n\t\t\tdocument.title = `${fileName} - ${OCA.Theming?.name ?? oc_defaults.name}`\n\t\t},\n\n\t\t/**\n\t\t * Registering possible new handlers\n\t\t *\n\t\t * @param {object} handler the handler to register\n\t\t * @param {string} handler.id unique handler identifier\n\t\t * @param {Array} handler.mimes list of valid mimes compatible with the handler\n\t\t * @param {object} handler.component a VueJs component to render when a file matching the mime list is opened\n\t\t * @param {string} [handler.group] a group name to be associated with for the slideshow\n\t\t */\n\t\tregisterHandler(handler) {\n\t\t\t// checking if handler is not already registered\n\t\t\tif (handler.id && Object.values(this.registeredHandlers).findIndex((h) => h.id === handler.id) > -1) {\n\t\t\t\tlogger.error('The following handler is already registered', { handler })\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// checking valid handler id\n\t\t\tif (!handler.id || handler.id.trim() === '' || typeof handler.id !== 'string') {\n\t\t\t\tlogger.error('The following handler doesn\\'t have a valid id', { handler })\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// checking if no valid mimes data but alias. If so, skipping...\n\t\t\tif (!(handler.mimes && Array.isArray(handler.mimes)) && handler.mimesAliases) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Nothing available to process! Failure\n\t\t\tif (!(handler.mimes && Array.isArray(handler.mimes)) && !handler.mimesAliases) {\n\t\t\t\tlogger.error('The following handler doesn\\'t have a valid mime array', { handler })\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// checking valid handler component data\n\t\t\tif ((!handler.component || (typeof handler.component !== 'object' && typeof handler.component !== 'function'))) {\n\t\t\t\tlogger.error('The following handler doesn\\'t have a valid component', { handler })\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// force apply mixin\n\t\t\thandler.component.mixins = [...handler?.component?.mixins ?? [], Mime]\n\n\t\t\t// parsing mimes registration\n\t\t\tif (handler.mimes) {\n\t\t\t\thandler.mimes.forEach(mime => {\n\t\t\t\t\t// checking valid mime\n\t\t\t\t\tif (this.components[mime]) {\n\t\t\t\t\t\tlogger.error('The following mime is already registered', { mime, handler })\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// register groups\n\t\t\t\t\tthis.registerGroups({ mime, group: handler.group })\n\n\t\t\t\t\t// register mime's component\n\t\t\t\t\tthis.components[mime] = handler.component\n\t\t\t\t\tVue.component(handler.component.name, handler.component)\n\n\t\t\t\t\t// set the handler as registered\n\t\t\t\t\tthis.registeredHandlers[mime] = handler\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\n\t\tregisterHandlerAlias(handler) {\n\t\t\t// parsing aliases registration\n\t\t\tif (handler.mimesAliases) {\n\t\t\t\tObject.keys(handler.mimesAliases).forEach(mime => {\n\n\t\t\t\t\tif (handler.mimesAliases && typeof handler.mimesAliases !== 'object') {\n\t\t\t\t\t\tlogger.error('The following handler doesn\\'t have a valid mimesAliases object', { handler })\n\t\t\t\t\t\treturn\n\n\t\t\t\t\t}\n\n\t\t\t\t\t// this is the targeted alias\n\t\t\t\t\tconst alias = handler.mimesAliases[mime]\n\n\t\t\t\t\t// checking valid mime\n\t\t\t\t\tif (this.components[mime]) {\n\t\t\t\t\t\tlogger.error('The following mime is already registered', { mime, handler })\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif (!this.components[alias]) {\n\t\t\t\t\t\tlogger.error('The requested alias does not exists', { alias, mime, handler })\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// register groups if the request alias had a group\n\t\t\t\t\tthis.registerGroups({ mime, group: this.mimeGroups[alias] })\n\n\t\t\t\t\t// register mime's component\n\t\t\t\t\tthis.components[mime] = this.components[alias]\n\n\t\t\t\t\t// set the handler as registered\n\t\t\t\t\tthis.registeredHandlers[mime] = handler\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\n\t\tregisterGroups({ mime, group }) {\n\t\t\tif (group) {\n\t\t\t\tthis.mimeGroups[mime] = group\n\t\t\t\t// init if undefined\n\t\t\t\tif (!this.mimeGroups[group]) {\n\t\t\t\t\tthis.mimeGroups[group] = []\n\t\t\t\t}\n\t\t\t\tthis.mimeGroups[group].push(mime)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Close the viewer\n\t\t */\n\t\tclose() {\n\t\t\t// This will set file to ''\n\t\t\t// which then triggers cleanup.\n\t\t\tOCA.Viewer.close()\n\n\t\t\tif (OCA?.Files?.Sidebar) {\n\t\t\t\tOCA.Files.Sidebar.setFullScreenMode(false)\n\t\t\t}\n\n\t\t\tif (this.isFullscreenMode) {\n\t\t\t\tthis.exitFullscreen()\n\t\t\t}\n\t\t},\n\n\t\tkeyboardDeleteFile(event) {\n\t\t\tif (this.canDelete && event.key === 'Delete' && event.ctrlKey === true) {\n\t\t\t\tthis.onDelete()\n\t\t\t}\n\t\t},\n\n\t\tkeyboardDownloadFile(event) {\n\t\t\tif (event.key === 's' && event.ctrlKey === true) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tif (this.canDownload) {\n\t\t\t\t\tthis.onDownload()\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tkeyboardEditFile(event) {\n\t\t\tif (event.key === 'e' && event.ctrlKey === true) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tif (this.canEdit) {\n\t\t\t\t\tthis.onEdit()\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tcleanup() {\n\t\t\tlogger.info('Cleaning up viewer')\n\n\t\t\t// reset all properties\n\t\t\tthis.currentFile = {}\n\t\t\tthis.comparisonFile = null\n\t\t\tthis.currentModal = null\n\t\t\tthis.fileList = []\n\t\t\tthis.initiated = false\n\t\t\tthis.theme = null\n\n\t\t\t// cancel requests\n\t\t\tthis.cancelRequestFile()\n\t\t\tthis.cancelRequestFolder()\n\n\t\t\t// restore default\n\t\t\tdocument.body.style.overflow = null\n\t\t\tdocument.documentElement.style.overflow = null\n\n\t\t\t// Callback before updating the title\n\t\t\t// If the callback creates a new entry in browser history\n\t\t\t// the title update will affect the new entry\n\t\t\t// rather then the previous one.\n\t\t\tthis.Viewer.onClose()\n\n\t\t\t// swap back original title\n\t\t\tconst title = document.getElementsByTagName('head')[0].getElementsByTagName('title')[0]\n\t\t\tif (title && title.dataset.old) {\n\t\t\t\tdocument.title = title.dataset.old\n\t\t\t\tdelete title.dataset.old\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Open previous available file\n\t\t */\n\t\tprevious() {\n\t\t\tthis.currentIndex--\n\t\t\tif (this.currentIndex < 0) {\n\t\t\t\tthis.currentIndex = this.fileList.length - 1\n\t\t\t}\n\n\t\t\tconst fileInfo = this.fileList[this.currentIndex]\n\t\t\tthis.openFileFromList(fileInfo)\n\t\t\tthis.Viewer.onPrev(fileInfo)\n\t\t\tthis.updateTitle(this.currentFile.basename)\n\t\t},\n\n\t\t/**\n\t\t * Open next available file\n\t\t */\n\t\tnext() {\n\t\t\tthis.currentIndex++\n\t\t\tif (this.currentIndex > this.fileList.length - 1) {\n\t\t\t\tthis.currentIndex = 0\n\t\t\t}\n\n\t\t\tconst fileInfo = this.fileList[this.currentIndex]\n\t\t\tthis.openFileFromList(fileInfo)\n\t\t\tthis.Viewer.onNext(fileInfo)\n\n\t\t\tthis.updateTitle(this.currentFile.basename)\n\t\t},\n\n\t\t/**\n\t\t * Failures handlers\n\t\t */\n\t\tcomparisonFailed() {\n\t\t\tthis.comparisonFile.failed = true\n\t\t},\n\n\t\tpreviousFailed() {\n\t\t\tthis.previousFile.failed = true\n\t\t},\n\n\t\tcurrentFailed() {\n\t\t\tthis.currentFile.failed = true\n\t\t},\n\n\t\tnextFailed() {\n\t\t\tthis.nextFile.failed = true\n\t\t},\n\n\t\t/**\n\t\t * Show the sharing sidebar\n\t\t */\n\n\t\tasync showSidebar() {\n\t\t\t// Open the sidebar sharing tab\n\t\t\t// TODO: also hide figure, needs a proper method for it in server Sidebar\n\n\t\t\tif (this.enableSidebar && OCA?.Files?.Sidebar) {\n\t\t\t\tawait OCA.Files.Sidebar.open(this.sidebarOpenFilePath)\n\t\t\t}\n\t\t},\n\n\t\thandleAppSidebarOpen() {\n\t\t\tthis.isSidebarShown = true\n\t\t\tconst sidebar = document.querySelector('aside.app-sidebar')\n\t\t\tif (sidebar) {\n\t\t\t\tthis.sidebarPosition = sidebar.getBoundingClientRect().left\n\t\t\t\tthis.trapElements = [sidebar]\n\t\t\t}\n\t\t},\n\n\t\thandleAppSidebarClose() {\n\t\t\tthis.isSidebarShown = false\n\t\t\tthis.trapElements = []\n\t\t},\n\n\t\t// Update etag of updated file to break cache.\n\t\t/**\n\t\t *\n\t\t * @param {Node} node\n\t\t */\n\t\tasync handleFileUpdated(node) {\n\t\t\tconst index = this.fileList.findIndex(({ fileid: currentFileId }) => currentFileId === node.fileid)\n\n\t\t\t// Ensure compatibility with the legacy data model that the Viewer is using. (see \"model.ts\").\n\t\t\t// This can be removed once Viewer is migrated to the new Node API.\n\t\t\tnode.etag = node.attributes.etag\n\t\t\tthis.fileList.splice(index, 1, node)\n\t\t\tif (node.fileid === this.currentFile.fileid) {\n\t\t\t\tthis.currentFile.etag = node.attributes.etag\n\t\t\t}\n\t\t},\n\n\t\tonResize() {\n\t\t\tconst sidebar = document.querySelector('aside.app-sidebar')\n\t\t\tif (sidebar) {\n\t\t\t\tthis.sidebarPosition = sidebar.getBoundingClientRect().left\n\t\t\t}\n\t\t},\n\n\t\tasync onDelete() {\n\t\t\ttry {\n\t\t\t\tconst fileid = this.currentFile.fileid\n\t\t\t\tconst url = this.currentFile.source ?? this.currentFile.davPath\n\n\t\t\t\t// Fake node to emit the event until Viewer is migrated to the new Node API.\n\t\t\t\tconst node = new NcFile({\n\t\t\t\t\tsource: url,\n\t\t\t\t\tid: fileid,\n\t\t\t\t\tmime: this.currentFile.mime,\n\t\t\t\t\towner: this.currentFile.ownerId,\n\t\t\t\t\troot: url.includes('remote.php/dav') ? davGetRootPath() : undefined,\n\t\t\t\t})\n\n\t\t\t\tawait axios.delete(url)\n\t\t\t\temit('files:node:deleted', node)\n\n\t\t\t\t// fileid is not unique, basename is not unique, filename is\n\t\t\t\tconst currentIndex = this.fileList.findIndex(file => file.filename === this.currentFile.filename)\n\t\t\t\tif (this.hasPrevious || this.hasNext) {\n\t\t\t\t\t// Checking the previous or next file\n\t\t\t\t\tthis.hasNext ? this.next() : this.previous()\n\n\t\t\t\t\tthis.fileList.splice(currentIndex, 1)\n\t\t\t\t} else {\n\t\t\t\t\tthis.close()\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(error)\n\t\t\t\tshowError(error)\n\t\t\t}\n\t\t},\n\n\t\tonEdit() {\n\t\t\tthis.toggleEditor(true)\n\t\t},\n\n\t\t/**\n\t\t * Call handler's downloadCallback before downloading\n\t\t */\n\t\tasync onDownload() {\n\t\t\tif (!this.canDownload) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Get the current handler for this file\n\t\t\tconst mime = this.currentFile.mime\n\t\t\tconst alias = mime?.split('/')[0]\n\t\t\tconst handler = this.registeredHandlers[mime] ?? this.registeredHandlers[alias]\n\n\t\t\tif (handler?.downloadCallback && typeof handler.downloadCallback === 'function') {\n\t\t\t\ttry {\n\t\t\t\t\tlogger.debug('Calling handler downloadCallback before download')\n\t\t\t\t\tawait handler.downloadCallback(this.currentFile)\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger.error('Failed to execute downloadCallback', { error })\n\t\t\t\t\tshowError(t('viewer', 'Failed to save file before download'))\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.performDownload()\n\t\t},\n\n\t\tperformDownload() {\n\t\t\tlogger.debug('Performing download', { file: this.currentFile })\n\t\t\tconst a = document.createElement('a')\n\t\t\ta.href = this.currentFile.source ?? this.currentFile.davPath\n\t\t\ta.download = this.currentFile.basename\n\t\t\tdocument.body.appendChild(a)\n\t\t\ta.click()\n\t\t\tdocument.body.removeChild(a)\n\t\t},\n\n\t\thandleTrapElementsChange(element) {\n\t\t\tthis.trapElements.push(element)\n\t\t},\n\n\t\t// Support full screen API on standard-compliant browsers and Safari (apparently except iPhone).\n\t\t// Implementation based on:\n\t\t// https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API/Guide\n\n\t\ttoggleFullScreen() {\n\t\t\tif (this.isFullscreenMode) {\n\t\t\t\tthis.exitFullscreen()\n\t\t\t} else {\n\t\t\t\tthis.requestFullscreen()\n\t\t\t}\n\t\t},\n\n\t\trequestFullscreen() {\n\t\t\tconst el = document.documentElement\n\t\t\tif (el.requestFullscreen) {\n\t\t\t\tel.requestFullscreen()\n\t\t\t} else if (el.webkitRequestFullscreen) {\n\t\t\t\tel.webkitRequestFullscreen()\n\t\t\t}\n\t\t},\n\n\t\texitFullscreen() {\n\t\t\tif (document.exitFullscreen) {\n\t\t\t\tdocument.exitFullscreen()\n\t\t\t} else if (document.webkitExitFullscreen) {\n\t\t\t\tdocument.webkitExitFullscreen()\n\t\t\t}\n\t\t},\n\n\t\taddFullscreenEventListeners() {\n\t\t\tdocument.addEventListener('fullscreenchange', this.onFullscreenchange)\n\t\t\tdocument.addEventListener('webkitfullscreenchange', this.onFullscreenchange)\n\t\t},\n\n\t\tremoveFullscreenEventListeners() {\n\t\t\tdocument.addEventListener('fullscreenchange', this.onFullscreenchange)\n\t\t\tdocument.addEventListener('webkitfullscreenchange', this.onFullscreenchange)\n\t\t},\n\n\t\tonFullscreenchange() {\n\t\t\tif (document.fullscreenElement === document.documentElement\n\t\t\t\t|| document.webkitFullscreenElement === document.documentElement) {\n\t\t\t\tthis.isFullscreenMode = true\n\t\t\t} else {\n\t\t\t\tthis.isFullscreenMode = false\n\t\t\t}\n\t\t},\n\n\t},\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.viewer {\n\t&.modal-mask {\n\t\ttransition: width ease 100ms, background-color .3s ease;\n\t}\n\n\t:deep(.modal-container),\n\t&__content {\n\t\toverflow: visible !important;\n\t\tcursor: pointer;\n\t}\n\n\t&--split {\n\t\tdisplay: flex;\n\n\t\t.viewer__file--active {\n\t\t\twidth: 50%;\n\t\t\tleft: 0;\n\t\t\tposition: relative;\n\t\t}\n\t}\n\n\t:deep(.modal-wrapper) {\n\t\t.modal-container {\n\t\t\t// Ensure some space at the bottom\n\t\t\ttop: var(--header-height);\n\t\t\tbottom: var(--header-height);\n\t\t\theight: auto;\n\t\t\t// let the mime components manage their own background-color\n\t\t\tbackground-color: transparent;\n\t\t\tbox-shadow: none;\n\t\t}\n\t}\n\n\t&__content {\n\t\twidth: 100%;\n\t\theight: 100%;\n\t}\n\n\t&__file-wrapper {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\twidth: 100%;\n\t\theight: 100%;\n\n\t\t// display on page but make it invisible\n\t\t&--hidden {\n\t\t\tposition: absolute;\n\t\t\tz-index: -1;\n\t\t\tleft: -10000px;\n\t\t}\n\t}\n\n\t&__file {\n\t\ttransition: height 100ms ease,\n\t\t\twidth 100ms ease;\n\t}\n\n\t&.theme--dark:deep(.button-vue--vue-tertiary) {\n\t\t&:hover {\n\t\t\tbackground-color: rgba(255, 255, 255, .08) !important;\n\t\t}\n\t\t&:focus,\n\t\t&:focus-visible {\n\t\t\tbackground-color: rgba(255, 255, 255, .08) !important;\n\t\t\toutline: 2px solid var(--color-primary-element) !important;\n\t\t}\n\t\t&.action-item__menutoggle {\n\t\t\tbackground-color: transparent;\n\t\t}\n\t}\n\n\t&.theme--undefined.modal-mask {\n\t\tbackground-color: transparent !important;\n\t}\n\n\t&.theme--light {\n\t\t&.modal-mask {\n\t\t\tbackground-color: rgba(255, 255, 255, .92) !important;\n\t\t}\n\t\t:deep(.modal-header__name),\n\t\t:deep(.modal-header .icons-menu button svg) {\n\t\t\tcolor: #000 !important;\n\t\t}\n\t}\n\n\t&.theme--default {\n\t\t&.modal-mask {\n\t\t\tbackground-color: var(--color-main-background) !important;\n\t\t}\n\t\t:deep(.modal-header__name),\n\t\t:deep(.modal-header .icons-menu) {\n\t\t\tcolor: var(--color-main-text) !important;\n\n\t\t\tbutton svg, a {\n\t\t\t\tcolor: var(--color-main-text) !important;\n\t\t\t}\n\t\t}\n\t}\n\n\t&.image--fullscreen {\n\t\t// Special display mode for images in full screen\n\t\t:deep(.modal-header) {\n\t\t\t.modal-header__name {\n\t\t\t\t// Hide file name\n\t\t\t\topacity: 0;\n\t\t\t}\n\t\t\t.icons-menu {\n\t\t\t\t// Semi-transparent background for icons only\n\t\t\t\tbackground-color: rgba(0, 0, 0, 0.2);\n\t\t\t}\n\t\t}\n\t\t:deep(.modal-wrapper) {\n\t\t\t.modal-container {\n\t\t\t\t// Use entire screen height\n\t\t\t\ttop: 0;\n\t\t\t\tbottom: 0;\n\t\t\t\theight: 100%;\n\t\t\t}\n\t\t}\n\t}\n}\n\n</style>\n\n<style lang=\"scss\">\n.component-fade-enter-active,\n.component-fade-leave-active {\n\ttransition: opacity .3s ease;\n}\n\n.component-fade-enter, .component-fade-leave-to {\n\topacity: 0;\n}\n\n// force white icon on single buttons\n#viewer.modal-mask--dark .action-item--single.icon-menu-sidebar {\n\tbackground-image: url('../assets/menu-sidebar-white.svg');\n}\n\n#viewer.modal-mask--dark .action-item--single.icon-download {\n\tbackground-image: var(--icon-download-fff);\n}\n\n// put autocomplete over full sidebar\n// TODO: remove when new sharing sidebar (18)\n// is the min-version of viewer\n.ui-autocomplete {\n\tz-index: 2050 !important;\n}\n\n</style>\n","/**\n * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport Vue from 'vue'\n\nimport ViewerComponent from './views/Viewer.vue'\n\nVue.mixin({\n\tmethods: {\n\t\tt,\n\t},\n})\n\nVue.prototype.OC = window.OC\nVue.prototype.OCA = window.OCA\n\n// Create document root\nconst ViewerRoot = document.createElement('div')\nViewerRoot.id = 'viewer'\ndocument.body.appendChild(ViewerRoot)\n\n// Put controls for video viewer\n// Needed as Firefox CSP blocks the loading of the svg through the normal plyr system\nconst VideoControls = document.createElement('div')\nVideoControls.innerHTML = PLYR_ICONS\nVideoControls.style.display = 'none'\ndocument.body.appendChild(VideoControls)\n\n// Init vue\nexport default new Vue({\n\tel: '#viewer',\n\t// When debugging the page, it's easier to find which app\n\t// is which. Especially when there is multiple apps\n\t// roots mounted o the same page!\n\t// eslint-disable-next-line vue/match-component-file-name\n\tname: 'ViewerRoot',\n\trender: h => h(ViewerComponent),\n})\n"],"file":"js/viewer-main.mjs"} |