Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/read only setting #4902

Merged
merged 12 commits into from
Dec 16, 2024
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
101 changes: 101 additions & 0 deletions cypress/e2e/openreadonly.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { User } from '@nextcloud/cypress'
import { randUser } from '../utils/index.js'

const admin = new User('admin', 'admin')
const user = randUser()

describe('Open read-only mode', function() {

before(function() {
cy.createUser(user)
cy.login(user)
cy.uploadFile('test.md', 'text/markdown')
cy.uploadFile('test.md', 'text/plain', 'test.txt')
})

const setReadOnlyMode = function(mode) {
cy.login(admin)
cy.setAppConfig('open_read_only_enabled', mode)
}

describe('Disabled', function() {
const checkMenubar = function() {
cy.get('.text-editor--readonly-bar').should('not.exist')
cy.get('.text-menubar', { timeout: 10000 })
.getActionEntry('done').should('not.exist')
}

before(function() {
setReadOnlyMode(0)
})

beforeEach(function() {
cy.login(user)
cy.visit('/apps/files')
})

it('Test writable markdown file', function() {
cy.openFile('test.md')
checkMenubar()
})

it('Test writable text file', function() {
cy.openFile('test.txt')
checkMenubar()
})
})

describe('Enabled', function() {
const requireReadOnlyBar = function() {
cy.get('.text-editor--readonly-bar').should('exist')
cy.get('.text-editor--readonly-bar').getActionEntry('edit').should('exist')
}

const requireMenubar = function() {
cy.get('.text-editor--readonly-bar').should('not.exist')
cy.get('.text-menubar').getActionEntry('done').should('exist')
}

before(function() {
setReadOnlyMode(1)
})

beforeEach(function() {
cy.login(user)
cy.visit('/apps/files')
})

it('Test read-only markdown file', function() {
cy.openFile('test.md')

requireReadOnlyBar()

// Switch to edit-mode
cy.get('.text-editor--readonly-bar').getActionEntry('edit').click()

requireMenubar()

// Switch to read-only mode
cy.get('.text-menubar').getActionEntry('done').click()

requireReadOnlyBar()
})

it('Test read-only text file', function() {
cy.openFile('test.txt')

requireReadOnlyBar()

// Switch to edit-mode
cy.get('.text-editor--readonly-bar').getActionEntry('edit').click()

// Check that read-only bar does not exist
cy.get('.text-editor--readonly-bar').should('not.exist')
})
})
})
8 changes: 8 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,14 @@ Cypress.Commands.add('showHiddenFiles', (value = true) => {
)
})

Cypress.Commands.add('setAppConfig', (key, value) => {
Cypress.log()
return axios.post(
`${url}/ocs/v2.php/apps/testing/api/v1/app/text/${key}`,
{ value },
)
})

Cypress.Commands.add('createDescription', (buttonLabel = 'Add folder description') => {
const url = '**/remote.php/dav/files/**'
cy.intercept({ method: 'PUT', url })
Expand Down
4 changes: 4 additions & 0 deletions lib/Service/ConfigService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down
5 changes: 5 additions & 0 deletions lib/Service/InitialStateProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
25 changes: 20 additions & 5 deletions src/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@
:has-connection-issue="hasConnectionIssue"
:content-loaded="contentLoaded"
:show-outline-outside="showOutlineOutside"
@read-only-toggled="readOnlyToggled"
@outline-toggled="outlineToggled">
<MainContainer v-if="hasEditor">
<!-- Readonly -->
<div v-if="readOnly" class="text-editor--readonly-bar">
<div v-if="readOnly || (openReadOnlyEnabled && !editMode)" class="text-editor--readonly-bar">
<slot name="readonlyBar">
<ReadonlyBar>
<ReadonlyBar :open-read-only="openReadOnlyEnabled">
<Status :document="document"
:dirty="dirty"
:sessions="filteredSessions"
Expand All @@ -43,6 +44,7 @@
<MenuBar v-if="renderMenus"
ref="menubar"
:is-hidden="hideMenu"
:open-read-only="openReadOnlyEnabled"
:loaded.sync="menubarLoaded">
<Status :document="document"
:dirty="dirty"
Expand Down Expand Up @@ -260,6 +262,8 @@
hasConnectionIssue: false,
hasEditor: false,
readOnly: true,
openReadOnlyEnabled: OCA.Text.OpenReadOnlyEnabled,
editMode: true,

Check warning on line 266 in src/components/Editor.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor.vue#L265-L266

Added lines #L265 - L266 were not covered by tests
forceRecreate: false,
menubarLoaded: false,
draggedOver: false,
Expand Down Expand Up @@ -508,8 +512,10 @@
this.currentSession = session
this.document = document
this.readOnly = document.readOnly
this.editMode = !document.readOnly && !this.openReadOnlyEnabled

Check warning on line 515 in src/components/Editor.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor.vue#L515

Added line #L515 was not covered by tests

if (this.$editor) {
this.$editor.setEditable(!this.readOnly)
this.$editor.setEditable(this.editMode)

Check warning on line 518 in src/components/Editor.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor.vue#L518

Added line #L518 was not covered by tests
}
this.lock = this.$syncService.lock
localStorage.setItem('nick', this.currentSession.guestName)
Expand Down Expand Up @@ -586,7 +592,7 @@
this.document = document

this.syncError = null
const editable = !this.readOnly && !this.hasConnectionIssue
const editable = this.editMode && !this.hasConnectionIssue

Check warning on line 595 in src/components/Editor.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor.vue#L595

Added line #L595 was not covered by tests
if (this.$editor.isEditable !== editable) {
this.$editor.setEditable(editable)
}
Expand Down Expand Up @@ -682,7 +688,8 @@
this.$syncService.close()
this.idle = true
this.readOnly = true
this.$editor.setEditable(!this.readOnly)
this.editMode = false
this.$editor.setEditable(this.editMode)

Check warning on line 692 in src/components/Editor.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor.vue#L691-L692

Added lines #L691 - L692 were not covered by tests

this.$nextTick(() => {
this.emit('sync-service:idle')
Expand Down Expand Up @@ -815,6 +822,14 @@
this.emit('outline-toggled', visible)
},

readOnlyToggled() {
if (this.editMode) {
this.$syncService.save()
}
this.editMode = !this.editMode
this.$editor.setEditable(this.editMode)
},

Check warning on line 831 in src/components/Editor.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor.vue#L825-L831

Added lines #L825 - L831 were not covered by tests

onKeyDown(event) {
if (event.key === 'Escape') {
event.preventDefault()
Expand Down
12 changes: 12 additions & 0 deletions src/components/Editor/Wrapper.provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -28,3 +29,14 @@ export const useOutlineActions = {
},
},
}

export const useReadOnlyActions = {
inject: {
$readOnlyActions: {
from: READ_ONLY_ACTIONS,
default: {
toggle: () => {},
},
},
},
}
10 changes: 9 additions & 1 deletion src/components/Editor/Wrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<script>
import { useIsRichEditorMixin, useIsRichWorkspaceMixin } from './../Editor.provider.js'
import { OUTLINE_STATE, OUTLINE_ACTIONS } from './Wrapper.provider.js'
import { OUTLINE_STATE, OUTLINE_ACTIONS, READ_ONLY_ACTIONS } from './Wrapper.provider.js'

Check warning on line 19 in src/components/Editor/Wrapper.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/Wrapper.vue#L19

Added line #L19 was not covered by tests
import useStore from '../../mixins/store.js'
import { mapState } from 'vuex'

Expand All @@ -35,6 +35,11 @@
toggle: this.outlineToggle,
}),
},
[READ_ONLY_ACTIONS]: {
get: () => ({
toggle: this.readOnlyToggle,
}),
},

Check warning on line 42 in src/components/Editor/Wrapper.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/Wrapper.vue#L38-L42

Added lines #L38 - L42 were not covered by tests
})

return val
Expand Down Expand Up @@ -116,6 +121,9 @@
this.outlineToggle()
}
},
readOnlyToggle() {
this.$emit('read-only-toggled')
},

