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

github.com/nextcloud/spreed.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--css/merged-public-share.scss1
-rw-r--r--css/publicshare.scss87
-rw-r--r--lib/PublicShare/TemplateLoader.php2
-rw-r--r--src/PublicShareSidebar.vue151
-rw-r--r--src/mainPublicShareSidebar.js101
-rw-r--r--src/services/filesIntegrationServices.js13
-rw-r--r--webpack.common.js1
7 files changed, 355 insertions, 1 deletions
diff --git a/css/merged-public-share.scss b/css/merged-public-share.scss
index 04a24cced..c38c70b10 100644
--- a/css/merged-public-share.scss
+++ b/css/merged-public-share.scss
@@ -1 +1,2 @@
@import 'icons.scss';
+@import 'publicshare.scss';
diff --git a/css/publicshare.scss b/css/publicshare.scss
new file mode 100644
index 000000000..20ac3bd93
--- /dev/null
+++ b/css/publicshare.scss
@@ -0,0 +1,87 @@
+/* Special layout to include the Talk sidebar */
+
+/* The standard layout defined in the server includes a fixed header with a
+ * sticky sidebar. This causes the scroll bar for the main area to appear to the
+ * right of the sidebar, which looks confusing for the chat. Thus that layout is
+ * overridden with a static header and a content with full height without header
+ * to limit the vertical scroll bar only to it.
+ * Note that the flex layout can not be cascaded from the body element, as a
+ * flex display is not compatible with the absolute position set for the
+ * autocompletion panel, which is reparented to the body when shown. */
+#body-user #header,
+#body-public #header {
+ /* Override fixed position from server to include it in the body layout */
+ position: static;
+}
+
+#content {
+ &,
+ &.full-height {
+ /* Always full height without header. */
+ height: calc(100% - 50px);
+ }
+
+ display: flex;
+ flex-direction: row;
+ overflow: hidden;
+
+ flex-grow: 1;
+
+ /* Override "min-height: 100%" and "padding-top: 50px" set in server, as the
+ * header is part of the flex layout and thus the whole body is not
+ * available for the content. */
+ min-height: 0;
+ padding-top: 0;
+
+ /* Does not change anything in normal mode, but ensures that the element
+ * will stretch to the full width in full screen mode. */
+ width: 100%;
+
+ /* Override margin used in server, as the header is part of the flex layout
+ * and thus the content does not need to be pushed down. */
+ margin-top: 0;
+}
+
+#app-content {
+ display: flex;
+ flex-direction: column;
+ overflow-y: auto;
+ overflow-x: hidden;
+
+ flex-grow: 1;
+
+ margin-right: 0;
+}
+
+#files-public-content {
+ flex-grow: 1;
+}
+
+#content footer p a {
+ /* The server sets an height to the footer of 65px, but its contents are
+ * slightly larger, which causes a scroll bar to be added to the content
+ * even if there is enough space for the app content and the footer.
+ * The padding of links is 10px, so in practice reducing the bottom padding
+ * only affects the bottom padding of the last element (as in adjacent
+ * paragraphs the paddings would get merged and there will still be 10px
+ * from the top padding of the second element). */
+ padding-bottom: 8px;
+}
+
+
+
+#talk-sidebar-trigger {
+ width: 44px;
+ height: 44px;
+
+ background-color: transparent;
+ border-color: transparent;
+
+ opacity: 0.6;
+
+ &:hover,
+ &:focus,
+ &:active {
+ opacity: 1;
+ }
+}
diff --git a/lib/PublicShare/TemplateLoader.php b/lib/PublicShare/TemplateLoader.php
index a388e0690..21694218f 100644
--- a/lib/PublicShare/TemplateLoader.php
+++ b/lib/PublicShare/TemplateLoader.php
@@ -67,7 +67,7 @@ class TemplateLoader {
}
Util::addStyle('spreed', 'merged-public-share');
- Util::addScript('spreed', 'merged-public-share');
+ Util::addScript('spreed', 'talk-public-share-sidebar');
}
}
diff --git a/src/PublicShareSidebar.vue b/src/PublicShareSidebar.vue
new file mode 100644
index 000000000..50ed67d4b
--- /dev/null
+++ b/src/PublicShareSidebar.vue
@@ -0,0 +1,151 @@
+<!--
+ - @copyright Copyright (c) 2020, Daniel Calviño Sánchez <danxuliu@gmail.com>
+ -
+ - @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>
+ <aside v-if="isOpen" id="talk-sidebar">
+ <div v-if="!conversation" class="emptycontent room-not-joined">
+ <div class="icon icon-talk" />
+ <h2>{{ t('spreed', 'Discuss this file') }}</h2>
+ <button class="primary" @click="joinConversation">
+ {{ t('spreed', 'Join conversation') }}
+ </button>
+ </div>
+ <div v-else class="emptycontent">
+ <div class="icon icon-talk" />
+ <h2>Conversation joined</h2>
+ </div>
+ </aside>
+</template>
+
+<script>
+import { EventBus } from './services/EventBus'
+import { fetchConversation } from './services/conversationsService'
+import { getPublicShareConversationToken } from './services/filesIntegrationServices'
+import { joinConversation } from './services/participantsService'
+import { getSignaling } from './utils/webrtc/index'
+
+export default {
+
+ name: 'PublicShareSidebar',
+
+ props: {
+ shareToken: {
+ type: String,
+ required: true,
+ },
+
+ state: {
+ type: Object,
+ required: true,
+ },
+ },
+
+ data() {
+ return {
+ fetchCurrentConversationIntervalId: null,
+ }
+ },
+
+ computed: {
+ token() {
+ return this.$store.getters.getToken()
+ },
+
+ conversation() {
+ return this.$store.getters.conversations[this.token]
+ },
+
+ isOpen() {
+ return this.state.isOpen
+ },
+ },
+
+ methods: {
+
+ async joinConversation() {
+ await this.getPublicShareConversationToken()
+
+ await joinConversation(this.token)
+
+ // No need to wait for it, but fetching the conversation needs to be
+ // done once the user has joined the conversation (otherwise only
+ // limited data would be received if the user was not a participant
+ // of the conversation yet).
+ this.fetchCurrentConversation()
+
+ // FIXME The participant will not be updated with the server data
+ // when the conversation is got again (as "addParticipantOnce" is
+ // used), although that should not be a problem given that only the
+ // "inCall" flag (which is locally updated when joining and leaving
+ // a call) is currently used.
+ const signaling = await getSignaling()
+ if (signaling.url) {
+ EventBus.$on('shouldRefreshConversations', this.fetchCurrentConversation)
+ } else {
+ // The "shouldRefreshConversations" event is triggered only when
+ // the external signaling server is used; when the internal
+ // signaling server is used periodic polling has to be used
+ // instead.
+ this.fetchCurrentConversationIntervalId = window.setInterval(this.fetchCurrentConversation, 30000)
+ }
+ },
+
+ async getPublicShareConversationToken() {
+ const token = await getPublicShareConversationToken(this.shareToken)
+
+ this.$store.dispatch('updateToken', token)
+ },
+
+ async fetchCurrentConversation() {
+ if (!this.token) {
+ return
+ }
+
+ try {
+ const response = await fetchConversation(this.token)
+ this.$store.dispatch('addConversation', response.data.ocs.data)
+ } catch (exception) {
+ window.clearInterval(this.fetchCurrentConversationIntervalId)
+
+ this.$store.dispatch('deleteConversationByToken', this.token)
+ this.$store.dispatch('updateToken', '')
+ }
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+/* Properties based on the app-sidebar */
+#talk-sidebar {
+ position: relative;
+ flex-shrink: 0;
+ width: 27vw;
+ min-width: 300px;
+ max-width: 500px;
+
+ background: var(--color-main-background);
+ border-left: 1px solid var(--color-border);
+
+ overflow-x: hidden;
+ overflow-y: auto;
+ z-index: 1500;
+}
+</style>
diff --git a/src/mainPublicShareSidebar.js b/src/mainPublicShareSidebar.js
new file mode 100644
index 000000000..53de6e139
--- /dev/null
+++ b/src/mainPublicShareSidebar.js
@@ -0,0 +1,101 @@
+/**
+ * @copyright Copyright (c) 2020 Daniel Calviño Sánchez <danxuliu@gmail.com>
+ *
+ * @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/>.
+ *
+ */
+
+import Vue from 'vue'
+import PublicShareSidebar from './PublicShareSidebar'
+
+// Store
+import Vuex from 'vuex'
+import store from './store'
+
+// Utils
+import { generateFilePath } from '@nextcloud/router'
+import { getRequestToken } from '@nextcloud/auth'
+
+// Directives
+import { translate, translatePlural } from '@nextcloud/l10n'
+
+// CSP config for webpack dynamic chunk loading
+// eslint-disable-next-line
+__webpack_nonce__ = btoa(getRequestToken())
+
+// Correct the root of the app for chunk loading
+// OC.linkTo matches the apps folders
+// OC.generateUrl ensure the index.php (or not)
+// We do not want the index.php since we're loading files
+// eslint-disable-next-line
+__webpack_public_path__ = generateFilePath('spreed', '', 'js/')
+
+Vue.prototype.t = translate
+Vue.prototype.n = translatePlural
+Vue.prototype.OC = OC
+Vue.prototype.OCA = OCA
+
+Vue.use(Vuex)
+
+function adjustLayout() {
+ document.querySelector('#app-content').append(document.querySelector('footer'))
+
+ const talkSidebarElement = document.createElement('div')
+ talkSidebarElement.setAttribute('id', 'talk-sidebar')
+ document.querySelector('#content').append(talkSidebarElement)
+}
+
+adjustLayout()
+
+// An "isOpen" boolean should be passed to the component, but as it is a
+// primitive it would not be reactive; it needs to be wrapped in an object and
+// that object passed to the component to get reactivity.
+const sidebarState = {
+ isOpen: false,
+}
+
+// Open the sidebar by default based on the window width using the same
+// threshold as in the main Talk UI (in Talk 7).
+if (window.innerWidth > 1111) {
+ sidebarState.isOpen = true
+}
+
+function addTalkSidebarTrigger() {
+ const talkSidebarTriggerElement = document.createElement('button')
+ talkSidebarTriggerElement.setAttribute('id', 'talk-sidebar-trigger')
+ talkSidebarTriggerElement.setAttribute('class', 'icon-menu-people-white')
+ talkSidebarTriggerElement.addEventListener('click', () => {
+ sidebarState.isOpen = !sidebarState.isOpen
+ })
+ document.querySelector('.header-right').append(talkSidebarTriggerElement)
+}
+
+addTalkSidebarTrigger()
+
+function getShareToken() {
+ const shareTokenElement = document.getElementById('sharingToken')
+ return shareTokenElement.value
+}
+
+const talkSidebarVm = new Vue({
+ store,
+ propsData: {
+ shareToken: getShareToken(),
+ state: sidebarState,
+ },
+ ...PublicShareSidebar,
+})
+talkSidebarVm.$mount(document.querySelector('#talk-sidebar'))
diff --git a/src/services/filesIntegrationServices.js b/src/services/filesIntegrationServices.js
index 70a69d79d..3782b7f71 100644
--- a/src/services/filesIntegrationServices.js
+++ b/src/services/filesIntegrationServices.js
@@ -39,6 +39,19 @@ const getFileConversation = async function({ fileId }, options) {
}
}
+/**
+ * Gets the public share conversation token for a given share token.
+ *
+ * @param {String} shareToken the token of the share
+ * @returns {String} the conversation token
+ * @throws {Exception} if the conversation token could not be got
+ */
+const getPublicShareConversationToken = async function(shareToken) {
+ const response = await axios.get(generateOcsUrl('apps/spreed/api/v1', 2) + `publicshare/${shareToken}`)
+ return response.data.ocs.data.token
+}
+
export {
getFileConversation,
+ getPublicShareConversationToken,
}
diff --git a/webpack.common.js b/webpack.common.js
index 62ce7c36a..0881a2a85 100644
--- a/webpack.common.js
+++ b/webpack.common.js
@@ -15,6 +15,7 @@ module.exports = {
'talk-files-sidebar': path.join(__dirname, 'src', 'mainFilesSidebar.js'),
'talk-files-sidebar-loader': path.join(__dirname, 'src', 'mainFilesSidebarLoader.js'),
'talk-public-share-auth-sidebar': path.join(__dirname, 'src', 'mainPublicShareAuthSidebar.js'),
+ 'talk-public-share-sidebar': path.join(__dirname, 'src', 'mainPublicShareSidebar.js'),
'flow': path.join(__dirname, 'src', 'flow.js')
},
output: {