From e0103bc84a699dde98c51c03b8526d2f3b3d1307 Mon Sep 17 00:00:00 2001 From: Benjamin Thiemann Date: Mon, 12 Feb 2024 11:02:48 +0100 Subject: [PATCH 1/8] 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 c9dddac2e23..8bd33eeeccc 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,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 378ea453982b7ac2e9d31652cd17e9c653015064 Mon Sep 17 00:00:00 2001 From: Benjamin Thiemann Date: Tue, 24 Oct 2023 07:55:50 +0200 Subject: [PATCH 2/8] 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 b4499c7f196..9b0837121a9 100644 --- a/lib/Service/ConfigService.php +++ b/lib/Service/ConfigService.php @@ -17,6 +17,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 0471b05a662..5f91228f36f 100644 --- a/lib/Service/InitialStateProvider.php +++ b/lib/Service/InitialStateProvider.php @@ -28,6 +28,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 726f278775d4715ea26c919a08532f6015a44a84 Mon Sep 17 00:00:00 2001 From: Benjamin Thiemann Date: Tue, 24 Oct 2023 07:56:05 +0200 Subject: [PATCH 3/8] 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 67cadf325ab..2fa636e793a 100644 --- a/src/files.js +++ b/src/files.js @@ -29,6 +29,7 @@ __webpack_public_path__ = linkTo('text', 'js/') // eslint-disable-line 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') { @@ -58,4 +59,5 @@ document.addEventListener('DOMContentLoaded', async () => { OCA.Text = { RichWorkspaceEnabled: workspaceEnabled, + OpenReadOnlyEnabled: openReadOnlyEnabled, } diff --git a/src/public.js b/src/public.js index 803a26495ed..e799850d733 100644 --- a/src/public.js +++ b/src/public.js @@ -157,4 +157,5 @@ documentReady(() => { OCA.Text = { RichWorkspaceEnabled: loadState('text', 'workspace_available'), + OpenReadOnlyEnabled: loadState('text', 'open_read_only_enabled'), } From 3bf84e3c116e7341e7eeb4b1930d8b24fe9cfce5 Mon Sep 17 00:00:00 2001 From: Benjamin Thiemann Date: Tue, 13 Feb 2024 16:06:43 +0100 Subject: [PATCH 4/8] components: Add menu entries for open read-only mode Signed-off-by: Benjamin Thiemann --- src/components/Menu/entries.js | 22 +++++++++++++++++-- .../Suggestion/LinkPicker/suggestions.js | 10 ++++----- src/components/icons.js | 4 ++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/components/Menu/entries.js b/src/components/Menu/entries.js index bfa0dd4ebcf..4ccfd772a8d 100644 --- a/src/components/Menu/entries.js +++ b/src/components/Menu/entries.js @@ -44,6 +44,8 @@ import { Images, Info, LinkIcon, + Pencil, + PencilOff, Positive, Table, Warn, @@ -54,7 +56,7 @@ import ActionInsertLink from './ActionInsertLink.vue' import { MODIFIERS } from './keys.js' -export const ReadonlyEntries = [{ +export const OutlineEntries = [{ key: 'outline', forceLabel: true, icon: FormatListBulleted, @@ -66,7 +68,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 702ea3144b5..cc9442826ca 100644 --- a/src/components/Suggestion/LinkPicker/suggestions.js +++ b/src/components/Suggestion/LinkPicker/suggestions.js @@ -22,7 +22,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' @@ -70,11 +70,11 @@ const sortImportantFirst = (list) => { const formattingSuggestions = (query) => { return sortImportantFirst( [ - ...menuEntries.find(e => e.key === 'headings').children, - ...menuEntries.filter(e => e.action && !filterOut(e)), - ...menuEntries.find(e => e.key === 'callouts').children, + ...MenuEntries.find(e => e.key === 'headings').children, + ...MenuEntries.filter(e => e.action && !filterOut(e)), + ...MenuEntries.find(e => e.key === 'callouts').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 b9d5a5bd048..df5c6616355 100644 --- a/src/components/icons.js +++ b/src/components/icons.js @@ -58,6 +58,8 @@ import MDI_LinkOff from 'vue-material-design-icons/LinkOff.vue' 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_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' @@ -135,6 +137,8 @@ export const LinkIcon = makeIcon(MDI_Link) export const LinkOff = makeIcon(MDI_LinkOff) export const LinkVariantPlus = makeIcon(MDI_LinkVariantPlus) export const Lock = makeIcon(MDI_Lock) +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 d1bb0ae352424a4bdf8dad641dd7c0a5943d2066 Mon Sep 17 00:00:00 2001 From: Benjamin Thiemann Date: Mon, 26 Feb 2024 14:48:22 +0100 Subject: [PATCH 5/8] 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 66d565bf4a1..83a40e92f00 100644 --- a/src/components/Editor.vue +++ b/src/components/Editor.vue @@ -40,12 +40,13 @@ :content-loaded="contentLoaded" :show-author-annotations="showAuthorAnnotations" :show-outline-outside="showOutlineOutside" + @read-only-toggled="readOnlyToggled" @outline-toggled="outlineToggled"> -
+
- + { this.emit('sync-service:idle') @@ -734,6 +741,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 b8b1c29f389..19bcceedd8e 100644 --- a/src/components/Editor/Wrapper.provider.js +++ b/src/components/Editor/Wrapper.provider.js @@ -1,5 +1,6 @@ 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: { @@ -23,3 +24,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 7e717f1a670..3c590a89031 100644 --- a/src/components/Editor/Wrapper.vue +++ b/src/components/Editor/Wrapper.vue @@ -35,7 +35,7 @@