mirror of
https://github.com/nextcloud/richdocuments.git
synced 2025-12-17 21:12:14 +01:00
Add local editing button to Nextcloud Office
Squashed commits: - Fix CSP tests - Update logic on opening file locally to prevent race condition with collabora post message API - Unlock file before opening locally - Add confirmation dialog - Toolbar button: open file locally Signed-off-by: raul <raul@nextcloud.com> Signed-off-by: Raul <r.ferreira.fuentes@gmail.com>
This commit is contained in:
parent
277c4e3895
commit
e927907c48
5 changed files with 94 additions and 9 deletions
4
img/launch.svg
Normal file
4
img/launch.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 270 B |
|
|
@ -68,6 +68,7 @@ class CSPListener implements IEventListener {
|
|||
|
||||
$policy = new EmptyContentSecurityPolicy();
|
||||
$policy->addAllowedFrameDomain("'self'");
|
||||
$policy->addAllowedFrameDomain("nc:");
|
||||
|
||||
foreach ($urls as $url) {
|
||||
$policy->addAllowedFrameDomain($url);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { emit } from '@nextcloud/event-bus'
|
||||
import { getRootUrl } from '@nextcloud/router'
|
||||
import { getRootUrl, imagePath } from '@nextcloud/router'
|
||||
import { getRequestToken } from '@nextcloud/auth'
|
||||
import Config from './services/config.tsx'
|
||||
import { setGuestName, shouldAskForGuestName } from './helpers/guestName'
|
||||
|
|
@ -11,9 +11,10 @@ import {
|
|||
isDirectEditing,
|
||||
isMobileInterfaceAvailable,
|
||||
} from './helpers/mobile'
|
||||
import { getWopiUrl, getSearchParam } from './helpers/url'
|
||||
import { getWopiUrl, getSearchParam, getNextcloudUrl } from './helpers/url'
|
||||
|
||||
import '../css/document.scss'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
const PostMessages = new PostMessageService({
|
||||
parent: window.parent,
|
||||
|
|
@ -307,6 +308,15 @@ const documentsMain = {
|
|||
|
||||
if (Config.get('userId') === null) {
|
||||
PostMessages.sendWOPIPostMessage('loolframe', 'Hide_Menu_Item', { id: 'insertgraphicremote' })
|
||||
} else {
|
||||
PostMessages.sendWOPIPostMessage('loolframe', 'Insert_Button', {
|
||||
id: 'Open_Local_Editor',
|
||||
imgurl: window.location.protocol + '//' + getNextcloudUrl() + imagePath('richdocuments', 'launch.svg'),
|
||||
mobile: false,
|
||||
label: t('richdocuments', 'Open in local editor'),
|
||||
hint: t('richdocuments', 'Open in local editor'),
|
||||
insertBefore: 'undo',
|
||||
})
|
||||
}
|
||||
|
||||
emit('richdocuments:wopi-load:succeeded', {
|
||||
|
|
@ -442,6 +452,23 @@ const documentsMain = {
|
|||
case 'File_Rename':
|
||||
documentsMain.fileName = args.NewName
|
||||
break
|
||||
case 'Clicked_Button':
|
||||
if (parsed.args.Id === 'Open_Local_Editor') {
|
||||
documentsMain.UI.initiateOpenLocally()
|
||||
} else {
|
||||
console.debug('[document] Unhandled `Clicked_Button` post message', parsed)
|
||||
}
|
||||
break
|
||||
case 'Get_Views_Resp':
|
||||
if (documentsMain.openingLocally) {
|
||||
documentsMain.UI.removeViews(parsed.args)
|
||||
documentsMain.unlockFile()
|
||||
.catch(_ => {}) // Unlocking failed, possibly because file was not locked, we want to proceed regardless.
|
||||
.then(() => {
|
||||
documentsMain.openLocally()
|
||||
})
|
||||
}
|
||||
break
|
||||
default:
|
||||
console.debug('[document] Unhandled post message', parsed)
|
||||
}
|
||||
|
|
@ -500,6 +527,53 @@ const documentsMain = {
|
|||
$(document.body).removeClass('claro')
|
||||
})
|
||||
},
|
||||
|
||||
initiateOpenLocally() {
|
||||
OC.dialogs.confirmDestructive(
|
||||
t('richdocuments', 'When opening a file locally, the document will close for all users currently viewing the document.'),
|
||||
t('richdocuments', 'Open file locally'),
|
||||
{
|
||||
type: OC.dialogs.YES_NO_BUTTONS,
|
||||
confirm: t('richdocuments', 'Open locally'),
|
||||
confirmClasses: 'error',
|
||||
cancel: t('richdocuments', 'Continue editing online'),
|
||||
},
|
||||
(decision) => {
|
||||
if (!decision) {
|
||||
return
|
||||
}
|
||||
documentsMain.openingLocally = true
|
||||
PostMessages.sendWOPIPostMessage('loolframe', 'Get_Views')
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
removeViews(views) {
|
||||
PostMessages.sendWOPIPostMessage('loolframe', 'Action_Save', {
|
||||
DontTerminateEdit: false,
|
||||
DontSaveIfUnmodified: false,
|
||||
Notify: false,
|
||||
})
|
||||
|
||||
views.forEach((view) => {
|
||||
PostMessages.sendWOPIPostMessage('loolframe', 'Action_RemoveView', { ViewId: Number(view.ViewId) })
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
unlockFile() {
|
||||
const unlockUrl = getRootUrl() + '/index.php/apps/richdocuments/wopi/files/' + documentsMain.fileId
|
||||
const unlockConfig = {
|
||||
headers: { 'X-WOPI-Override': 'UNLOCK' }
|
||||
}
|
||||
return axios.post(unlockUrl, { access_token: documentsMain.token }, unlockConfig)
|
||||
},
|
||||
|
||||
openLocally() {
|
||||
if (documentsMain.openingLocally) {
|
||||
documentsMain.openingLocally = false
|
||||
window.location.href = 'nc://open/' + Config.get('userId') + '@' + getNextcloudUrl() + OC.encodePath(documentsMain.fullPath)
|
||||
}
|
||||
},
|
||||
|
||||
onStartup() {
|
||||
|
|
@ -560,6 +634,7 @@ const documentsMain = {
|
|||
|
||||
$('footer,nav').show()
|
||||
documentsMain.UI.hideEditor()
|
||||
documentsMain.openLocally()
|
||||
|
||||
PostMessages.sendPostMessage('parent', 'close', '*')
|
||||
},
|
||||
|
|
|
|||
|
|
@ -86,6 +86,10 @@ const getDocumentUrlForFile = (fileDir, fileId) => {
|
|||
})
|
||||
}
|
||||
|
||||
const getNextcloudUrl = () => {
|
||||
return window.location.host + (window.location.port ? `:${window.location.port}` : '')
|
||||
}
|
||||
|
||||
export {
|
||||
getSearchParam,
|
||||
getWopiUrl,
|
||||
|
|
@ -93,4 +97,5 @@ export {
|
|||
getDocumentUrlFromTemplate,
|
||||
getDocumentUrlForPublicFile,
|
||||
getDocumentUrlForFile,
|
||||
getNextcloudUrl,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ class CSPListenerTest extends TestCase {
|
|||
|
||||
$policy = $this->getMergedPolicy();
|
||||
|
||||
self::assertEquals(["'self'", "http://public"], $policy->getAllowedFrameDomains());
|
||||
self::assertEquals(["'self'", "nc:" , "http://public"], $policy->getAllowedFrameDomains());
|
||||
self::assertEquals(["'self'", "http://public"], $policy->getAllowedFormActionDomains());
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ class CSPListenerTest extends TestCase {
|
|||
|
||||
$policy = $this->getMergedPolicy();
|
||||
|
||||
self::assertEquals(["'self'"], $policy->getAllowedFrameDomains());
|
||||
self::assertEquals(["'self'", "nc:"], $policy->getAllowedFrameDomains());
|
||||
self::assertEquals(["'self'"], $policy->getAllowedFormActionDomains());
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ class CSPListenerTest extends TestCase {
|
|||
|
||||
$policy = $this->getMergedPolicy();
|
||||
|
||||
self::assertEquals(["'self'", "http://public"], $policy->getAllowedFrameDomains());
|
||||
self::assertEquals(["'self'", "nc:", "http://public"], $policy->getAllowedFrameDomains());
|
||||
self::assertEquals(["'self'", "http://public"], $policy->getAllowedFormActionDomains());
|
||||
}
|
||||
|
||||
|
|
@ -147,7 +147,7 @@ class CSPListenerTest extends TestCase {
|
|||
|
||||
$policy = $this->getMergedPolicy();
|
||||
|
||||
self::assertEquals(["'self'", "https://public"], $policy->getAllowedFrameDomains());
|
||||
self::assertEquals(["'self'", "nc:", "https://public"], $policy->getAllowedFrameDomains());
|
||||
self::assertEquals(["'self'", "https://public"], $policy->getAllowedFormActionDomains());
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +165,7 @@ class CSPListenerTest extends TestCase {
|
|||
|
||||
$policy = $this->getMergedPolicy();
|
||||
|
||||
self::assertEquals(["'self'", "https://public", "*.example.com"], $policy->getAllowedFrameDomains());
|
||||
self::assertEquals(["'self'", "nc:", "https://public", "*.example.com"], $policy->getAllowedFrameDomains());
|
||||
self::assertEquals(["'self'", "https://public", "*.example.com"], $policy->getAllowedFormActionDomains());
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ class CSPListenerTest extends TestCase {
|
|||
|
||||
$policy = $this->getMergedPolicy();
|
||||
|
||||
self::assertEquals(["'self'", "http://internal"], $policy->getAllowedFrameDomains());
|
||||
self::assertEquals(["'self'", "nc:", "http://internal"], $policy->getAllowedFrameDomains());
|
||||
self::assertEquals(["'self'", "http://internal"], $policy->getAllowedFormActionDomains());
|
||||
}
|
||||
|
||||
|
|
@ -205,7 +205,7 @@ class CSPListenerTest extends TestCase {
|
|||
|
||||
$policy = $manager->getDefaultPolicy();
|
||||
|
||||
self::assertArrayUnordered(["'self'", "external.example.com", "http://public"], $policy->getAllowedFrameDomains(), "Domains are equal", 0.0, 10, true);
|
||||
self::assertArrayUnordered(["'self'", "external.example.com", "http://public", "nc:"], $policy->getAllowedFrameDomains(), "Domains are equal", 0.0, 10, true);
|
||||
self::assertArrayUnordered(["'self'", "external.example.com", "http://public"], $policy->getAllowedFormActionDomains());
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue