Add dummy Talk sidebar to public share page

Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
This commit is contained in:
Daniel Calviño Sánchez 2020-01-15 04:21:03 +01:00
Родитель d809fc7bf0
Коммит aae3c5be24
7 изменённых файлов: 355 добавлений и 1 удалений

Просмотреть файл

@ -1 +1,2 @@
@import 'icons.scss';
@import 'publicshare.scss';

87
css/publicshare.scss Normal file
Просмотреть файл

@ -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;
}
}

Просмотреть файл

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

151
src/PublicShareSidebar.vue Normal file
Просмотреть файл

@ -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>

Просмотреть файл

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

Просмотреть файл

@ -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,
}

Просмотреть файл

@ -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: {