diff --git a/lib/Listener/LoadViewerScript.php b/lib/Listener/LoadViewerScript.php
index 552a22b78..51e3466bc 100644
--- a/lib/Listener/LoadViewerScript.php
+++ b/lib/Listener/LoadViewerScript.php
@@ -11,6 +11,7 @@
use OCA\Files\Event\LoadAdditionalScriptsEvent;
use OCA\Viewer\AppInfo\Application;
use OCA\Viewer\Event\LoadViewer;
+use OCP\AppFramework\Services\IAppConfig;
use OCP\AppFramework\Services\IInitialState;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
@@ -28,6 +29,7 @@ class LoadViewerScript implements IEventListener {
public function __construct(
IInitialState $initialStateService,
IPreview $previewManager,
+ private readonly IAppConfig $appConfig,
) {
$this->initialStateService = $initialStateService;
$this->previewManager = $previewManager;
@@ -39,9 +41,13 @@ public function handle(Event $event): void {
}
Util::addStyle(Application::APP_ID, 'viewer-init');
+
+ $alwaysShowViewer = $this->appConfig->getAppValue('always_show_viewer', 'no') === 'yes';
+
Util::addStyle(Application::APP_ID, 'viewer-main');
Util::addInitScript(Application::APP_ID, 'viewer-init');
Util::addScript(Application::APP_ID, 'viewer-main', 'files');
$this->initialStateService->provideInitialState('enabled_preview_providers', array_keys($this->previewManager->getProviders()));
+ $this->initialStateService->provideInitialState('always_show_viewer', $alwaysShowViewer);
}
}
diff --git a/src/components/Default.vue b/src/components/Default.vue
new file mode 100644
index 000000000..686b3a5bd
--- /dev/null
+++ b/src/components/Default.vue
@@ -0,0 +1,69 @@
+
+
+
+
![mimetype-icon]()
+
+ {{ basename }}
+
+
+
+
+
+
+
diff --git a/src/components/Images.vue b/src/components/Images.vue
index dd07e1441..ecd7534ec 100644
--- a/src/components/Images.vue
+++ b/src/components/Images.vue
@@ -12,7 +12,8 @@
@close="onClose" />
-
+
node.type === 'folder')
+ }
+
return nodes.every((node) =>
Boolean(node.permissions & Permission.READ)
&& window.OCA.Viewer.mimetypes.includes(node.mime),
diff --git a/src/models/config.ts b/src/models/config.ts
new file mode 100644
index 000000000..20d87b7bd
--- /dev/null
+++ b/src/models/config.ts
@@ -0,0 +1,12 @@
+/**
+ * SPDX-FileCopyrightText: 2024 STRATO AG
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+import { loadState } from '@nextcloud/initial-state'
+
+const alwaysShowViewer = loadState('viewer', 'always_show_viewer', false)
+
+export default {
+ alwaysShowViewer,
+ defaultMimeType: 'all',
+}
diff --git a/src/models/default.ts b/src/models/default.ts
new file mode 100644
index 000000000..94ef98f3a
--- /dev/null
+++ b/src/models/default.ts
@@ -0,0 +1,17 @@
+/**
+ * SPDX-FileCopyrightText: 2024 STRATO AG
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import Default from '../components/Default.vue'
+import config from './config.ts'
+
+export default {
+ id: 'default',
+ group: 'other',
+ mimes: [
+ config.defaultMimeType,
+ ],
+ mimesAliases: {},
+ component: Default,
+}
diff --git a/src/services/Viewer.js b/src/services/Viewer.js
index 3b1076be0..2c671f8f5 100644
--- a/src/services/Viewer.js
+++ b/src/services/Viewer.js
@@ -6,6 +6,7 @@
import Images from '../models/images.js'
import Videos from '../models/videos.js'
import Audios from '../models/audios.js'
+import Default from '../models/default.ts'
import logger from './logger.js'
/**
@@ -62,6 +63,7 @@ export default class Viewer {
this.registerHandler(Images)
this.registerHandler(Videos)
this.registerHandler(Audios)
+ this.registerHandler(Default)
logger.debug('OCA.Viewer initialized')
}
diff --git a/src/utils/livePhotoUtils.ts b/src/utils/livePhotoUtils.ts
index 432252b15..20afec18c 100644
--- a/src/utils/livePhotoUtils.ts
+++ b/src/utils/livePhotoUtils.ts
@@ -26,6 +26,6 @@ export function findLivePhotoPeerFromName(referenceFile: BasicFileInfo, fileList
return fileList.find(comparedFile => {
// if same filename and extension is allowed
return comparedFile.filename !== referenceFile.filename
- && (comparedFile.basename.startsWith(referenceFile.name) && livePictureExtRegex.test(comparedFile.basename))
+ && (comparedFile.basename.toString().startsWith(referenceFile.name) && livePictureExtRegex.test(comparedFile.basename))
})
}
diff --git a/src/views/Viewer.vue b/src/views/Viewer.vue
index 7c2245422..62e6924a1 100644
--- a/src/views/Viewer.vue
+++ b/src/views/Viewer.vue
@@ -44,7 +44,7 @@
:inline-actions="canEdit ? 1 : 0"
:spread-navigation="true"
:style="{ width: isSidebarShown ? `${sidebarPosition}px` : null }"
- :name="currentFile.basename"
+ :name="modalTitle"
class="viewer"
size="full"
@close="close"
@@ -192,6 +192,7 @@ import { canDownload } from '../utils/canDownload.ts'
import { extractFilePaths, extractFilePathFromSource } from '../utils/fileUtils.ts'
import { toggleEditor } from '../files_actions/viewerAction.ts'
import cancelableRequest from '../utils/CancelableRequest.js'
+import configModule from '../models/config.ts'
import Error from '../components/Error.vue'
import fetchNode from '../services/FetchFile.ts'
import File from '../models/file.js'
@@ -390,6 +391,14 @@ export default defineComponent({
}
},
+ modalTitle() {
+ if (!configModule.alwaysShowViewer) {
+ return this.currentFile.basename
+ }
+
+ return this.currentFile?.modal?.name === 'Default' ? '' : this.currentFile.basename
+ },
+
showComparison() {
return !this.isMobile
},
@@ -728,6 +737,11 @@ export default defineComponent({
handler = this.registeredHandlers[mime] ?? this.registeredHandlers[alias]
}
+ // fallback to default viewer if enabled
+ if (!handler && configModule.alwaysShowViewer) {
+ handler = this.registeredHandlers[configModule.defaultMimeType]
+ }
+
// if we don't have a handler for this mime, abort
if (!handler) {
logger.error('The following file could not be displayed', { fileInfo })
@@ -745,8 +759,10 @@ export default defineComponent({
this.comparisonFile = null
this.updatePreviousNext()
+ // fallback to default viewer group if enabled
+ const groupFallback = configModule.alwaysShowViewer ? this.mimeGroups[configModule.defaultMimeType] : undefined
// check if part of a group, if so retrieve full files list
- const group = this.mimeGroups[mime]
+ const group = this.mimeGroups[mime] ?? groupFallback
if (this.files && this.files.length > 0) {
logger.debug('A files list have been provided. No folder content will be fetched.')
// we won't sort files here, let's use the order the array has
@@ -769,8 +785,22 @@ export default defineComponent({
const fileList = await folderRequest(dirPath)
- // filter out the unwanted mimes
- const filteredFiles = fileList.filter(file => file.mime && mimes.indexOf(file.mime) !== -1)
+ let filteredFiles
+ if (configModule.alwaysShowViewer) {
+ // only include files with mime to exclude directories
+ // and office documents/pdfs to exclude collabora files
+ // otherwise accept all mimes
+ filteredFiles = fileList.filter(file => {
+ const mime = file?.mime
+ const isOfficeDocument = mime && OC.MimeTypeList.aliases[mime]?.startsWith('x-office')
+ const isPdf = mime && mime === 'application/pdf'
+
+ return mime && !isOfficeDocument && !isPdf
+ })
+ } else {
+ // filter out the unwanted mimes
+ filteredFiles = fileList.filter(file => file.mime && mimes.indexOf(file.mime) !== -1)
+ }
// sort like the files list
// TODO: implement global sorting API
@@ -814,7 +844,7 @@ export default defineComponent({
openFileFromList(fileInfo) {
// override mimetype if existing alias
const mime = fileInfo.mime
- this.currentFile = new File(fileInfo, mime, this.components[mime])
+ this.currentFile = new File(fileInfo, mime, this.components[mime] || this.components[configModule.defaultMimeType])
this.changeSidebar()
this.updatePreviousNext()
},
@@ -1328,6 +1358,14 @@ export default defineComponent({
}
}
+ // The header actions (play/pause, actions menu, close) are normally pushed
+ // to the right by the full-width `.modal-header__name` element. When the
+ // modal name is empty (Default handler / always-show-viewer), NcModal omits
+ // that element, so keep the menu right-aligned explicitly.
+ :deep(.modal-header .icons-menu) {
+ margin-inline-start: auto;
+ }
+
&__content {
width: 100%;
height: 100%;