From 96fd407c87221f1edce9cc0e55dc61f5b2c17ae6 Mon Sep 17 00:00:00 2001 From: Benjamin Thiemann Date: Mon, 12 Feb 2024 11:02:48 +0100 Subject: [PATCH 01/12] readme: Add documentation for read-only mode Signed-off-by: Benjamin Thiemann --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 93fe6920151..39bb7bddd6d 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,11 @@ The rich workspaces in the file list can be disabled either by the users in the occ config:app:set text workspace_available --value=0 ``` +The app can be configured to open files read-only by default. This setting is globally valid and can be set by the admin with the following command: + +```bash +occ config:app:set text open_read_only_enabled --value=1 +``` ## 🏗 Development setup From 1cfe0da0da6be74ae56a1d201499feb4b6b3905e Mon Sep 17 00:00:00 2001 From: Benjamin Thiemann Date: Tue, 24 Oct 2023 07:55:50 +0200 Subject: [PATCH 02/12] lib: Add config setting for read-only mode Signed-off-by: Benjamin Thiemann --- lib/Service/ConfigService.php | 4 ++++ lib/Service/InitialStateProvider.php | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/lib/Service/ConfigService.php b/lib/Service/ConfigService.php index da3af31a768..3a8051d927b 100644 --- a/lib/Service/ConfigService.php +++ b/lib/Service/ConfigService.php @@ -22,6 +22,10 @@ public function getDefaultFileExtension(): string { return $this->appConfig->getValueString(Application::APP_NAME, 'default_file_extension', 'md'); } + public function isOpenReadOnlyEnabled(): bool { + return $this->appConfig->getValueString(Application::APP_NAME, 'open_read_only_enabled', '0') === '1'; + } + public function isRichEditingEnabled(): bool { return ($this->appConfig->getValueString(Application::APP_NAME, 'rich_editing_enabled', '1') === '1'); } diff --git a/lib/Service/InitialStateProvider.php b/lib/Service/InitialStateProvider.php index f8138f03574..2908b701cf6 100644 --- a/lib/Service/InitialStateProvider.php +++ b/lib/Service/InitialStateProvider.php @@ -42,6 +42,11 @@ public function provideState(): void { $this->configService->isRichWorkspaceEnabledForUser($this->userId) ); + $this->initialState->provideInitialState( + 'open_read_only_enabled', + $this->configService->isOpenReadOnlyEnabled() + ); + $this->initialState->provideInitialState( 'default_file_extension', $this->configService->getDefaultFileExtension() From 7818ee11d06d6f9d4b4955f5dcb331f168245362 Mon Sep 17 00:00:00 2001 From: Benjamin Thiemann Date: Tue, 24 Oct 2023 07:56:05 +0200 Subject: [PATCH 03/12] src: Load config setting for read-only mode Signed-off-by: Benjamin Thiemann --- src/files.js | 2 ++ src/public.js | 1 + 2 files changed, 3 insertions(+) diff --git a/src/files.js b/src/files.js index 8afeb4d99dd..a5b5da6a53a 100644 --- a/src/files.js +++ b/src/files.js @@ -10,6 +10,7 @@ import 'vite/modulepreload-polyfill' const workspaceAvailable = loadState('text', 'workspace_available') const workspaceEnabled = loadState('text', 'workspace_enabled') +const openReadOnlyEnabled = loadState('text', 'open_read_only_enabled') document.addEventListener('DOMContentLoaded', async () => { if (typeof OCA.Viewer === 'undefined') { @@ -39,4 +40,5 @@ document.addEventListener('DOMContentLoaded', async () => { OCA.Text = { RichWorkspaceEnabled: workspaceEnabled, + OpenReadOnlyEnabled: openReadOnlyEnabled, } diff --git a/src/public.js b/src/public.js index 131a7416477..e02ef2753fa 100644 --- a/src/public.js +++ b/src/public.js @@ -162,4 +162,5 @@ documentReady(() => { OCA.Text = { RichWorkspaceEnabled: loadState('text', 'workspace_available'), + OpenReadOnlyEnabled: loadState('text', 'open_read_only_enabled'), } From f1990226720b3c6caa444ad8f27cae463c0506eb Mon Sep 17 00:00:00 2001 From: Benjamin Thiemann Date: Tue, 13 Feb 2024 16:06:43 +0100 Subject: [PATCH 04/12] components: Add menu entries for open read-only mode Signed-off-by: Benjamin Thiemann --- src/components/Menu/entries.js | 22 +++++++++++++++++-- .../Suggestion/LinkPicker/suggestions.js | 12 +++++----- src/components/icons.js | 4 ++++ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/components/Menu/entries.js b/src/components/Menu/entries.js index e6896af587d..4833f27543b 100644 --- a/src/components/Menu/entries.js +++ b/src/components/Menu/entries.js @@ -30,6 +30,8 @@ import { Info, LinkIcon, Paperclip, + Pencil, + PencilOff, Positive, Table, UnfoldMoreHorizontal, @@ -41,7 +43,7 @@ import ActionInsertLink from './ActionInsertLink.vue' import { MODIFIERS } from './keys.js' -export const ReadonlyEntries = [{ +export const OutlineEntries = [{ key: 'outline', forceLabel: true, icon: FormatListBulleted, @@ -53,7 +55,23 @@ export const ReadonlyEntries = [{ }, }] -export default [ +export const ReadOnlyEditEntries = [{ + key: 'edit', + label: t('text', 'Edit'), + forceLabel: true, + icon: Pencil, + click: ({ $readOnlyActions }) => $readOnlyActions.toggle(), +}] + +export const ReadOnlyDoneEntries = [{ + key: 'done', + label: t('text', 'Done'), + keyChar: 'esc', + icon: PencilOff, + click: ({ $readOnlyActions }) => $readOnlyActions.toggle(), +}] + +export const MenuEntries = [ { key: 'undo', label: t('text', 'Undo'), diff --git a/src/components/Suggestion/LinkPicker/suggestions.js b/src/components/Suggestion/LinkPicker/suggestions.js index 14371dee6e2..caecc6ac9b3 100644 --- a/src/components/Suggestion/LinkPicker/suggestions.js +++ b/src/components/Suggestion/LinkPicker/suggestions.js @@ -6,7 +6,7 @@ import createSuggestions from '../suggestions.js' import LinkPickerList from './LinkPickerList.vue' import { searchProvider, getLinkWithPicker } from '@nextcloud/vue/dist/Components/NcRichText.js' -import menuEntries from './../../Menu/entries.js' +import { MenuEntries } from './../../Menu/entries.js' import { getIsActive } from '../../Menu/utils.js' import markdownit from '../../../markdownit/index.js' import shouldInterpretAsMarkdown from '../../../markdownit/shouldInterpretAsMarkdown.js' @@ -38,12 +38,12 @@ const sortImportantFirst = (list) => { const formattingSuggestions = (query) => { return sortImportantFirst( [ - ...menuEntries.find(e => e.key === 'headings').children, - ...menuEntries.find(e => e.key === 'lists').children, - ...menuEntries.filter(e => e.action && !filterOut(e)), - ...menuEntries.find(e => e.key === 'blocks').children, + ...MenuEntries.find(e => e.key === 'headings').children, + ...MenuEntries.find(e => e.key === 'lists').children, + ...MenuEntries.filter(e => e.action && !filterOut(e)), + ...MenuEntries.find(e => e.key === 'blocks').children, { - ...menuEntries.find(e => e.key === 'emoji-picker'), + ...MenuEntries.find(e => e.key === 'emoji-picker'), action: (command) => command.insertContent(':'), }, ].filter(e => e?.label?.toLowerCase?.()?.includes(query.toLowerCase())) diff --git a/src/components/icons.js b/src/components/icons.js index 54f31b8096e..6ed21e39f1b 100644 --- a/src/components/icons.js +++ b/src/components/icons.js @@ -46,6 +46,8 @@ import MDI_LinkVariantPlus from 'vue-material-design-icons/LinkVariantPlus.vue' import MDI_Loading from 'vue-material-design-icons/Loading.vue' import MDI_Lock from 'vue-material-design-icons/Lock.vue' import MDI_Paperclip from 'vue-material-design-icons/Paperclip.vue' +import MDI_Pencil from 'vue-material-design-icons/Pencil.vue' +import MDI_PencilOff from 'vue-material-design-icons/PencilOff.vue' import MDI_Positive from 'vue-material-design-icons/CheckboxMarkedCircle.vue' import MDI_Redo from 'vue-material-design-icons/ArrowURightTop.vue' import MDI_Shape from 'vue-material-design-icons/Shape.vue' @@ -128,6 +130,8 @@ export const LinkOff = makeIcon(MDI_LinkOff) export const LinkVariantPlus = makeIcon(MDI_LinkVariantPlus) export const Lock = makeIcon(MDI_Lock) export const Paperclip = makeIcon(MDI_Paperclip) +export const Pencil = makeIcon(MDI_Pencil) +export const PencilOff = makeIcon(MDI_PencilOff) export const Positive = makeIcon(MDI_Positive) export const Redo = makeIcon(MDI_Redo) export const Shape = makeIcon(MDI_Shape) From c7f0c2932980e71d8c94fc4f155dbdf33852a08a Mon Sep 17 00:00:00 2001 From: Benjamin Thiemann Date: Mon, 26 Feb 2024 14:48:22 +0100 Subject: [PATCH 05/12] components: Add actions for open read-only mode Signed-off-by: Benjamin Thiemann --- src/components/Editor.vue | 25 ++++++++++++++++++----- src/components/Editor/Wrapper.provider.js | 12 +++++++++++ src/components/Editor/Wrapper.vue | 10 ++++++++- src/components/Menu/BaseActionEntry.js | 11 ++++++++-- src/components/Menu/MenuBar.vue | 8 ++++++-- src/components/Menu/ReadonlyBar.vue | 10 +++++++-- 6 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/components/Editor.vue b/src/components/Editor.vue index 01d02ba583a..76893fca873 100644 --- a/src/components/Editor.vue +++ b/src/components/Editor.vue @@ -24,12 +24,13 @@ :has-connection-issue="hasConnectionIssue" :content-loaded="contentLoaded" :show-outline-outside="showOutlineOutside" + @read-only-toggled="readOnlyToggled" @outline-toggled="outlineToggled"> -
+
- + { this.emit('sync-service:idle') @@ -815,6 +822,14 @@ export default { this.emit('outline-toggled', visible) }, + readOnlyToggled() { + if (this.editMode) { + this.$syncService.save() + } + this.editMode = !this.editMode + this.$editor.setEditable(this.editMode) + }, + onKeyDown(event) { if (event.key === 'Escape') { event.preventDefault() diff --git a/src/components/Editor/Wrapper.provider.js b/src/components/Editor/Wrapper.provider.js index 0626e7c4867..56a231bf490 100644 --- a/src/components/Editor/Wrapper.provider.js +++ b/src/components/Editor/Wrapper.provider.js @@ -5,6 +5,7 @@ export const OUTLINE_STATE = Symbol('wrapper:outline-state') export const OUTLINE_ACTIONS = Symbol('wrapper:outline-actions') +export const READ_ONLY_ACTIONS = Symbol('wrapper:read-only-actions') export const useOutlineStateMixin = { inject: { @@ -28,3 +29,14 @@ export const useOutlineActions = { }, }, } + +export const useReadOnlyActions = { + inject: { + $readOnlyActions: { + from: READ_ONLY_ACTIONS, + default: { + toggle: () => {}, + }, + }, + }, +} diff --git a/src/components/Editor/Wrapper.vue b/src/components/Editor/Wrapper.vue index d81e8900429..01a03c53e5f 100644 --- a/src/components/Editor/Wrapper.vue +++ b/src/components/Editor/Wrapper.vue @@ -16,7 +16,7 @@