Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/text.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--appinfo/app.php2
-rw-r--r--appinfo/routes.php1
-rw-r--r--lib/AppInfo/Application.php12
-rw-r--r--lib/Controller/DirectSessionController.php141
-rw-r--r--lib/Controller/SessionController.php4
-rw-r--r--lib/Db/Session.php5
-rw-r--r--lib/DirectEditing/TextDirectEditor.php151
-rw-r--r--lib/DirectEditing/TextDocumentCreator.php59
-rw-r--r--lib/DirectEditing/TextDocumentTemplateCreator.php78
-rw-r--r--lib/Service/ApiService.php22
-rw-r--r--lib/Service/DocumentService.php63
-rw-r--r--lib/Service/SessionService.php40
-rw-r--r--src/components/EditorWrapper.vue33
-rw-r--r--src/components/FilesEditor.vue6
-rw-r--r--src/main.js21
-rw-r--r--src/services/SyncService.js53
-rw-r--r--src/views/DirectEditing.vue115
-rw-r--r--templates/main.php1
18 files changed, 718 insertions, 89 deletions
diff --git a/appinfo/app.php b/appinfo/app.php
index 85ed0d5af..4913f8aa4 100644
--- a/appinfo/app.php
+++ b/appinfo/app.php
@@ -38,3 +38,5 @@ $eventDispatcher->addListener('OCA\Files_Sharing::loadAdditionalScripts', functi
\OCP\Util::addScript('text', 'public');
\OCP\Util::addStyle('text', 'icons');
});
+
+$app = \OC::$server->query(Application::class);
diff --git a/appinfo/routes.php b/appinfo/routes.php
index 12c8330a1..903fdb95b 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -37,6 +37,7 @@ return [
['name' => 'PublicSession#fetch', 'url' => '/public/session/fetch', 'verb' => 'GET'],
['name' => 'PublicSession#sync', 'url' => '/public/session/sync', 'verb' => 'POST'],
['name' => 'PublicSession#push', 'url' => '/public/session/push', 'verb' => 'POST'],
+
['name' => 'PublicSession#close', 'url' => '/public/session/close', 'verb' => 'GET'],
],
'ocs' => [
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index 93ba04806..954a32e1a 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -24,7 +24,10 @@ declare(strict_types=1);
namespace OCA\Text\AppInfo;
+use OCA\Text\DirectEditing\TextDirectEditor;
use OCP\AppFramework\App;
+use OCP\DirectEditing\RegisterDirectEditorEvent;
+use OCP\EventDispatcher\IEventDispatcher;
class Application extends App {
@@ -36,9 +39,18 @@ class Application extends App {
* Application constructor.
*
* @param array $params
+ * @throws \OCP\AppFramework\QueryException
*/
public function __construct(array $params = []) {
parent::__construct(self::APP_NAME, $params);
+
+ $container = $this->getContainer();
+ /** @var IEventDispatcher $eventDispatcher */
+ $eventDispatcher = $this->getContainer()->getServer()->query(IEventDispatcher::class);
+ $eventDispatcher->addListener(RegisterDirectEditorEvent::class, function (RegisterDirectEditorEvent $event) use ($container) {
+ $editor = $container->query(TextDirectEditor::class);
+ $event->register($editor);
+ });
}
}
diff --git a/lib/Controller/DirectSessionController.php b/lib/Controller/DirectSessionController.php
new file mode 100644
index 000000000..aef43873e
--- /dev/null
+++ b/lib/Controller/DirectSessionController.php
@@ -0,0 +1,141 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Text\Controller;
+
+use OC\Authentication\Exceptions\InvalidTokenException;
+use OCA\Text\Service\ApiService;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Response;
+use OCP\AppFramework\PublicShareController;
+use OCP\DirectEditing\IManager;
+use OCP\ISession;
+use OCP\Share\Exceptions\ShareNotFound;
+use OCP\Share\IManager as ShareManager;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\IRequest;
+use OCP\Share\IShare;
+
+class DirectSessionController extends Controller {
+
+ /** @var ShareManager */
+ private $shareManager;
+
+ /** @var IShare */
+ private $share;
+
+ /** @var ApiService */
+ private $apiService;
+ /** @var IManager */
+ private $directManager;
+
+ public function __construct(string $appName, IRequest $request, ShareManager $shareManager, ApiService $apiService, IManager $directManager) {
+ parent::__construct($appName, $request);
+ $this->shareManager = $shareManager;
+ $this->apiService = $apiService;
+ $this->directManager = $directManager;
+ }
+
+ /**
+ * @PublicPage
+ */
+ public function create(string $token, string $file = null, $guestName = null, bool $forceRecreate = false): DataResponse {
+ try {
+ $tokenObject = $this->directManager->getToken($token);
+ $tokenObject->extend();
+ $tokenObject->useTokenScope();
+ $node = $tokenObject->getFile();
+ $node->touch();
+ return new DataResponse([
+ 'mtime' => $node->getMTime()
+ ]);
+ } catch (InvalidTokenException $e) {
+ return new DataResponse('error');
+ }
+ //return $this->apiService->create(null, $file, $token, $guestName, $forceRecreate);
+ }
+
+ /**
+ * @NoAdminRequired
+ * @PublicPage
+ */
+ public function fetch(int $documentId, string $sessionId, string $sessionToken): Response {
+ return $this->apiService->fetch($documentId, $sessionId, $sessionToken);
+ }
+
+ /**
+ * @NoAdminRequired
+ * @PublicPage
+ */
+ public function close(int $documentId, int $sessionId, string $sessionToken): DataResponse {
+ return $this->apiService->close($documentId, $sessionId, $sessionToken);
+ }
+
+ /**
+ * @NoAdminRequired
+ * @PublicPage
+ */
+ public function push(int $documentId, int $sessionId, string $sessionToken, int $version, array $steps, string $token): DataResponse {
+ return $this->apiService->push($documentId, $sessionId, $sessionToken, $version, $steps, $token);
+ }
+
+ /**
+ * @NoAdminRequired
+ * @PublicPage
+ */
+ public function sync(string $token, int $documentId, int $sessionId, string $sessionToken, int $version = 0, string $autosaveContent = null, bool $force = false, bool $manualSave = false): DataResponse {
+ return $this->apiService->sync($documentId, $sessionId, $sessionToken, $version, $autosaveContent, $force, $manualSave, $token);
+ }
+
+ /**
+ * @NoAdminRequired
+ * @PublicPage
+ */
+ public function updateSession(int $documentId, int $sessionId, string $sessionToken, string $guestName) {
+ return $this->apiService->updateSession($documentId, $sessionId, $sessionToken, $guestName);
+ }
+
+}
diff --git a/lib/Controller/SessionController.php b/lib/Controller/SessionController.php
index 28660cb77..ff95a6cd9 100644
--- a/lib/Controller/SessionController.php
+++ b/lib/Controller/SessionController.php
@@ -51,6 +51,7 @@ class SessionController extends Controller {
/**
* @NoAdminRequired
+ * @PublicPage
*/
public function fetch(int $documentId, int $sessionId, string $sessionToken): Response {
return $this->apiService->fetch($documentId, $sessionId, $sessionToken);
@@ -58,6 +59,7 @@ class SessionController extends Controller {
/**
* @NoAdminRequired
+ * @PublicPage
*/
public function close(int $documentId, int $sessionId, string $sessionToken): DataResponse {
return $this->apiService->close($documentId, $sessionId, $sessionToken);
@@ -65,6 +67,7 @@ class SessionController extends Controller {
/**
* @NoAdminRequired
+ * @PublicPage
*/
public function push(int $documentId, int $sessionId, string $sessionToken, int $version, array $steps): DataResponse {
return $this->apiService->push($documentId, $sessionId, $sessionToken, $version, $steps);
@@ -72,6 +75,7 @@ class SessionController extends Controller {
/**
* @NoAdminRequired
+ * @PublicPage
*/
public function sync(int $documentId, int $sessionId, string $sessionToken, int $version = 0, string $autosaveContent = null, bool $force = false, bool $manualSave = false): DataResponse {
return $this->apiService->sync($documentId, $sessionId, $sessionToken, $version, $autosaveContent, $force, $manualSave);
diff --git a/lib/Db/Session.php b/lib/Db/Session.php
index dc3bc9b7d..9f5e17161 100644
--- a/lib/Db/Session.php
+++ b/lib/Db/Session.php
@@ -26,6 +26,11 @@ namespace OCA\Text\Db;
use OCP\AppFramework\Db\Entity;
+/**
+ * @method setLastContact(int $getTime)
+ * @method getDocumentId()
+ * @method getUserId()
+ */
class Session extends Entity implements \JsonSerializable {
public $id;
diff --git a/lib/DirectEditing/TextDirectEditor.php b/lib/DirectEditing/TextDirectEditor.php
new file mode 100644
index 000000000..cb6e06879
--- /dev/null
+++ b/lib/DirectEditing/TextDirectEditor.php
@@ -0,0 +1,151 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Text\DirectEditing;
+
+use OCA\Files\Controller\ApiController;
+use OCA\Text\AppInfo\Application;
+use OCA\Text\Service\ApiService;
+use OCP\AppFramework\Http\NotFoundResponse;
+use OCP\AppFramework\Http\Response;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\DirectEditing\IEditor;
+use OCP\DirectEditing\IToken;
+use OCP\Files\InvalidPathException;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\IInitialStateService;
+use OCP\IL10N;
+
+class TextDirectEditor implements IEditor {
+
+ /** @var IL10N */
+ private $l10n;
+
+ /** @var IInitialStateService */
+ private $initialStateService;
+
+ /** @var ApiService */
+ private $apiService;
+
+ public function __construct(IL10N $l10n, IInitialStateService $initialStateService, ApiService $apiService) {
+ $this->l10n = $l10n;
+ $this->initialStateService = $initialStateService;
+ $this->apiService = $apiService;
+ }
+
+ /**
+ * Return a unique identifier for the editor
+ *
+ * e.g. richdocuments
+ *
+ * @return string
+ */
+ public function getId(): string {
+ return Application::APP_NAME;
+ }
+
+ /**
+ * Return a readable name for the editor
+ *
+ * e.g. Collabora Online
+ *
+ * @return string
+ */
+ public function getName(): string {
+ return $this->l10n->t('Nextcloud Text');
+ }
+
+ /**
+ * A list of mimetypes that should open the editor by default
+ *
+ * @return array
+ */
+ public function getMimetypes(): array {
+ return [
+ 'text/markdown'
+ ];
+ }
+
+ /**
+ * A list of mimetypes that can be opened in the editor optionally
+ *
+ * @return array
+ */
+ public function getMimetypesOptional(): array {
+ return [
+ 'text/plain'
+ ];
+ }
+
+ /**
+ * Return a list of file creation options to be presented to the user
+ *
+ * @return array of ACreateFromTemplate|ACreateEmpty
+ */
+ public function getCreators(): array {
+ return [
+ new TextDocumentCreator($this->l10n),
+ new TextDocumentTemplateCreator($this->l10n)
+ ];
+ }
+
+ /**
+ * Return if the view is able to securely view a file without downloading it to the browser
+ *
+ * @return bool
+ */
+ public function isSecure(): bool {
+ return false;
+ }
+
+ /**
+ * Return a template response for displaying the editor
+ *
+ * open can only be called once when the client requests the editor with a one-time-use token
+ * For handling editing and later requests, editors need to impelement their own token handling and take care of invalidation
+ *
+ * This behavior is similar to the current direct editing implementation in collabora where we generate a one-time token and switch over to the regular wopi token for the actual editing/saving process
+ *
+ * @param IToken $token
+ * @return Response
+ */
+ public function open(IToken $token): Response {
+ $token->useTokenScope();
+ try {
+ $session = $this->apiService->create($token->getFile()->getId());
+ $this->initialStateService->provideInitialState('text', 'file', [
+ 'fileId' => $token->getFile()->getId(),
+ 'content' => $token->getFile()->getContent(),
+ 'session' => \json_encode($session->getData())
+ ]);
+ $this->initialStateService->provideInitialState('text', 'directEditingToken', $token->getToken());
+ return new TemplateResponse('text', 'main', [], 'base');
+ } catch (InvalidPathException $e) {
+ } catch (NotFoundException $e) {
+ } catch (NotPermittedException $e) {
+ }
+ return new NotFoundResponse();
+ }
+
+}
diff --git a/lib/DirectEditing/TextDocumentCreator.php b/lib/DirectEditing/TextDocumentCreator.php
new file mode 100644
index 000000000..6b78cefb4
--- /dev/null
+++ b/lib/DirectEditing/TextDocumentCreator.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Text\DirectEditing;
+
+
+use OCP\DirectEditing\ACreateEmpty;
+use OCP\Files\File;
+use OCP\IL10N;
+
+class TextDocumentCreator extends ACreateEmpty {
+
+ /**
+ * @var IL10N
+ */
+ private $l10n;
+
+ public function __construct(IL10N $l10n) {
+ $this->l10n = $l10n;
+ }
+
+ public function getId(): string {
+ return 'textdocument';
+ }
+
+ public function getName(): string {
+ return $this->l10n->t('New text document');
+ }
+
+ public function getExtension(): string {
+ return '.md';
+ }
+
+ public function create(File $file, string $creatorId = null, string $templateId = null): void {
+ parent::create($file, $creatorId, $templateId); // TODO: Change the autogenerated stub
+
+ $file->putContent('## Empty document with Nextcloud Text');
+ }
+}
diff --git a/lib/DirectEditing/TextDocumentTemplateCreator.php b/lib/DirectEditing/TextDocumentTemplateCreator.php
new file mode 100644
index 000000000..d7732573c
--- /dev/null
+++ b/lib/DirectEditing/TextDocumentTemplateCreator.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Text\DirectEditing;
+
+
+use OCP\DirectEditing\ACreateFromTemplate;
+use OCP\Files\File;
+use OCP\IL10N;
+
+class TextDocumentTemplateCreator extends ACreateFromTemplate {
+
+ const TEMPLATES = [
+ '1' => [
+ 'id' => '1',
+ 'extension' => 'md',
+ 'name' => 'Weekly ToDo',
+ 'preview' => 'https://cloud.bitgrid.net/apps/richdocuments/template/preview/832537'
+ ],
+ '2' => [
+ 'id' => '2',
+ 'extension' => 'md',
+ 'name' => 'Meeting notes',
+ 'preview' => 'https://cloud.bitgrid.net/apps/richdocuments/template/preview/832537'
+ ]
+ ];
+
+ /**
+ * @var IL10N
+ */
+ private $l10n;
+
+ public function __construct(IL10N $l10n) {
+ $this->l10n = $l10n;
+ }
+
+ public function getId(): string {
+ return 'textdocumenttemplate';
+ }
+
+ public function getName(): string {
+ return $this->l10n->t('New text document from template');
+ }
+
+ public function getExtension(): string {
+ return '.md';
+ }
+
+ public function getTemplates(): array {
+ return self::TEMPLATES;
+ }
+
+ public function create(File $file, string $creatorId = null, string $templateId = null): void {
+ $template = self::TEMPLATES[$templateId];
+ $file->putContent('## ' . $template['name'] . '\n\n' . 'Created from a template with Nextcloud text');
+ }
+
+}
diff --git a/lib/Service/ApiService.php b/lib/Service/ApiService.php
index c6a35c610..a43498ceb 100644
--- a/lib/Service/ApiService.php
+++ b/lib/Service/ApiService.php
@@ -61,7 +61,6 @@ class ApiService {
try {
$readOnly = true;
/** @var File $file */
- $file = null;
if ($token) {
$file = $this->documentService->getFileByShareToken($token, $this->request->getParam('filePath'));
try {
@@ -72,9 +71,6 @@ class ApiService {
} else if ($fileId) {
$file = $this->documentService->getFileById($fileId);
$readOnly = !$file->isUpdateable();
- } else if ($filePath) {
- $file = $this->documentService->getFileByPath($filePath);
- $readOnly = !$file->isUpdateable();
} else {
return new DataResponse('No valid file argument provided', 500);
}
@@ -132,11 +128,8 @@ class ApiService {
* @throws \OCP\AppFramework\Db\DoesNotExistException
*/
public function push($documentId, $sessionId, $sessionToken, $version, $steps, $token = null): DataResponse {
- if ($token) {
- $file = $this->documentService->getFileByShareToken($token, $this->request->getParam('filePath'));
- } else {
- $file = $this->documentService->getFileById($documentId);
- }
+ $session = $this->sessionService->getSession($documentId, $sessionId, $sessionToken);
+ $file = $this->documentService->getFileForSession($session, $token);
if ($this->sessionService->isValidSession($documentId, $sessionId, $sessionToken) && !$this->documentService->isReadOnly($file, $token)) {
try {
$steps = $this->documentService->addStep($documentId, $sessionId, $steps, $version);
@@ -162,16 +155,13 @@ class ApiService {
'document' => $this->documentService->get($documentId)
];
+ $session = $this->sessionService->getSession($documentId, $sessionId, $sessionToken);
+ $file = $this->documentService->getFileForSession($session, $token);
+
try {
- $result['document'] = $this->documentService->autosave($documentId, $version, $autosaveContent, $force, $manualSave, $token, $this->request->getParam('filePath'));
+ $result['document'] = $this->documentService->autosave($file, $documentId, $version, $autosaveContent, $force, $manualSave, $token, $this->request->getParam('filePath'), $userId);
} catch (DocumentSaveConflictException $e) {
try {
- if ($token) {
- /** @var File $file */
- $file = $this->documentService->getFileByShareToken($token, $this->request->getParam('filePath'));
- } else {
- $file = $this->documentService->getFileById($documentId);
- }
$result['outsideChange'] = $file->getContent();
} catch (LockedException $e) {
// Ignore locked exception since it might happen due to an autosave action happening at the same time
diff --git a/lib/Service/DocumentService.php b/lib/Service/DocumentService.php
index 605a404fc..be682251d 100644
--- a/lib/Service/DocumentService.php
+++ b/lib/Service/DocumentService.php
@@ -26,6 +26,8 @@ declare(strict_types=1);
namespace OCA\Text\Service;
use \InvalidArgumentException;
+use OCA\Text\Db\Session;
+use OCP\DirectEditing\IManager;
use function json_encode;
use OC\Files\Node\File;
use OCA\Text\Db\Document;
@@ -108,6 +110,18 @@ class DocumentService {
} catch (NotFoundException $e) {
$this->appData->newFolder('documents');
}
+
+ // FIXME
+ $token = \OC::$server->getRequest()->getParam('token');
+ if ($this->userId === null && $token !== null) {
+ $this->directManager = \OC::$server->query(IManager::class);
+ try {
+ $tokenObject = $this->directManager->getToken($token);
+ $tokenObject->extend();
+ $tokenObject->useTokenScope();
+ $this->userId = $tokenObject->getUser();
+ } catch (\Exception $e) {}
+ }
}
/**
@@ -216,7 +230,7 @@ class DocumentService {
* @param $autoaveDocument
* @param bool $force
* @param bool $manualSave
- * @param null $token
+ * @param null $shareToken
* @return Document
* @throws DocumentSaveConflictException
* @throws DoesNotExistException
@@ -226,22 +240,15 @@ class DocumentService {
* @throws NotPermittedException
* @throws ShareNotFound
*/
- public function autosave($documentId, $version, $autoaveDocument, $force = false, $manualSave = false, $token = null, $filePath = null): Document {
+ public function autosave($file, $documentId, $version, $autoaveDocument, $force = false, $manualSave = false, $shareToken = null, $filePath = null, $userId = null): Document {
/** @var Document $document */
$document = $this->documentMapper->find($documentId);
- /** @var File $file */
- if (!$token) {
- $file = $this->getFileById($documentId);
- } else {
- $file = $this->getFileByShareToken($token, $filePath);
- }
-
if ($file === null) {
throw new NotFoundException();
}
- if ($this->isReadOnly($file, $token)) {
+ if ($this->isReadOnly($file, $shareToken)) {
return $document;
}
@@ -308,19 +315,42 @@ class DocumentService {
}
/**
+ * @param Session $session
+ * @param $shareToken
+ * @return \OCP\Files\File|Folder|Node
* @throws NotFoundException
*/
- public function getFileById($fileId): Node {
- $files = $this->rootFolder->getUserFolder($this->userId)->getById($fileId);
- if (count($files) === 0) {
+ public function getFileForSession(Session $session, $shareToken) {
+ if ($session->getUserId() !== null) {
+ return $this->getFileById($session->getDocumentId(), $session->getUserId());
+ }
+ try {
+ $share = $this->shareManager->getShareByToken($shareToken);
+
+ } catch (ShareNotFound $e) {
throw new NotFoundException();
}
- return $files[0];
+ $node = $share->getNode();
+ if ($node instanceof \OCP\Files\File) {
+ return $node;
+ }
+ if ($node instanceof Folder) {
+ return $node->getById($session->getDocumentId())[0];
+ }
+ throw new \InvalidArgumentException('No proper share data');
}
- public function getFileByPath($path): Node {
- return $this->rootFolder->getUserFolder($this->userId)->get($path);
+ /**
+ * @throws NotFoundException
+ */
+ public function getFileById($fileId, $userId = null): Node {
+ $files = $this->rootFolder->getUserFolder($this->userId ?? $userId)->getById($fileId);
+ if (count($files) === 0) {
+ throw new NotFoundException();
+ }
+
+ return $files[0];
}
/**
@@ -332,7 +362,6 @@ class DocumentService {
public function getFileByShareToken($shareToken, $path = null) {
try {
$share = $this->shareManager->getShareByToken($shareToken);
-
} catch (ShareNotFound $e) {
throw new NotFoundException();
}
diff --git a/lib/Service/SessionService.php b/lib/Service/SessionService.php
index b7be301ef..457fea497 100644
--- a/lib/Service/SessionService.php
+++ b/lib/Service/SessionService.php
@@ -29,6 +29,7 @@ use OCA\Text\Db\Session;
use OCA\Text\Db\SessionMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\DirectEditing\IManager;
use OCP\IAvatar;
use OCP\IAvatarManager;
use OCP\Security\ISecureRandom;
@@ -40,13 +41,29 @@ class SessionService {
private $sessionMapper;
private $secureRandom;
private $timeFactory;
+ private $avatarManager;
private $userId;
- public function __construct(SessionMapper $sessionMapper, ISecureRandom $secureRandom, ITimeFactory $timeFactory, $userId) {
+ /** @var Session cache current session in the request */
+ private $session = null;
+
+ public function __construct(SessionMapper $sessionMapper, ISecureRandom $secureRandom, ITimeFactory $timeFactory, IAvatarManager $avatarManager, $userId) {
$this->sessionMapper = $sessionMapper;
$this->secureRandom = $secureRandom;
$this->timeFactory = $timeFactory;
$this->userId = $userId;
+ // FIXME
+ $token = \OC::$server->getRequest()->getParam('token');
+ if ($this->userId === null && $token !== null) {
+ $this->directManager = \OC::$server->query(IManager::class);
+ try {
+ $tokenObject = $this->directManager->getToken($token);
+ $tokenObject->extend();
+ $tokenObject->useTokenScope();
+ $this->userId = $tokenObject->getUser();
+ } catch (\Exception $e) {}
+ }
+ $this->avatarManager = $avatarManager;
}
public function initSession($documentId, $guestName = null): Session {
@@ -55,9 +72,7 @@ class SessionService {
$userName = $this->userId ? $this->userId : $guestName;
$session->setUserId($userName);
$session->setToken($this->secureRandom->generate(64));
- /** @var IAvatarManager $avatarGenerator */
- $avatarGenerator = \OC::$server->query(IAvatarManager::class);
- $color = $avatarGenerator->getGuestAvatar($userName)->avatarBackgroundColor($userName);
+ $color = $this->avatarManager->getGuestAvatar($userName)->avatarBackgroundColor($userName);
$color = sprintf("#%02x%02x%02x", $color->r, $color->g, $color->b);
$session->setColor($color);
if ($this->userId === null) {
@@ -96,9 +111,24 @@ class SessionService {
return $this->sessionMapper->deleteInactive($documentId);
}
+ public function getSession($documentId, $sessionId, $token) {
+ if ($this->session !== null) {
+ return $this->session;
+ }
+ try {
+ return $this->sessionMapper->find($documentId, $sessionId, $token);
+ } catch (DoesNotExistException $e) {
+ $this->session = false;
+ return false;
+ }
+ }
+
public function isValidSession($documentId, $sessionId, $token) {
+ if ($this->userId) {
+ return true;
+ }
try {
- $session = $this->sessionMapper->find($documentId, $sessionId, $token);
+ $session = $this->getSession($documentId, $sessionId, $token);
} catch (DoesNotExistException $e) {
return false;
}
diff --git a/src/components/EditorWrapper.vue b/src/components/EditorWrapper.vue
index 5c8cf17e3..7b4a7c674 100644
--- a/src/components/EditorWrapper.vue
+++ b/src/components/EditorWrapper.vue
@@ -45,6 +45,7 @@
<GuestNameDialog v-if="isPublic && currentSession.guestName" :sync-service="syncService" />
</SessionList>
</div>
+ <slot name="header" />
</MenuBar>
<div>
<MenuBubble v-if="!readOnly && isRichEditor" :editor="tiptap" />
@@ -98,6 +99,10 @@ export default {
isMobile,
],
props: {
+ initialSession: {
+ type: Object,
+ default: null,
+ },
relativePath: {
type: String,
default: null,
@@ -192,7 +197,7 @@ export default {
}
},
hasDocumentParameters() {
- return this.fileId || this.shareToken
+ return this.fileId || this.shareToken || this.initialSession
},
isPublic() {
return document.getElementById('isPublic') && document.getElementById('isPublic').value === '1'
@@ -373,12 +378,20 @@ export default {
this.dirty = state.dirty
}
})
- this.syncService.open({
- fileId: this.fileId,
- filePath: this.relativePath,
- }).catch((e) => {
- this.hasConnectionIssue = true
- })
+ if (this.initialSession === null) {
+ this.syncService.open({
+ fileId: this.fileId,
+ filePath: this.relativePath,
+ }).catch((e) => {
+ this.hasConnectionIssue = true
+ })
+ } else {
+ this.syncService.open({
+ initialSession: this.initialSession,
+ }).catch((e) => {
+ this.hasConnectionIssue = true
+ })
+ }
this.forceRecreate = false
},
@@ -451,8 +464,7 @@ export default {
display: block;
width: 100%;
max-width: 100%;
- height: calc(100% - 50px);
- top: 50px;
+ height: 100%;
left: 0;
margin: 0 auto;
position: relative;
@@ -516,8 +528,7 @@ export default {
}
#editor-session-list {
- padding: 9px;
- padding-right: 16px;
+ padding: 4px 16px 4px 4px;
display: flex;
input, div {
diff --git a/src/components/FilesEditor.vue b/src/components/FilesEditor.vue
index 28c3df2b7..708df3f3c 100644
--- a/src/components/FilesEditor.vue
+++ b/src/components/FilesEditor.vue
@@ -64,3 +64,9 @@ export default {
},
}
</script>
+<style>
+#editor-container {
+ height: calc(100% - 50px);
+ top: 50px;
+}
+</style>
diff --git a/src/main.js b/src/main.js
index a9a241f1b..1aed86f45 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,29 +1,18 @@
-import FilesEditor from './components/FilesEditor'
-
__webpack_nonce__ = btoa(OC.requestToken) // eslint-disable-line
__webpack_public_path__ = OC.linkTo('text', 'js/') // eslint-disable-line
-if (document.getElementById('maineditor')) {
+if (document.getElementById('app-content')) {
Promise.all([
import(/* webpackChunkName: "editor" */'vue'),
- import(/* webpackChunkName: "editor" */'./components/EditorWrapper'),
+ import(/* webpackChunkName: "editor" */'./views/DirectEditing'),
]).then((imports) => {
const Vue = imports[0].default
Vue.prototype.t = window.t
Vue.prototype.OCA = window.OCA
- const Editor = imports[1].default
+ const DirectEditing = imports[1].default
const vm = new Vue({
- render: h => h(Editor, {
- props: {
- relativePath: '/welcome.md',
- active: true,
- },
- }),
+ render: h => h(DirectEditing),
})
- vm.$mount(document.getElementById('preview'))
+ vm.$mount(document.getElementById('app-content'))
})
}
-
-OCA.Text = {
- Editor: FilesEditor,
-}
diff --git a/src/services/SyncService.js b/src/services/SyncService.js
index 7af158ab0..0702b2c84 100644
--- a/src/services/SyncService.js
+++ b/src/services/SyncService.js
@@ -84,28 +84,38 @@ class SyncService {
return this
}
- open({ fileId, filePath }) {
- return this._openDocument({ fileId, filePath }).then(() => {
- this.emit('opened', {
+ async open({ fileId, filePath, initialSession }) {
+ let connectionData = null
+ if (typeof initialSession === 'undefined') {
+ try {
+ const response = await this._openDocument({ fileId, filePath })
+ connectionData = response.data
+ } catch (error) {
+ if (!error.response || error.code === 'ECONNABORTED') {
+ this.emit('error', ERROR_TYPE.CONNECTION_FAILED, {})
+ } else {
+ this.emit('error', ERROR_TYPE.LOAD_ERROR, error.response.status)
+ }
+ throw error
+ }
+ } else {
+ connectionData = initialSession
+ }
+
+ this.document = connectionData.document
+ this.document.readOnly = connectionData.readOnly
+ this.session = connectionData.session
+
+ this.emit('opened', {
+ document: this.document,
+ session: this.session,
+ })
+ return this._fetchDocument().then(({ data }) => {
+ this.emit('loaded', {
document: this.document,
session: this.session,
+ documentSource: '' + data,
})
- return this._fetchDocument().then(({ data }) => {
-
- this.emit('loaded', {
- document: this.document,
- session: this.session,
- documentSource: '' + data,
- })
- })
- }).catch((error) => {
- if (!error.response || error.code === 'ECONNABORTED') {
- this.emit('error', ERROR_TYPE.CONNECTION_FAILED, {})
- } else {
- this.emit('error', ERROR_TYPE.LOAD_ERROR, error.response.status)
- }
-
- return Promise.reject(error)
})
}
@@ -122,11 +132,6 @@ class SyncService {
guestName: this.options.guestName,
forceRecreate: this.options.forceRecreate,
},
- }).then((response) => {
- this.document = response.data.document
- this.document.readOnly = response.data.readOnly
- this.session = response.data.session
- return response.data
})
}
diff --git a/src/views/DirectEditing.vue b/src/views/DirectEditing.vue
new file mode 100644
index 000000000..114996977
--- /dev/null
+++ b/src/views/DirectEditing.vue
@@ -0,0 +1,115 @@
+<!--
+ - @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ -
+ - @author Julius Härtl <jus@bitgrid.net>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+
+<template>
+ <div id="editor-wrapper">
+ <EditorWrapper :initial-session="initialSession" :active="true" mime="text/markdown">
+ <template #header>
+ <button class="icon-share" @click="share" />
+ <button class="icon-close" @click="close" />
+ </template>
+ </EditorWrapper>
+ <!--<hr>
+ <h3>Debug output</h3>
+ <pre>{{ initialSession }}</pre>
+ <pre>Last request time: {{ log.mtime }}</pre>
+ <pre>{{ messages }}</pre>-->
+ </div>
+</template>
+
+<script>
+import Vue from 'vue'
+import EditorWrapper from '../components/EditorWrapper'
+
+const log = Vue.observable({
+ messages: [],
+ mtime: 0,
+})
+
+window.OCP.DirectEditing = {
+ close() {
+ log.messages.push('OCP.DirectEditing.close got called')
+ },
+}
+
+window.addEventListener('message', function(message) {
+ log.messages.push(message.data)
+})
+export default {
+ name: 'DirectEditing',
+ components: { EditorWrapper },
+ data() {
+ return {
+ initial: OCP.InitialState.loadState('text', 'file'),
+ messages: log.messages,
+ log: log,
+ }
+ },
+ computed: {
+ initialSession() {
+ return JSON.parse(this.initial.session) || null
+ },
+ },
+ methods: {
+ close() {
+ window.postMessage('close')
+ },
+ share() {
+ window.postMessage('share')
+ },
+ },
+}
+</script>
+
+<style scoped lang="scss">
+ pre {
+ width: 100%;
+ max-width: 700px;
+ margin: auto;
+ background-color: var(--color-background-dark);
+ }
+
+ button {
+ width: 44px;
+ height: 44px;
+ margin: 0;
+ background-size: 16px;
+ border: 0;
+ background-color: transparent;
+ opacity: .5;
+ color: var(--color-main-text);
+ background-position: center center;
+ vertical-align: top;
+ &:hover, &:focus, &:active {
+ background-color: var(--color-background-dark);
+ }
+ &.is-active,
+ &:hover,
+ &:focus {
+ opacity: 1;
+ }
+
+ &.icon-undo, &.icon-redo {
+ opacity: .4;
+ }
+ }
+</style>
diff --git a/templates/main.php b/templates/main.php
index 6d0a82953..384bcfdd8 100644
--- a/templates/main.php
+++ b/templates/main.php
@@ -5,3 +5,4 @@ style('text', 'icons');
<div id="app-content">
<div id="maineditor"></div>
</div>
+