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

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/cypress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ jobs:
php occ user:add --password-from-env user2
php occ app:enable viewer
php occ app:enable text
php occ app:enable testing
php occ app:list
php occ background:cron
php occ config:system:set session_keepalive --value=false --type=boolean
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
104 changes: 104 additions & 0 deletions cypress/e2e/openreadonly.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
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() {

const setReadOnlyMode = function(mode) {
cy.login(admin)
cy.ocsRequest({
method: 'POST',
url: `${Cypress.env('baseUrl')}/ocs/v2.php/apps/testing/api/v1/app/text/open_read_only_enabled`,
body: { value: mode },
}).then(response => {
cy.log(response.status)
})
cy.logout()
}

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

beforeEach(function() {
setReadOnlyMode(0)

cy.createUser(user)
cy.login(user)

cy.uploadFile('test.md', 'text/markdown')
cy.uploadFile('test.md', 'text/markdown', 'test.txt')

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')
}

beforeEach(function() {
setReadOnlyMode(1)

cy.createUser(user)
cy.login(user)

cy.removeFile('test.md')
cy.removeFile('test.txt')

cy.uploadFile('test.md', 'text/markdown')
cy.uploadFile('test.md', 'text/plain', 'test.txt')

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')
})
})
})
4 changes: 4 additions & 0 deletions lib/Service/ConfigService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down
5 changes: 5 additions & 0 deletions lib/Service/InitialStateProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
25 changes: 20 additions & 5 deletions src/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@
:content-loaded="contentLoaded"
:show-author-annotations="showAuthorAnnotations"
: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 @@ -59,6 +60,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 @@ -244,6 +246,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 @@ -470,8 +474,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 @@ -558,7 +564,7 @@ export default {
this.document = document

this.syncError = null
this.$editor.setEditable(!this.readOnly)
this.$editor.setEditable(this.editMode)
},

onSync({ steps, document }) {
Expand Down Expand Up @@ -627,7 +633,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 @@ -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()
Expand Down
12 changes: 12 additions & 0 deletions src/components/Editor/Wrapper.provider.js
Original file line number Diff line number Diff line change
@@ -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: {
Expand All @@ -23,3 +24,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 @@ -35,7 +35,7 @@
<script>
import { ERROR_TYPE } from './../../services/SyncService.js'
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 @@ -54,6 +54,11 @@ export default {
toggle: this.outlineToggle,
}),
},
[READ_ONLY_ACTIONS]: {
get: () => ({
toggle: this.readOnlyToggle,
}),
},
})

return val
Expand Down Expand Up @@ -133,6 +138,9 @@ export default {
this.outline.visible = !this.outline.visible
this.$emit('outline-toggled', this.outline.visible)
},
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 @@ -25,7 +25,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 @@ -35,7 +35,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 @@ -93,7 +93,7 @@ import CharacterCount from './CharacterCount.vue'
import HelpModal from '../HelpModal.vue'
import ToolBarLogic from './ToolBarLogic.js'
import Translate from './../Modal/Translate.vue'
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 @@ -139,10 +139,14 @@ export default {
type: Boolean,
default: false,
},
openReadOnly: {
type: Boolean,
default: false,
},
},
data() {
return {
entries: [...actionsFullEntries],
entries: this.openReadOnly ? [...ReadOnlyDoneEntries, ...MenuEntries] : [...MenuEntries],
randomID: `menu-bar-${(Math.ceil((Math.random() * 10000) + 500)).toString(16)}`,
displayHelp: false,
displayTranslate: 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 @@ -20,7 +20,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 @@ -33,9 +33,15 @@ export default defineComponent({
ActionSingle,
},
extends: ToolBarLogic,
props: {
openReadOnly: {
type: Boolean,
default: false,
},
},
data() {
return {
entries,
entries: this.openReadOnly ? [...ReadOnlyEditEntries, ...OutlineEntries] : [...OutlineEntries],
}
},
})
Expand Down
22 changes: 20 additions & 2 deletions src/components/Menu/entries.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import {
Images,
Info,
LinkIcon,
Pencil,
PencilOff,
Positive,
Table,
Warn,
Expand All @@ -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,
Expand All @@ -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'),
Expand Down
Loading
Loading