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:
authorJulius Härtl <jus@bitgrid.net>2019-06-15 00:26:12 +0300
committerJulius Härtl <jus@bitgrid.net>2019-06-15 11:14:01 +0300
commitd392585407cfd5abfd6b9f1880c96feeacc5447a (patch)
tree2ea57b1a86aeac4e07d3a0630fdaa99db0432dc8
parent1428971a593764d829007b30ffe9d13d3ffec8ab (diff)
Implement guest name change
Signed-off-by: Julius Härtl <jus@bitgrid.net>
-rw-r--r--appinfo/routes.php1
-rw-r--r--lib/Controller/PublicSessionController.php8
-rw-r--r--lib/Db/SessionMapper.php4
-rw-r--r--lib/Service/ApiService.php20
-rw-r--r--lib/Service/SessionService.php22
-rw-r--r--src/components/EditorWrapper.vue49
-rw-r--r--src/components/GuestNameDialog.vue52
-rw-r--r--src/helpers.js8
-rw-r--r--src/services/SyncService.js25
9 files changed, 125 insertions, 64 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php
index 6dc7cfefb..4243816c2 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -14,6 +14,7 @@ return [
['name' => 'Session#close', 'url' => '/session/close', 'verb' => 'GET'],
['name' => 'PublicSession#create', 'url' => '/public/session/create', 'verb' => 'GET'],
+ ['name' => 'PublicSession#updateSession', 'url' => '/public/session', 'verb' => 'POST'],
['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'],
diff --git a/lib/Controller/PublicSessionController.php b/lib/Controller/PublicSessionController.php
index c5e430eab..4aff8d83b 100644
--- a/lib/Controller/PublicSessionController.php
+++ b/lib/Controller/PublicSessionController.php
@@ -110,4 +110,12 @@ class PublicSessionController extends PublicShareController {
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/Db/SessionMapper.php b/lib/Db/SessionMapper.php
index 87e305cff..c162d82ae 100644
--- a/lib/Db/SessionMapper.php
+++ b/lib/Db/SessionMapper.php
@@ -30,6 +30,10 @@ use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
+/**
+ * @method Session update(Session $session)
+ * @method Session insert(Session $session)
+ */
class SessionMapper extends QBMapper {
public function __construct(IDBConnection $db) {
diff --git a/lib/Service/ApiService.php b/lib/Service/ApiService.php
index a98d957ee..2b0280ece 100644
--- a/lib/Service/ApiService.php
+++ b/lib/Service/ApiService.php
@@ -61,7 +61,8 @@ class ApiService {
try {
$this->documentService->checkSharePermissions($token, Constants::PERMISSION_UPDATE);
$readOnly = false;
- } catch (NotFoundException $e) {}
+ } catch (NotFoundException $e) {
+ }
} else if ($fileId) {
$file = $this->documentService->getFileById($fileId);
$readOnly = !$file->isUpdateable();
@@ -77,7 +78,8 @@ class ApiService {
if (count($activeSessions) === 0 || $forceRecreate) {
try {
$this->documentService->resetDocument($file->getId(), $forceRecreate);
- } catch (DocumentHasUnsavedChangesException $e) {}
+ } catch (DocumentHasUnsavedChangesException $e) {
+ }
}
$document = $this->documentService->createDocument($file);
@@ -113,7 +115,8 @@ class ApiService {
if (count($activeSessions) === 0) {
try {
$this->documentService->resetDocument($documentId);
- } catch (DocumentHasUnsavedChangesException $e) {}
+ } catch (DocumentHasUnsavedChangesException $e) {
+ }
}
return new DataResponse([]);
}
@@ -167,4 +170,15 @@ class ApiService {
'document' => $document
]);
}
+
+ public function updateSession(int $documentId, int $sessionId, string $sessionToken, string $guestName) {
+ if (!$this->sessionService->isValidSession($documentId, $sessionId, $sessionToken)) {
+ return new DataResponse([], 500);
+ }
+
+ if ($guestName === '') {
+ return new DataResponse([ 'message' => 'A guest name needs to be provided'], 500);
+ }
+ return $this->sessionService->updateSession($documentId, $sessionId, $sessionToken, $guestName);
+ }
}
diff --git a/lib/Service/SessionService.php b/lib/Service/SessionService.php
index fe796955c..b4eb393dd 100644
--- a/lib/Service/SessionService.php
+++ b/lib/Service/SessionService.php
@@ -60,7 +60,9 @@ class SessionService {
$color = $avatarGenerator->getGuestAvatar($userName)->avatarBackgroundColor($userName);
$color = sprintf("#%02x%02x%02x", $color->r, $color->g, $color->b);
$session->setColor($color);
- $session->setGuestName($guestName);
+ if ($this->userId === null) {
+ $session->setGuestName($guestName);
+ }
$session->setLastContact($this->timeFactory->getTime());
return $this->sessionMapper->insert($session);
}
@@ -102,8 +104,20 @@ class SessionService {
return true;
}
- public function cleanupSession() {
- // find expired sessions
- // remove them
+ /**
+ * @param $documentId
+ * @param $sessionId
+ * @param $sessionToken
+ * @param $guestName
+ * @return Session
+ * @throws DoesNotExistException
+ */
+ public function updateSession(int $documentId, int $sessionId, string $sessionToken, string $guestName): Session {
+ if ($this->userId !== null) {
+ throw new \Exception('Logged in users cannot set a guest name');
+ }
+ $session = $this->sessionMapper->find($documentId, $sessionId, $sessionToken);
+ $session->setGuestName($guestName);
+ return $this->sessionMapper->update($session);
}
}
diff --git a/src/components/EditorWrapper.vue b/src/components/EditorWrapper.vue
index 91ec5531d..3f2d28c25 100644
--- a/src/components/EditorWrapper.vue
+++ b/src/components/EditorWrapper.vue
@@ -31,6 +31,7 @@
<div id="editor">
<menu-bar v-if="!syncError && !readOnly" ref="menubar" :editor="tiptap">
<div v-if="currentSession && active" id="editor-session-list">
+ <guest-name-dialog v-if="isPublic && currentSession.guestName" :sync-service="syncService" />
<div v-tooltip="lastSavedStatusTooltip" class="save-status" :class="lastSavedStatusClass">
{{ lastSavedStatus }}
</div>
@@ -47,8 +48,6 @@
</div>
<collision-resolve-dialog v-if="hasSyncCollission && !readOnly" @resolveUseThisVersion="resolveUseThisVersion" @resolveUseServerVersion="resolveUseServerVersion" />
-
- <guest-name-dialog v-if="isPublic && !guestNameConfirmed" :value="guestName" @input="setGuestName($event)" />
</div>
</template>
@@ -56,7 +55,7 @@
import Vue from 'vue'
import { SyncService, ERROR_TYPE } from './../services/SyncService'
-import { endpointUrl } from './../helpers'
+import { endpointUrl, getRandomGuestName } from './../helpers'
import { createEditor, markdownit, createMarkdownSerializer } from './../EditorFactory'
import { EditorContent } from 'tiptap'
@@ -129,10 +128,7 @@ export default {
lastSavedString: '',
syncError: null,
readOnly: true,
- forceRecreate: false,
-
- guestName: '',
- guestNameConfirmed: false
+ forceRecreate: false
}
},
computed: {
@@ -191,14 +187,8 @@ export default {
watch: {
lastSavedStatus: function() { this.$refs.menubar.redrawMenuBar() }
},
- beforeMount() {
- const guestName = localStorage.getItem('text-guestName')
- if (guestName !== null) {
- this.guestName = guestName
- }
- },
mounted() {
- if (this.active && (this.hasDocumentParameters) && !this.isPublic) {
+ if (this.active && (this.hasDocumentParameters)) {
this.initSession()
}
setInterval(() => { this.updateLastSavedStatus() }, 2000)
@@ -211,12 +201,6 @@ export default {
}
},
methods: {
- setGuestName(guestName) {
- this.guestName = guestName
- this.guestNameConfirmed = true
- localStorage.setItem('text-guestName', this.guestName)
- this.initSession()
- },
updateLastSavedStatus() {
if (this.document) {
this.lastSavedString = window.moment(this.document.lastSavedVersionTime * 1000).fromNow()
@@ -227,9 +211,10 @@ export default {
this.$emit('error', 'No valid file provided')
return
}
+ const guestName = localStorage.getItem('text-guestName') ? localStorage.getItem('text-guestName') : getRandomGuestName()
this.syncService = new SyncService({
shareToken: this.shareToken,
- guestName: this.guestName,
+ guestName,
forceRecreate: this.forceRecreate,
serialize: (document) => {
const markdown = (createMarkdownSerializer(this.tiptap.nodes, this.tiptap.marks)).serialize(document)
@@ -241,6 +226,7 @@ export default {
this.currentSession = session
this.document = document
this.readOnly = document.readOnly
+ localStorage.setItem('text-guestName', this.currentSession.guestName)
})
.on('change', ({ document, sessions }) => {
if (this.document.baseVersionEtag !== '' && document.baseVersionEtag !== this.document.baseVersionEtag) {
@@ -254,7 +240,7 @@ export default {
this.tiptap.setOptions({ editable: !this.readOnly })
})
- .on('loaded', ({ document, session, documentSource }) => {
+ .on('loaded', ({ documentSource }) => {
this.tiptap = createEditor({
content: markdownit.render(documentSource),
onUpdate: ({ state }) => {
@@ -337,26 +323,25 @@ export default {
updateSessions(sessions) {
this.sessions = sessions.sort((a, b) => b.lastContact - a.lastContact)
let currentSessionIds = this.sessions.map((session) => session.userId)
+ let currentGuestIds = this.sessions.map((session) => session.guestId)
+
const removedSessions = Object.keys(this.filteredSessions)
- .filter(sessionId => !currentSessionIds.includes(sessionId))
+ .filter(sessionId => !currentSessionIds.includes(sessionId) && !currentGuestIds.includes(sessionId))
- // remove sessions
+ // remove sessions TODO: remove guest sessions
for (let index in removedSessions) {
Vue.delete(this.filteredSessions, removedSessions[index])
}
for (let index in this.sessions) {
let session = this.sessions[index]
- if (!session.userId) {
- session.userId = session.id
- }
-
- if (this.filteredSessions[session.userId]) {
+ const sessionKey = session.displayName ? session.userId : session.id
+ if (this.filteredSessions[sessionKey]) {
// update timestamp if relevant
- if (this.filteredSessions[session.userId].lastContact < session.lastContact) {
- Vue.set(this.filteredSessions[session.userId], 'lastContact', session.lastContact)
+ if (this.filteredSessions[sessionKey].lastContact < session.lastContact) {
+ Vue.set(this.filteredSessions[sessionKey], 'lastContact', session.lastContact)
}
} else {
- Vue.set(this.filteredSessions, session.userId, session)
+ Vue.set(this.filteredSessions, sessionKey, session)
}
}
},
diff --git a/src/components/GuestNameDialog.vue b/src/components/GuestNameDialog.vue
index e774f7333..fe82328f1 100644
--- a/src/components/GuestNameDialog.vue
+++ b/src/components/GuestNameDialog.vue
@@ -21,47 +21,51 @@
-->
<template>
- <div class="guest-name-dialog">
- <p>{{ t('text', 'Please enter a name to identify you as a public editor:') }}</p>
- <form @submit.prevent="setGuestName()">
- <input ref="guestNameField" type="text" :value="value">
- <input type="submit" class="icon-confirm" value="">
- </form>
- </div>
+ <form v-tooltip="t('text', 'Please enter a name to identify you as a public editor:')" class="guest-name-dialog" @submit.prevent="setGuestName()">
+ <input v-model="guestName" type="text">
+ <input type="submit" class="icon-confirm" value="">
+ </form>
</template>
<script>
export default {
name: 'GuestNameDialog',
props: {
- value: {
- type: String,
- default: ''
+ syncService: {
+ type: Object,
+ default: null
}
},
+ data() {
+ return {
+ guestName: ''
+ }
+ },
+ beforeMount() {
+ this.guestName = this.syncService.session.guestName
+ },
methods: {
setGuestName() {
- this.$emit('input', this.$refs.guestNameField.value)
+ const previousGuestName = this.syncService.session.guestName
+ this.syncService.updateSession(this.guestName).then(() => {
+ localStorage.setItem('text-guestName', this.guestName)
+ }).catch((e) => {
+ this.guestName = previousGuestName
+ })
}
}
}
</script>
<style scoped lang="scss">
- .guest-name-dialog {
- padding: 30px;
- text-align: center;
-
- form {
- display: flex;
- width: 100%;
- max-width: 200px;
- margin: auto;
- margin-top: 30px;
+ form.guest-name-dialog {
+ display: flex;
+ width: 100%;
+ max-width: 200px;
+ margin: auto;
- input[type=text] {
- flex-grow: 1;
- }
+ input[type=text] {
+ flex-grow: 1;
}
}
</style>
diff --git a/src/helpers.js b/src/helpers.js
index f68d18d46..77dfc5532 100644
--- a/src/helpers.js
+++ b/src/helpers.js
@@ -41,7 +41,13 @@ const endpointUrl = (endpoint, isPublic = false) => {
return `${_baseUrl}/${endpoint}`
}
+const randomGuestNames = ['Artichoke', 'Arugula', 'Asparagus', 'Avocado', 'Bamboo Shoot', 'Bean Sprout', 'Bean', 'Beet', 'Belgian Endive', 'Bell Pepper', 'Bitter Melon', 'Bitter Gourd', 'Bok Choy', 'Broccoli', 'Brussels Sprout', 'Burdock Root', 'Cabbage', 'Calabash', 'Caper', 'Carrot', 'Cassava', 'Cauliflower', 'Celery', 'Celery Root', 'Celtuce', 'Chayote', 'Chinese Broccoli', 'Corn', 'Baby Corn', 'Cucumber', 'English Cucumber', 'Gherkin', 'Pickling Cucumber', 'Daikon Radish', 'Edamame', 'Eggplant', 'Elephant Garlic', 'Endive', 'Curly', 'Escarole', 'Fennel', 'Fiddlehead', 'Galangal', 'Garlic', 'Ginger', 'Grape Leave', 'Green Bean', 'Wax Bean', 'Green', 'Amaranth Leave', 'Beet Green', 'Collard Green', 'Dandelion Green', 'Kale', 'Kohlrabi Green', 'Mustard Green', 'Rapini', 'Spinach', 'Swiss Chard', 'Turnip Green', 'Hearts of Palm', 'Horseradish', 'Jerusalem Artichoke', 'Jícama', 'Kale', 'Curly', 'Lacinato', 'Ornamental', 'Kohlrabi', 'Leeks', 'Lemongrass', 'Lettuce', 'Butterhead', 'Iceberg', 'Leaf', 'Romaine', 'Lotus Root', 'Lotus Seed', 'Mushroom', 'Napa Cabbage', 'Nopales', 'Okra', 'Olive', 'Onion', 'Green Onion', 'Parsley', 'Parsley Root', 'Parsnip', 'Pepper', 'Plantain', 'Potato', 'Pumpkin', 'Purslane', 'Radicchio', 'Radish', 'Rutabaga', 'Shallots', 'Spinach', 'Squash', 'Sweet Potato', 'Swiss Chard', 'Taro', 'Tomatillo', 'Tomato', 'Turnip', 'Water Chestnut', 'Water Spinach', 'Watercress', 'Winter Melon', 'Yams', 'Zucchini']
+const getRandomGuestName = () => {
+ return randomGuestNames[Math.floor(Math.random() * randomGuestNames.length)]
+}
+
export {
documentReady,
- endpointUrl
+ endpointUrl,
+ getRandomGuestName
}
diff --git a/src/services/SyncService.js b/src/services/SyncService.js
index 976c7b5b6..15bce1901 100644
--- a/src/services/SyncService.js
+++ b/src/services/SyncService.js
@@ -134,6 +134,27 @@ class SyncService {
)
}
+ updateSession(guestName) {
+ if (!this.isPublic()) {
+ return
+ }
+ return axios.post(
+ endpointUrl('session', !!this.options.shareToken), {
+ documentId: this.document.id,
+ sessionId: this.session.id,
+ sessionToken: this.session.token,
+ token: this.options.shareToken,
+ guestName
+ }
+ ).then(({ data }) => {
+ this.session = data
+ return data
+ }).catch((error) => {
+ console.error('Failed to update the session', error)
+ return Promise.reject(error)
+ })
+ }
+
sendSteps(_sendable) {
let sendable = _sendable || sendableSteps(this.state)
if (!sendable) {
@@ -228,6 +249,10 @@ class SyncService {
}
}
+ isPublic() {
+ return !!this.options.shareToken
+ }
+
}
export default SyncService