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:
Diffstat (limited to 'src/components/RightSidebar/Description/Description.vue')
-rw-r--r--src/components/RightSidebar/Description/Description.vue423
1 files changed, 423 insertions, 0 deletions
diff --git a/src/components/RightSidebar/Description/Description.vue b/src/components/RightSidebar/Description/Description.vue
new file mode 100644
index 000000000..63afe5f49
--- /dev/null
+++ b/src/components/RightSidebar/Description/Description.vue
@@ -0,0 +1,423 @@
+<!--
+ - @copyright Copyright (c) 2020 Marco Ambrosini <marcoambrosini@pm.me>
+ -
+ - @author Marco Ambrosini <marcoambrosini@pm.me>
+ -
+ - @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 ref="description"
+ v-click-outside="handleClickOutside"
+ class="description"
+ :class="{'description--editing': editing, 'description--expanded': expanded}">
+ <RichContentEditable
+ ref="contenteditable"
+ :key="forceReRenderKey"
+ :value.sync="descriptionText"
+ class="description__contenteditable"
+ :auto-complete="()=>{}"
+ :maxlength="maxLength"
+ :contenteditable="editing && !loading"
+ :placeholder="placeholder"
+ @submit="handleSubmitDescription"
+ @keydown.esc="handleCancelEditing" />
+ <template v-if="!loading">
+ <template v-if="editing">
+ <button
+ class="description__button"
+ :aria-label="t('spreed','Cancel editing description')"
+ @click="handleCancelEditing">
+ <Close
+ decorative
+ title=""
+ :size="20" />
+ </button>
+ <button
+ class="description__button primary"
+ :aria-label="t('spreed','Submit conversation description')"
+ :disabled="!canSubmit"
+ @click="handleSubmitDescription">
+ <Check
+ decorative
+ title=""
+ :size="20" />
+ </button>
+ <div v-if="showCountDown"
+ v-tooltip.auto="countDownWarningText"
+ class="counter"
+ tabindex="0"
+ aria-label="countDownWarningText">
+ <span>{{ charactersCountDown }}</span>
+ </div>
+ </template>
+ <button v-if="!editing && editable"
+ class="description__button"
+ :aria-label="t('spreed','Edit conversation description')"
+ @click="handleEditDescription">
+ <Pencil
+ decorative
+ :size="20" />
+ </button>
+ </template>
+ <div v-if="loading" class="icon-loading-small spinner" />
+ <button v-if="!editing && overflows && expanded" class="expand-indicator description__button" @click="handleClick">
+ <ChevronDown />
+ </button>
+ <div v-if="showOverlay"
+ cursor="pointer"
+ class="overlay"
+ @click="handleClick" />
+ </div>
+</template>
+
+<script>
+import Pencil from 'vue-material-design-icons/Pencil'
+import Check from 'vue-material-design-icons/Check'
+import Close from 'vue-material-design-icons/Close'
+import ChevronDown from 'vue-material-design-icons/ChevronDown'
+import RichContentEditable from '@nextcloud/vue/dist/Components/RichContenteditable'
+import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
+import { directive as ClickOutside } from 'v-click-outside'
+
+export default {
+ name: 'Description',
+ components: {
+ Pencil,
+ Check,
+ Close,
+ RichContentEditable,
+ ChevronDown,
+ },
+
+ directives: {
+ Tooltip,
+ ClickOutside,
+ },
+
+ props: {
+ /**
+ * The description (An editable paragraph just above the sidebar tabs)
+ */
+ descriptionTitle: {
+ type: String,
+ default: t('spreed', 'Description'),
+ },
+
+ /**
+ * A paragraph below the title.
+ */
+ description: {
+ type: String,
+ default: '',
+ },
+
+ /**
+ * Shows or hides the editing buttons.
+ */
+ editable: {
+ type: Boolean,
+ default: false,
+ },
+
+ /**
+ * Toggles the description editing state on and off.
+ */
+ editing: {
+ type: Boolean,
+ default: false,
+ },
+
+ /**
+ * Placeholder for the contenteditable element.
+ */
+ placeholder: {
+ type: String,
+ default: '',
+ },
+
+ /**
+ * Toggles the loading state on and off.
+ */
+ loading: {
+ type: Boolean,
+ default: false,
+ },
+
+ /**
+ * Maximum description length in characters
+ */
+ maxLength: {
+ type: Number,
+ default: 500,
+ },
+ },
+
+ data() {
+ return {
+ descriptionText: '',
+ forceReRenderKey: 0,
+ expanded: false,
+ overflows: null,
+ }
+ },
+
+ computed: {
+
+ canSubmit() {
+ return this.charactersCount <= this.maxLength && this.descriptionText !== this.description
+ },
+
+ charactersCount() {
+ return this.descriptionText.length
+ },
+
+ charactersCountDown() {
+ return this.maxLength - this.charactersCount
+ },
+
+ showCountDown() {
+ return this.charactersCount >= this.maxLength - 20
+ },
+
+ countDownWarningText() {
+ return t('spreed', 'The description must be less than or equal to {maxLength} characters long. Your current text is {charactersCount} characters long.', {
+ maxLength: this.maxLength,
+ charactersCount: this.charactersCount,
+ })
+ },
+
+ showCollapseButton() {
+ return this.overflows && !this.editing && !this.loading && this.expanded
+ },
+
+ showOverlay() {
+ return this.overflows && !this.editing && !this.loading && !this.expanded
+ },
+ },
+
+ watch: {
+ // Each time the prop changes, reflect the changes in the value stored in this component
+ description() {
+ this.descriptionText = this.description
+ if (!this.editing) {
+ this.checkOverflow()
+ }
+ },
+ editing(newValue) {
+ if (!newValue) {
+ this.descriptionText = this.description
+ }
+ },
+ },
+
+ methods: {
+ handleEditDescription() {
+ const contenteditable = this.$refs.contenteditable.$refs.contenteditable
+ this.$emit('update:editing', true)
+ this.$nextTick(() => {
+ // Focus and select the text in the description
+ contenteditable.focus()
+ document.execCommand('selectAll', false, null)
+ })
+ },
+
+ handleSubmitDescription() {
+ if (!this.canSubmit) {
+ return
+ }
+ // Remove newlines and whitespaces.
+ this.descriptionText = this.descriptionText.replace(/\r\n|\n|\r/gm, '').trim()
+ // Submit description
+ this.$emit('submit:description', this.descriptionText)
+ /**
+ * Change the richcontenteditable key in order to trigger a re-render
+ * without this all the trimmed new lines and whitespaces would
+ * still be present in the contenteditable element.
+ */
+ this.forceReRenderKey += 1
+ },
+
+ handleCancelEditing() {
+ this.descriptionText = this.description
+ this.$emit('update:editing', false)
+ // Deselect all the text that's been selected in `handleEditDescription`
+ window.getSelection().removeAllRanges()
+ },
+
+ // Expand the description
+ handleClick() {
+ if (this.editing || this.loading) {
+ return
+ } if (this.overflows) {
+ this.expanded = !this.expanded
+ }
+ },
+
+ // Collapse the description or dismiss editing
+ handleClickOutside() {
+ this.expanded = false
+ this.$emit('update:editing', false)
+ },
+
+ checkOverflow() {
+ const descriptionScrollHeight = this.$refs.description.offsetHeight
+ const descriptionOffsetHeight = this.$refs.contenteditable.$refs.contenteditable.offsetHeight
+ this.overflows = descriptionScrollHeight > descriptionOffsetHeight
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+@import '../../../assets/variables.scss';
+
+.description {
+ margin: -20px 0 8px 8px;
+ display: flex;
+ width: 100%;
+ overflow: hidden;
+ position: relative;
+ max-height: calc(var(--default-line-height) * 3 + 28px);
+ &--editing {
+ box-shadow: 0 2px var(--color-primary-element);
+ transition: all 150ms ease-in-out;
+ max-height: unset;
+ align-items: flex-end;
+ }
+ &--expanded {
+ max-height: unset;
+ min-height: $clickable-area * 2;
+ align-items: flex-end;
+ }
+ &__header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 44px;
+ }
+ &__title {
+ color: var(--color-primary);
+ font-weight: bold;
+ font-size: var(--default-font-size);
+ line-height: var(----default-line-height);
+ }
+
+ &__contenteditable {
+ width: 100%;
+ &--empty:before {
+ position: absolute;
+ content: attr(placeholder);
+ color: var(--color-text-maxcontrast);
+ }
+ }
+ &__buttons{
+ display: flex;
+ margin-top: 8px;
+ justify-content: flex-end;
+ }
+ &__button {
+ width: $clickable-area;
+ height: $clickable-area;
+ flex-shrink: 0;
+ border: 0;
+ padding: 0;
+ margin: 0 0 4px 4px;
+ z-index: 1;
+ &:not(.primary) {
+ background-color: transparent;
+ }
+
+ &:hover,
+ &:focus {
+ background-color: var(--color-background-hover);
+ }
+ &:disabled {
+ &:hover {
+ background-color: var(--color-primary-element);
+ }
+ }
+ }
+}
+
+.spinner {
+ width: $clickable-area;
+ height: $clickable-area;
+ margin: 0 0 4px 0;
+}
+
+.expand-indicator {
+ width: $clickable-area;
+ height: $clickable-area;
+ margin: 0 0 4px 0;
+ position: absolute;
+ top: 0;
+ right: 0;
+}
+
+.counter {
+ background-color: var(--color-background-dark);
+ height: 44px;
+ width: 44px;
+ border-radius: var(--border-radius-pill);
+ position: absolute;
+ top: 0;
+ right: 0px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.overlay {
+ background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(255, 255, 255, 0.5) 75%, #FFFFFF 100%);
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding-right: $clickable-area;
+ cursor: pointer;
+}
+// Restyle richContentEditable component from our library.
+::v-deep .rich-contenteditable__input {
+ min-height: var(--default-line-height);
+ border-radius: 0;
+ overflow-x: hidden;
+ padding: 0 0 4px 0;
+ overflow: visible;
+ width: 100%;
+ background-color: transparent;
+ border: none;
+ color: var(--color-main-text);
+ font-size: var(--default-font-size);
+ line-height: var(--default-line-height);
+ margin-bottom: 4px;
+ max-height: unset;
+ align-self: flex-start;
+ margin-top: 12px;
+ &::before {
+ position: relative;
+ }
+ &[contenteditable='false'] {
+ background-color: transparent;
+ color: var(--color-main-text);
+ border: 0;
+ opacity: 1;
+ border-radius: 0;
+ }
+}
+
+</style>