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

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
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/markdown', '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 @@ export default {
hasConnectionIssue: false,
hasEditor: false,
readOnly: true,
openReadOnlyEnabled: OCA.Text.OpenReadOnlyEnabled,
editMode: true,
forceRecreate: false,
menubarLoaded: false,
draggedOver: false,
Expand Down Expand Up @@ -508,8 +512,10 @@ export default {
this.currentSession = session
this.document = document
this.readOnly = document.readOnly
this.editMode = !document.readOnly && !this.openReadOnlyEnabled

if (this.$editor) {
this.$editor.setEditable(!this.readOnly)
this.$editor.setEditable(this.editMode)
}
this.lock = this.$syncService.lock
localStorage.setItem('nick', this.currentSession.guestName)
Expand Down Expand Up @@ -586,7 +592,7 @@ export default {
this.document = document

this.syncError = null
const editable = !this.readOnly && !this.hasConnectionIssue
const editable = this.editMode && !this.hasConnectionIssue
if (this.$editor.isEditable !== editable) {
this.$editor.setEditable(editable)
}
Expand Down Expand Up @@ -682,7 +688,8 @@ export default {
this.$syncService.close()
this.idle = true
this.readOnly = true
this.$editor.setEditable(!this.readOnly)
this.editMode = false
this.$editor.setEditable(this.editMode)

this.$nextTick(() => {
this.emit('sync-service:idle')
Expand Down Expand Up @@ -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()
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'
import useStore from '../../mixins/store.js'
import { mapState } from 'vuex'

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

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

}
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 ActionSingle from './ActionSingle.vue'
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'
import { MENU_ID } from './MenuBar.provider.js'
import { DotsHorizontal, TranslateVariant } from '../icons.js'
import {
Expand Down Expand Up @@ -116,6 +116,10 @@ export default {
type: Boolean,
default: false,
},
openReadOnly: {
type: Boolean,
default: false,
},
},

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

data() {
return {
entries: [...actionsFullEntries],
entries: this.openReadOnly ? [...ReadOnlyDoneEntries, ...MenuEntries] : [...MenuEntries],
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'

import ActionList from './ActionList.vue'
import ActionSingle from './ActionSingle.vue'
Expand All @@ -38,9 +38,15 @@ export default defineComponent({
ActionSingle,
},
extends: ToolBarLogic,
props: {
openReadOnly: {
type: Boolean,
default: false,
},
},
data() {
return {
entries,
entries: this.openReadOnly ? [...ReadOnlyEditEntries, ...OutlineEntries] : [...OutlineEntries],
}
},
})
Expand Down
Loading
Loading