Check warning on line 126 in src/components/Editor/Wrapper.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Editor/Wrapper.vue#L124-L126

Added lines #L124 - L126 were not covered by tests
},

}
Expand Down
11 changes: 9 additions & 2 deletions src/components/Menu/BaseActionEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import debounce from 'debounce'

import { useEditorMixin, useIsMobileMixin } from '../Editor.provider.js'
import { useOutlineActions, useOutlineStateMixin } from '../Editor/Wrapper.provider.js'
import { useOutlineActions, useOutlineStateMixin, useReadOnlyActions } from '../Editor/Wrapper.provider.js'
import { getActionState, getKeys, getKeyshortcuts } from './utils.js'
import useStore from '../../mixins/store.js'

Expand All @@ -18,7 +18,14 @@ import './ActionEntry.scss'
* @type {import("vue").ComponentOptions} BaseActionEntry
*/
const BaseActionEntry = {
mixins: [useEditorMixin, useIsMobileMixin, useStore, useOutlineActions, useOutlineStateMixin],
mixins: [
useEditorMixin,
useIsMobileMixin,
useStore,
useOutlineActions,
useOutlineStateMixin,
useReadOnlyActions,
],
props: {
actionEntry: {
type: Object,
Expand Down
8 changes: 6 additions & 2 deletions src/components/Menu/MenuBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
import CharacterCount from './CharacterCount.vue'
import HelpModal from '../HelpModal.vue'
import ToolBarLogic from './ToolBarLogic.js'
import actionsFullEntries from './entries.js'
import { ReadOnlyDoneEntries, MenuEntries } from './entries.js'

Check warning on line 74 in src/components/Menu/MenuBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Menu/MenuBar.vue#L74

Added line #L74 was not covered by tests
import { MENU_ID } from './MenuBar.provider.js'
import { DotsHorizontal, TranslateVariant } from '../icons.js'
import {
Expand Down Expand Up @@ -116,6 +116,10 @@
type: Boolean,
default: false,
},
openReadOnly: {
type: Boolean,
default: false,
},

Check warning on line 122 in src/components/Menu/MenuBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Menu/MenuBar.vue#L119-L122

Added lines #L119 - L122 were not covered by tests
},

setup() {
Expand All @@ -126,7 +130,7 @@

data() {
return {
entries: [...actionsFullEntries],
entries: this.openReadOnly ? [...ReadOnlyDoneEntries, ...MenuEntries] : [...MenuEntries],

Check warning on line 133 in src/components/Menu/MenuBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Menu/MenuBar.vue#L133

Added line #L133 was not covered by tests
randomID: `menu-bar-${(Math.ceil((Math.random() * 10000) + 500)).toString(16)}`,
displayHelp: false,
isReady: false,
Expand Down
10 changes: 8 additions & 2 deletions src/components/Menu/ReadonlyBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

<script>
import { defineComponent } from 'vue'
import { ReadonlyEntries as entries } from './entries.js'
import { ReadOnlyEditEntries, OutlineEntries } from './entries.js'

Check warning on line 28 in src/components/Menu/ReadonlyBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Menu/ReadonlyBar.vue#L28

Added line #L28 was not covered by tests

import ActionList from './ActionList.vue'
import ActionSingle from './ActionSingle.vue'
Expand All @@ -38,9 +38,15 @@
ActionSingle,
},
extends: ToolBarLogic,
props: {
openReadOnly: {
type: Boolean,
default: false,
},
},

Check warning on line 46 in src/components/Menu/ReadonlyBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Menu/ReadonlyBar.vue#L41-L46

Added lines #L41 - L46 were not covered by tests
data() {
return {
entries,
entries: this.openReadOnly ? [...ReadOnlyEditEntries, ...OutlineEntries] : [...OutlineEntries],

Check warning on line 49 in src/components/Menu/ReadonlyBar.vue

View check run for this annotation

Codecov / codecov/patch

src/components/Menu/ReadonlyBar.vue#L49

Added line #L49 was not covered by tests
}
},
})
Expand Down
Loading
Loading