зеркало из https://github.com/nextcloud/spreed.git
Shamelessly copy the good work of Skjnldsv from the files app
Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Родитель
7de934124f
Коммит
ade8623048
|
@ -162,13 +162,15 @@
|
|||
|
||||
<!-- Text file creation dialog -->
|
||||
<NcModal v-if="showTextFileDialog !== false"
|
||||
size="small"
|
||||
size="normal"
|
||||
class="templates-picker"
|
||||
@close="dismissTextFileCreation">
|
||||
<div class="new-text-file">
|
||||
<h2>
|
||||
{{ t('spreed', 'Create and share a new file') }}
|
||||
</h2>
|
||||
<form class="new-text-file__form"
|
||||
<form class="new-text-file__form templates-picker__form"
|
||||
:style="style"
|
||||
@submit.prevent="handleCreateTextFile">
|
||||
<NcTextField id="new-file-form-name"
|
||||
ref="textFileTitleInput"
|
||||
|
@ -177,17 +179,33 @@
|
|||
:label="t('spreed', 'Name of the new file')"
|
||||
:placeholder="textFileTitle"
|
||||
:value.sync="textFileTitle" />
|
||||
|
||||
<template v-if="fileTemplate.templates.length">
|
||||
<ul class="templates-picker__list">
|
||||
<TemplatePreview v-bind="emptyTemplate"
|
||||
:checked="checked === emptyTemplate.fileid"
|
||||
@check="onCheck" />
|
||||
|
||||
<TemplatePreview v-for="template in fileTemplate.templates"
|
||||
:key="template.fileid"
|
||||
v-bind="template"
|
||||
:checked="checked === template.fileid"
|
||||
:ratio="fileTemplate.ratio"
|
||||
@check="onCheck" />
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<div class="new-text-file__buttons">
|
||||
<NcButton type="tertiary"
|
||||
@click="dismissTextFileCreation">
|
||||
{{ t('spreed', 'Close') }}
|
||||
</NcButton>
|
||||
<NcButton type="primary"
|
||||
@click="handleCreateTextFile">
|
||||
{{ t('spreed', 'Create file') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
</form>
|
||||
<div class="new-text-file__buttons">
|
||||
<NcButton type="tertiary"
|
||||
@click="dismissTextFileCreation">
|
||||
{{ t('spreed', 'Close') }}
|
||||
</NcButton>
|
||||
<NcButton type="primary"
|
||||
@click="handleCreateTextFile">
|
||||
{{ t('spreed', 'Create file') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
</div>
|
||||
</NcModal>
|
||||
</div>
|
||||
|
@ -216,6 +234,7 @@ import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
|
|||
import Folder from 'vue-material-design-icons/Folder.vue'
|
||||
import Upload from 'vue-material-design-icons/Upload.vue'
|
||||
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
|
||||
import TemplatePreview from './TemplatePreview.vue'
|
||||
|
||||
const picker = getFilePickerBuilder(t('spreed', 'File to share'))
|
||||
.setMultiSelect(false)
|
||||
|
@ -224,6 +243,10 @@ const picker = getFilePickerBuilder(t('spreed', 'File to share'))
|
|||
.allowDirectories()
|
||||
.build()
|
||||
|
||||
const border = 2
|
||||
const margin = 8
|
||||
const width = margin * 20
|
||||
|
||||
export default {
|
||||
|
||||
name: 'NewMessageForm',
|
||||
|
@ -245,6 +268,7 @@ export default {
|
|||
NcModal,
|
||||
Folder,
|
||||
Upload,
|
||||
TemplatePreview,
|
||||
NcTextField,
|
||||
},
|
||||
|
||||
|
@ -266,6 +290,9 @@ export default {
|
|||
showTextFileDialog: false,
|
||||
textFileTitle: t('spreed', 'New file'),
|
||||
newFileError: '',
|
||||
|
||||
// Check empty template by default
|
||||
checked: -1,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -372,6 +399,34 @@ export default {
|
|||
fileTemplateOptions() {
|
||||
return this.$store.getters.getFileTemplates()
|
||||
},
|
||||
|
||||
fileTemplate() {
|
||||
return this.fileTemplateOptions[this.showTextFileDialog]
|
||||
},
|
||||
|
||||
emptyTemplate() {
|
||||
return {
|
||||
basename: t('files', 'Blank'),
|
||||
fileid: -1,
|
||||
filename: t('files', 'Blank'),
|
||||
hasPreview: false,
|
||||
mime: this.fileTemplate?.mimetypes[0] || this.fileTemplate?.mimetypes,
|
||||
}
|
||||
},
|
||||
|
||||
selectedTemplate() {
|
||||
return this.fileTemplate.templates.find(template => template.fileid === this.checked)
|
||||
},
|
||||
|
||||
style() {
|
||||
return {
|
||||
'--margin': margin + 'px',
|
||||
'--width': width + 'px',
|
||||
'--border': border + 'px',
|
||||
'--fullwidth': width + 2 * margin + 2 * border + 'px',
|
||||
'--height': this.fileTemplate.ratio ? Math.round(width / this.fileTemplate.ratio) + 'px' : null,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
@ -656,19 +711,31 @@ export default {
|
|||
this.showSimplePollsEditor = value
|
||||
},
|
||||
|
||||
/**
|
||||
* Manages the radio template picker change
|
||||
*
|
||||
* @param {number} fileid the selected template file id
|
||||
*/
|
||||
onCheck(fileid) {
|
||||
this.checked = fileid
|
||||
},
|
||||
|
||||
// Create text file and share it to a conversation
|
||||
async handleCreateTextFile() {
|
||||
this.newFileError = ''
|
||||
let filePath = this.$store.getters.getAttachmentFolder() + '/' + this.textFileTitle.replace('/', '')
|
||||
const fileTemplate = this.fileTemplateOptions[this.showTextFileDialog]
|
||||
|
||||
if (!filePath.endsWith(fileTemplate.extension)) {
|
||||
filePath += fileTemplate.extension
|
||||
if (!filePath.endsWith(this.fileTemplate.extension)) {
|
||||
filePath += this.fileTemplate.extension
|
||||
}
|
||||
|
||||
let fileData
|
||||
try {
|
||||
const response = await createTextFile(filePath)
|
||||
const response = await createTextFile(
|
||||
filePath,
|
||||
this.selectedTemplate?.filename,
|
||||
this.selectedTemplate?.templateType,
|
||||
)
|
||||
fileData = response.data.ocs.data
|
||||
} catch (error) {
|
||||
console.error('Error while creating file', error)
|
||||
|
@ -813,6 +880,19 @@ export default {
|
|||
|
||||
&__form {
|
||||
width: 100%;
|
||||
|
||||
.templates-picker__list {
|
||||
display: grid;
|
||||
grid-gap: calc(var(--margin) * 2);
|
||||
grid-auto-columns: 1fr;
|
||||
// We want maximum 5 columns. Putting 6 as we don't count the grid gap. So it will always be lower than 6
|
||||
max-width: calc(var(--fullwidth) * 6);
|
||||
grid-template-columns: repeat(auto-fit, var(--fullwidth));
|
||||
// Make sure all rows are the same height
|
||||
grid-auto-rows: 1fr;
|
||||
// Center the columns set
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,240 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
-
|
||||
- @author John Molakvoæ <skjnldsv@protonmail.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/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<!-- FIXME COPY FROM apps/files/src/components/TemplatePreview.vue should be deduplicated -->
|
||||
<template>
|
||||
<li class="template-picker__item">
|
||||
<input :id="id"
|
||||
:checked="checked"
|
||||
type="radio"
|
||||
class="radio"
|
||||
name="template-picker"
|
||||
@change="onCheck">
|
||||
|
||||
<label :for="id" class="template-picker__label">
|
||||
<div class="template-picker__preview"
|
||||
:class="failedPreview ? 'template-picker__preview--failed' : ''">
|
||||
<img class="template-picker__image"
|
||||
:src="realPreviewUrl"
|
||||
alt=""
|
||||
draggable="false"
|
||||
@error="onFailure">
|
||||
</div>
|
||||
|
||||
<span class="template-picker__title">
|
||||
{{ nameWithoutExt }}
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
|
||||
const encodeFilePath = function(path) {
|
||||
const pathSections = (path.startsWith('/') ? path : `/${path}`).split('/')
|
||||
let relativePath = ''
|
||||
pathSections.forEach((section) => {
|
||||
if (section !== '') {
|
||||
relativePath += '/' + encodeURIComponent(section)
|
||||
}
|
||||
})
|
||||
return relativePath
|
||||
}
|
||||
|
||||
const isPublic = function() {
|
||||
return !getCurrentUser()
|
||||
}
|
||||
|
||||
const getToken = function() {
|
||||
return document.getElementById('sharingToken') && document.getElementById('sharingToken').value
|
||||
}
|
||||
|
||||
// preview width generation
|
||||
const previewWidth = 256
|
||||
|
||||
export default {
|
||||
name: 'TemplatePreview',
|
||||
inheritAttrs: false,
|
||||
|
||||
props: {
|
||||
basename: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
fileid: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
filename: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
previewUrl: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
hasPreview: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
mime: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
ratio: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
failedPreview: false,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* Strip away extension from name
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
nameWithoutExt() {
|
||||
return this.basename.indexOf('.') > -1 ? this.basename.split('.').slice(0, -1).join('.') : this.basename
|
||||
},
|
||||
|
||||
id() {
|
||||
return `template-picker-${this.fileid}`
|
||||
},
|
||||
|
||||
realPreviewUrl() {
|
||||
// If original preview failed, fallback to mime icon
|
||||
if (this.failedPreview && this.mimeIcon) {
|
||||
return this.mimeIcon
|
||||
}
|
||||
|
||||
if (this.previewUrl) {
|
||||
return this.previewUrl
|
||||
}
|
||||
// TODO: find a nicer standard way of doing this?
|
||||
if (isPublic()) {
|
||||
return generateUrl(`/apps/files_sharing/publicpreview/${getToken()}?fileId=${this.fileid}&file=${encodeFilePath(this.filename)}&x=${previewWidth}&y=${previewWidth}&a=1`)
|
||||
}
|
||||
return generateUrl(`/core/preview?fileId=${this.fileid}&x=${previewWidth}&y=${previewWidth}&a=1`)
|
||||
},
|
||||
|
||||
mimeIcon() {
|
||||
return OC.MimeType.getIconUrl(this.mime)
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onCheck() {
|
||||
this.$emit('check', this.fileid)
|
||||
},
|
||||
onFailure() {
|
||||
this.failedPreview = true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.template-picker {
|
||||
&__item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: flex;
|
||||
// Align in the middle of the grid
|
||||
align-items: center;
|
||||
flex: 1 1;
|
||||
flex-direction: column;
|
||||
|
||||
&, * {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__preview {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
// Stretch so all entries are the same width
|
||||
flex: 1 1;
|
||||
width: var(--width);
|
||||
min-height: var(--height);
|
||||
max-height: var(--height);
|
||||
padding: 0;
|
||||
border: var(--border) solid var(--color-border);
|
||||
border-radius: var(--border-radius-large);
|
||||
|
||||
input:checked + label > & {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
&--failed {
|
||||
// Make sure to properly center fallback icon
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&__image {
|
||||
max-width: 100%;
|
||||
background-color: var(--color-main-background);
|
||||
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
// Failed preview, fallback to mime icon
|
||||
&__preview--failed &__image {
|
||||
width: calc(var(--margin) * 8);
|
||||
// Center mime icon
|
||||
margin: auto;
|
||||
background-color: transparent !important;
|
||||
|
||||
object-fit: initial;
|
||||
}
|
||||
|
||||
&__title {
|
||||
overflow: hidden;
|
||||
// also count preview border
|
||||
max-width: calc(var(--width) + 2*2px);
|
||||
padding: var(--margin);
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -62,12 +62,16 @@ const getFileTemplates = async () => {
|
|||
/**
|
||||
* Share a text file to a conversation
|
||||
*
|
||||
* @param { string } filePath the file path
|
||||
* @param {string} filePath The new file destination path
|
||||
* @param {string} templatePath The template source path
|
||||
* @param {string} templateType The template type e.g 'user'
|
||||
* @return { object } the file object
|
||||
*/
|
||||
const createTextFile = async function(filePath) {
|
||||
const createTextFile = async function(filePath, templatePath, templateType) {
|
||||
return await axios.post(generateOcsUrl('apps/files/api/v1/templates/create'), {
|
||||
filePath,
|
||||
templatePath,
|
||||
templateType,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче