refactor: move template settings to vue component

Signed-off-by: Elizabeth Danzberger <lizzy7128@tutanota.de>
This commit is contained in:
Elizabeth Danzberger 2024-10-09 17:31:05 -04:00 коммит произвёл Julius Knorr
Родитель 938876af1b
Коммит fc11c12ae1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4C614C6ED2CDE6DF
10 изменённых файлов: 396 добавлений и 354 удалений

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

@ -10,37 +10,21 @@
#richdocuments {
#use_group_select, #edit_group_select {
width: 200px; display: block;
width: 200px;
display: block;
}
p {
margin-bottom: 15px;
}
#s2id_use_group_select,
#s2id_edit_group_select {
margin-left: 18px;
margin-top: -4px;
width: 300px !important;
}
}
input#zoteroAPIKeyField {
width: 300px;
}
textarea#documentSigningCertField {
width: 600px;
}
textarea#documentSigningKeyField {
width: 600px;
}
textarea#documentSigningCaField {
width: 600px;
}
#richdocuments,
#richdocuments-templates {
// inline buttons on section headers
> h2 {
display: inline-flex;
@ -58,6 +42,7 @@ textarea#documentSigningCaField {
line-height: 44px;
padding-left: 44px;
font-size: 16px;
&:hover,
&:focus,
&:active {
@ -67,77 +52,18 @@ textarea#documentSigningCaField {
}
}
#richdocuments-templates {
> input {
// feedback for keyboard navigation
&:hover,
&:focus,
&:active {
+ h2 .icon-add,
+ h2 .icon-loading-small {
opacity: 0.7;
}
+ #emptycontent label {
color: var(--color-text-light);
}
}
}
ul:not(.hidden) {
display: flex;
flex-wrap: wrap;
li {
$size: 150px;
$sizeY: math.div($size, 210) * 297;
$space: 10px;
border-radius: var(--border-radius);
border: 1px solid var(--color-border);
margin: $space;
position: relative;
figure {
display: flex;
flex-direction: column;
width: $size;
margin: $space;
img, .templatePlaceholder {
width: $size;
height: $sizeY;
background-color: var(--color-background-dark);
}
figcaption {
margin-top: $space;
}
}
.delete-cover,
.delete-template {
width: $size;
height: $sizeY;
top: 0;
left: 0;
position: absolute;
margin: $space;
opacity: 0;
transition: opacity 250ms ease-in-out;
z-index: 3;
line-height: $sizeY;
text-align: center;
font-size: 20px;
background-size: 24px;
// text is set as bg
color: var(--color-background-darker);
}
.delete-cover {
// bg is set as color
background-color: var(--color-text-lighter);
z-index: 2;
}
&:hover .delete-template,
.delete-template:focus,
.delete-template.icon-loading {
opacity: 1;
+ .delete-cover {
opacity: 0.5;
}
}
}
}
textarea#documentSigningCertField {
width: 600px;
}
textarea#documentSigningKeyField {
width: 600px;
}
textarea#documentSigningCaField {
width: 600px;
}
input#zoteroAPIKeyField {
width: 300px;
}

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

@ -72,11 +72,9 @@ describe('Office admin settings', function() {
cy.get('.settings-entry.font-list-settings').contains(font)
})
// FIXME: Template settings only get visible after reload
cy.reload()
cy.get('#richdocuments-templates')
cy.get('.settings-section__name')
.contains('Global Templates')
.scrollIntoView()
.should('be.visible')
})
})

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

@ -3,7 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
describe('Create new office files from templates', function() {
import {User} from "@nextcloud/cypress";
describe('Global templates', function() {
let randUser
before(function() {
@ -15,29 +17,54 @@ describe('Create new office files from templates', function() {
})
})
it('Create a new file from a user template', function() {
cy.visit('/apps/files')
it('Can be uploaded', function() {
cy.intercept('POST', '**/richdocuments/template').as('templateUploadRequest')
cy.uploadSystemTemplate({
fixturePath: 'templates/presentation.otp',
fileName: 'systemtemplate.otp',
mimeType: 'application/vnd.oasis.opendocument.presentation-template',
})
cy.get('[data-cy-upload-picker=""]')
.should('be.visible')
.as('newFileMenu')
cy.get('@newFileMenu').click()
cy.get('button[role="menuitem"]').contains('New presentation').click()
cy.get('input[data-cy-files-new-node-dialog-input=""]').type('FileFromTemplate')
cy.get('button[data-cy-files-new-node-dialog-submit=""]').click()
cy.get('form.templates-picker__form').as('templatePicker')
cy.get('@templatePicker').contains('presentation').click()
cy.get('@templatePicker').find('input[type="submit"]').click()
cy.waitForViewer()
cy.waitForCollabora()
cy.wait('@templateUploadRequest').then(({ response }) => {
expect(response.statusCode).to.equal(201)
expect(response.body.data.name).to.equal('systemtemplate.otp')
expect(response.body.data.type).to.equal('presentation')
})
})
it('Create a file from a system template as user', () => {
cy.uploadSystemTemplate()
it('Can prevent uploading a duplicate', function() {
cy.uploadSystemTemplate({
fixturePath: 'templates/presentation.otp',
fileName: 'systemtemplate.otp',
mimeType: 'application/vnd.oasis.opendocument.presentation-template',
})
cy.get('.toast-error').contains('Template "systemtemplate.otp" already exists').should('be.visible')
})
it('Can be deleted', function() {
cy.login(new User('admin', 'admin'))
cy.visit('/settings/admin/richdocuments')
cy.get('.settings-section__name')
.contains('Global Templates')
.scrollIntoView()
cy.intercept('DELETE', '**/richdocuments/template/*').as('templateDeleteRequest')
cy.get('.template-btn[data-cy-template-btn-name="systemtemplate"]').click()
cy.wait('@templateDeleteRequest').then(({ response }) => {
expect(response.statusCode).to.equal(204)
})
})
it('Can be created by a user', () => {
cy.uploadSystemTemplate({
fixturePath: 'templates/presentation.otp',
fileName: 'systemtemplate.otp',
mimeType: 'application/vnd.oasis.opendocument.presentation-template',
})
cy.login(randUser)
cy.visit('/apps/files')
@ -53,8 +80,15 @@ describe('Create new office files from templates', function() {
cy.get('form.templates-picker__form').as('templatePicker')
cy.get('@templatePicker').contains('systemtemplate').click()
cy.intercept('POST', '**/templates/create').as('templateCreateRequest')
cy.get('@templatePicker').find('input[type="submit"]').click()
cy.wait('@templateCreateRequest').then(({ response }) => {
expect(response.statusCode).to.equal(200)
expect(response.body.ocs.data.basename).to.equal('FileFromSystemTemplate.odp')
})
cy.waitForViewer()
cy.waitForCollabora()
})
@ -101,76 +135,99 @@ describe('Create new office files from templates', function() {
})
})
describe('Create templates with fields', () => {
let randUser
before(() => {
cy.createRandomUser().then(user => {
randUser = user
cy.login(randUser)
cy.visit('/apps/files')
// Create a templates folder
cy.get('[data-cy-upload-picker=""]')
.should('be.visible')
.as('newFileMenu')
cy.get('@newFileMenu').click()
cy.get('button[role="menuitem"]').contains('Create templates folder').click()
cy.get('button[data-cy-files-new-node-dialog-submit=""]').click()
// Upload the fixtures into the templates folder
cy.uploadFile(randUser, 'templates/document_template_with_fields.odt', 'application/vnd.oasis.opendocument.text', '/Templates/document.odt')
})
})
it('Create a document from a template with fields', () => {
const fields = [
{ type: 'rich-text', alias: 'Name', content: 'Nextcloud' },
{ type: 'rich-text', alias: 'Favorite app', content: 'richdocuments' },
{ type: 'checkbox', alias: 'Uses Nextcloud at home', checked: true },
]
describe('User templates', function() {
it.skip('Create a new file from a user template', function() {
cy.visit('/apps/files')
// Create a new document
cy.get('[data-cy-upload-picker=""]')
.should('be.visible')
.as('newFileMenu')
cy.get('@newFileMenu').click()
cy.get('button[role="menuitem"]').contains('New document').click()
cy.get('button[role="menuitem"]').contains('New presentation').click()
cy.get('input[data-cy-files-new-node-dialog-input=""]').type('FileFromTemplateWithFields')
cy.get('input[data-cy-files-new-node-dialog-input=""]').type('FileFromTemplate')
cy.get('button[data-cy-files-new-node-dialog-submit=""]').click()
// Choose the document template
cy.get('form.templates-picker__form').as('templatePicker')
cy.get('@templatePicker').contains('document').click()
cy.get('@templatePicker').contains('presentation').click()
cy.get('@templatePicker').find('input[type="submit"]').click()
// Intercept the POST request to verify the correct fields are submitted
cy.intercept('POST', '**/templates/create', (req) => {
const templateFields = Object.values(req.body.templateFields)
cy.waitForViewer()
cy.waitForCollabora()
})
expect(templateFields[0].content).to.equal(fields[0].content)
expect(templateFields[1].content).to.equal(fields[1].content)
describe('Create templates with fields', () => {
let randUser
req.continue()
}).as('reqFillFields')
before(() => {
cy.createRandomUser().then(user => {
randUser = user
cy.submitTemplateFields(fields)
cy.login(randUser)
cy.visit('/apps/files')
// Wait for the response and collect the file ID of the created file
cy.wait('@reqFillFields').then(({ response }) => {
cy.wrap(response.body.ocs.data.fileid).as('createdFileId')
// Create a templates folder
cy.get('[data-cy-upload-picker=""]')
.should('be.visible')
.as('newFileMenu')
cy.get('@newFileMenu').click()
cy.get('button[role="menuitem"]').contains('Create templates folder').click()
cy.get('button[data-cy-files-new-node-dialog-submit=""]').click()
// Upload the fixtures into the templates folder
cy.uploadFile(randUser, 'templates/document_template_with_fields.odt', 'application/vnd.oasis.opendocument.text', '/Templates/document.odt')
})
})
// Test if the fields currently match the values we passed to the template
cy.get('@createdFileId').then(createdFileId => {
cy.verifyTemplateFields(fields, createdFileId)
it('Create a document from a template with fields', () => {
const fields = [
{ type: 'rich-text', alias: 'Name', content: 'Nextcloud' },
{ type: 'rich-text', alias: 'Favorite app', content: 'richdocuments' },
{ type: 'checkbox', alias: 'Uses Nextcloud at home', checked: true },
]
cy.visit('/apps/files')
// Create a new document
cy.get('[data-cy-upload-picker=""]')
.should('be.visible')
.as('newFileMenu')
cy.get('@newFileMenu').click()
cy.get('button[role="menuitem"]').contains('New document').click()
cy.get('input[data-cy-files-new-node-dialog-input=""]').type('FileFromTemplateWithFields')
cy.get('button[data-cy-files-new-node-dialog-submit=""]').click()
// Choose the document template
cy.get('form.templates-picker__form').as('templatePicker')
cy.get('@templatePicker').contains('document').click()
cy.get('@templatePicker').find('input[type="submit"]').click()
// Intercept the POST request to verify the correct fields are submitted
cy.intercept('POST', '**/templates/create', (req) => {
const templateFields = Object.values(req.body.templateFields)
expect(templateFields[0].content).to.equal(fields[0].content)
expect(templateFields[1].content).to.equal(fields[1].content)
req.continue()
}).as('reqFillFields')
cy.submitTemplateFields(fields)
// Wait for the response and collect the file ID of the created file
cy.wait('@reqFillFields').then(({ response }) => {
cy.wrap(response.body.ocs.data.fileid).as('createdFileId')
})
// Test if the fields currently match the values we passed to the template
cy.get('@createdFileId').then(createdFileId => {
cy.verifyTemplateFields(fields, createdFileId)
})
})
})
})

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

@ -293,16 +293,19 @@ Cypress.Commands.add('verifyOpen', (filename) => {
.should('contain.text', filename)
})
Cypress.Commands.add('uploadSystemTemplate', () => {
Cypress.Commands.add('uploadSystemTemplate', ({ fixturePath, fileName, mimeType }) => {
cy.login(new User('admin', 'admin'))
cy.visit('/settings/admin/richdocuments')
cy.get('#richdocuments-templates').scrollIntoView()
cy.get('input[type=file]#add-template').selectFile({
contents: 'cypress/fixtures/templates/presentation.otp',
fileName: 'systemtemplate.otp',
mimeType: 'application/vnd.oasis.opendocument.presentation-template',
cy.get('.settings-section__name')
.contains('Global Templates')
.scrollIntoView()
cy.get('.settings-section input[type="file"]').selectFile({
contents: `cypress/fixtures/${fixturePath}`,
fileName,
mimeType,
}, { force: true })
cy.get('#richdocuments-templates li').contains('systemtemplate.otp')
})
Cypress.Commands.add('submitTemplateFields', (fields) => {

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

@ -11,6 +11,7 @@ namespace OCA\Richdocuments\Service;
use OCA\Richdocuments\AppConfig;
use OCA\Richdocuments\AppInfo\Application;
use OCA\Richdocuments\Db\Wopi;
use OCA\Richdocuments\TemplateManager;
use OCA\Theming\ImageManager;
use OCP\AppFramework\Services\IInitialState;
use OCP\Defaults;
@ -24,6 +25,7 @@ class InitialStateService {
private IInitialState $initialState,
private AppConfig $appConfig,
private ImageManager $imageManager,
private TemplateManager $templateManager,
private CapabilitiesService $capabilitiesService,
private IURLGenerator $urlGenerator,
private Defaults $themingDefaults,
@ -58,6 +60,13 @@ class InitialStateService {
$this->provideOptions();
}
public function provideAdminSettings(): void {
$this->initialState->provideInitialState('adminSettings', [
'templatesAvailable' => $this->capabilitiesService->hasTemplateSource(),
'templates' => $this->templateManager->getSystemFormatted(),
]);
}
public function prepareParams(array $params): array {
$defaults = [
'instanceId' => $this->config->getSystemValue('instanceid'),

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

@ -30,6 +30,8 @@ class Admin implements ISettings {
public function getForm(): TemplateResponse {
$this->initialStateService->provideCapabilities();
$this->initialStateService->provideAdminSettings();
return new TemplateResponse(
'richdocuments',
'admin',
@ -45,8 +47,6 @@ class Admin implements ISettings {
'external_apps' => $this->config->getAppValue('richdocuments', 'external_apps'),
'canonical_webroot' => $this->config->getAppValue('richdocuments', 'canonical_webroot'),
'disable_certificate_verification' => $this->config->getAppValue('richdocuments', 'disable_certificate_verification', '') === 'yes',
'templates' => $this->manager->getSystemFormatted(),
'templatesAvailable' => $this->capabilitiesService->hasTemplateSource(),
'settings' => $this->appConfig->getAppSettings(),
'demo_servers' => $this->demoService->fetchDemoServers(),
'web_server' => strtolower($_SERVER['SERVER_SOFTWARE']),
@ -59,11 +59,11 @@ class Admin implements ISettings {
);
}
public function getSection() {
public function getSection(): string {
return 'richdocuments';
}
public function getPriority() {
public function getPriority(): int {
return 0;
}
}

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

@ -4,8 +4,6 @@
*/
import './init-shared.js'
import Vue from 'vue'
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import AdminSettings from './components/AdminSettings.vue'
import '../css/admin.scss'
@ -29,119 +27,3 @@ const element = document.getElementById('admin-vue')
new Vue({
render: h => h(AdminSettings, { props: { initial: JSON.parse(element.dataset.initial) } }),
}).$mount('#admin-vue')
/**
* Append a new template to the dom
*
* @param {object} data the template data from the template controller response
*/
function appendTemplateFromData(data) {
const template = document.querySelector('.template-model').cloneNode(true)
template.className = ''
template.dataset.filename = data.name
template.querySelector('img').src = data.preview
template.querySelector('figcaption').textContent = data.name
template.querySelector('.delete-template').href = data.delete
document.querySelector('#richdocuments-templates > ul').appendChild(template)
template.querySelector('.delete-template').addEventListener('click', deleteTemplate)
}
/**
* Delete template event handler
*
* @param {Event} event the button click event
*/
function deleteTemplate(event) {
event.preventDefault()
const emptyElmt = document.querySelector('#richdocuments-templates #emptycontent')
const tplListElmt = document.querySelector('#richdocuments-templates > ul')
const elmt = event.target
// ensure no request is in progress
if (elmt.className.indexOf('loading') === -1 && elmt.textContent === '') {
const remote = event.target.href
elmt.classList.add('icon-loading')
elmt.classList.remove('icon-delete')
// send request
axios.delete(remote)
.then(function() {
// remove template
elmt.parentElement.remove()
// is list empty? Only the default template is left
if (tplListElmt.querySelectorAll('li').length === 1) {
tplListElmt.classList.add('hidden')
emptyElmt.classList.remove('hidden')
}
})
.catch(function(e) {
// failure, show warning
elmt.textContent = t('richdocuments', 'Error')
elmt.classList.remove('icon-loading')
setTimeout(function() {
elmt.classList.add('icon-delete')
elmt.textContent = ''
}, 2000)
})
}
}
/**
* Init the upload manager and the delete template handler
*/
function initTemplateManager() {
const inputElmt = document.querySelector('#add-template')
const buttonElmt = document.querySelector('.icon-add')
const deleteElmts = document.querySelectorAll('.delete-template')
const emptyElmt = document.querySelector('#richdocuments-templates #emptycontent')
const tplListElmt = document.querySelector('#richdocuments-templates > ul')
deleteElmts.forEach(function(elmt) {
elmt.addEventListener('click', deleteTemplate)
})
// fileupload plugin
$('#richdocuments-templates').fileupload({
dataType: 'json',
url: generateUrl('apps/richdocuments/template'),
type: 'POST',
add(e, data) {
// submit on file selection
data.submit()
inputElmt.disabled = true
buttonElmt.className = 'icon-loading-small'
},
submit(e, data) {
data.formData = _.extend(data.formData || {}, {
requesttoken: OC.requestToken,
})
},
success(e) {
document.querySelector(`[data-filename="${e.data.name}"]`)?.remove()
inputElmt.disabled = false
buttonElmt.className = 'icon-add'
// add template to dom
appendTemplateFromData(e.data)
tplListElmt.classList.remove('hidden')
emptyElmt.classList.add('hidden')
},
fail(e, data) {
// failure, show warning
buttonElmt.className = 'icon-add'
buttonElmt.textContent = t('richdocuments', 'An error occurred') + ': ' + data.jqXHR.responseJSON.data.message
setTimeout(function() {
inputElmt.disabled = false
buttonElmt.textContent = ''
}, 2000)
},
})
}
document.addEventListener('DOMContentLoaded', () => {
initTemplateManager()
})

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

@ -390,6 +390,8 @@
</p>
</div>
</div>
<GlobalTemplates v-if="isSetup" />
</div>
</template>
@ -406,6 +408,7 @@ import SettingsSelectGroup from './SettingsSelectGroup.vue'
import SettingsExternalApps from './SettingsExternalApps.vue'
import SettingsInputFile from './SettingsInputFile.vue'
import SettingsFontList from './SettingsFontList.vue'
import GlobalTemplates from './AdminSettings/GlobalTemplates.vue'
import '@nextcloud/dialogs/style.css'
import { getCallbackBaseUrl } from '../helpers/url.js'
@ -435,6 +438,7 @@ export default {
SettingsExternalApps,
SettingsInputFile,
SettingsFontList,
GlobalTemplates,
NcModal,
NcNoteCard,
},
@ -533,9 +537,6 @@ export default {
else this.serverError = Object.values(getCapabilities().collabora).length > 0 ? SERVER_STATE_OK : SERVER_STATE_CONNECTION_ERROR
}
},
isSetup() {
this.toggleTemplateSettings()
},
},
beforeMount() {
for (const key in this.initial.settings) {
@ -581,7 +582,6 @@ export default {
}
this.checkIfDemoServerIsActive()
this.checkSettings()
this.toggleTemplateSettings()
},
methods: {
async checkSettings() {
@ -815,13 +815,6 @@ export default {
this.settings.fonts.splice(index, 1)
}
},
toggleTemplateSettings() {
if (this.isSetup) {
document.getElementById('richdocuments-templates').classList.remove('hidden')
} else {
document.getElementById('richdocuments-templates').classList.add('hidden')
}
},
},
}
</script>

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

@ -0,0 +1,217 @@
<!--
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<NcSettingsSection v-if="templatesAvailable"
:name="t('richdocuments', 'Global Templates')">
<input ref="newTemplateInput"
type="file"
class="hidden-visually"
@change="selectFile">
<div class="template-buttons">
<NcButton type="tertiary-no-background" @click="newTemplate">
<div class="template-btn new-template-btn">
<div class="template-icon">
<NewTemplateIcon :size="38" />
</div>
<span>{{ t('richdocuments', 'New') }}</span>
</div>
</NcButton>
<div v-for="template in existingTemplates" :key="template.id">
<NcButton type="tertiary-no-background"
@click="deleteTemplate(template.id)">
<div class="template-btn" :data-cy-template-btn-name="basename(template.name)">
<div class="template-icon"
:style="`background-image: url(${template.preview})`">
<div class="template-delete-overlay">
<DeleteIcon :size="38" />
</div>
</div>
<span :title="template.name">
{{ basename(template.name) }}
</span>
</div>
</NcButton>
</div>
</div>
</NcSettingsSection>
</template>
<script lang="js">
import { NcSettingsSection, NcButton } from '@nextcloud/vue'
import { translate as t } from '@nextcloud/l10n'
import { generateUrl } from '@nextcloud/router'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'
import '@nextcloud/dialogs/style.css'
import axios from '@nextcloud/axios'
import NewTemplateIcon from 'vue-material-design-icons/FileDocumentPlusOutline.vue'
import DeleteIcon from 'vue-material-design-icons/Delete.vue'
export default {
name: 'GlobalTemplates',
components: {
NcSettingsSection,
NcButton,
NewTemplateIcon,
DeleteIcon,
},
data() {
return {
existingTemplates: [],
templatesAvailable: false,
}
},
mounted() {
// Later maybe we can retrieve these settings from AdminSettings.vue`
// and pass them in as props (once AdminSettings is cleaned up)
const settings = loadState('richdocuments', 'adminSettings', {})
this.templatesAvailable = settings.templatesAvailable
this.existingTemplates = settings.templates?.filter((template) => {
return template.name !== 'Empty'
})
},
methods: {
t,
newTemplate() {
this.$refs.newTemplateInput?.click()
},
async selectFile() {
const selectedFile = this.$refs.newTemplateInput?.files[0]
const templateAlreadyExists = this.existingTemplates.some((template) => {
return template.name === selectedFile.name
})
if (!templateAlreadyExists) {
const template = await this.uploadTemplate(selectedFile)
this.existingTemplates.push(template)
showSuccess(t('richdocuments', 'Uploaded template "{name}"', { name: template.name }))
} else {
showError(t('richdocuments', 'Template "{name}" already exists', { name: selectedFile.name }))
}
},
async uploadTemplate(file) {
const url = generateUrl('/apps/richdocuments/template')
const formData = new FormData()
formData.append('files', file)
let res = null
try {
res = await axios.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
} catch (error) {
showError(error.response.data.data.message)
}
return res.data.data
},
async deleteTemplate(templateId) {
const url = generateUrl('/apps/richdocuments/template/' + templateId)
try {
await axios.delete(url)
} catch {
showError(t('richdocuments', 'Unable to delete template'))
return
}
const templateIndex = this.existingTemplates.findIndex((template) => {
return template.id === templateId
})
if (templateIndex !== -1) {
this.existingTemplates.splice(templateIndex, 1)
}
showSuccess(t('richdocuments', 'Deleted template'))
},
basename(filename) {
return filename.substr(0, filename.lastIndexOf('.'))
},
},
}
</script>
<style lang="scss">
$padding: calc(var(--default-grid-baseline) * 3);
.template-buttons {
display: grid;
gap: calc(var(--default-grid-baseline) * 4);
grid-template-columns: repeat(auto-fit, 175px);
}
.template-btn {
display: flex;
flex-flow: column nowrap;
border:
var(--border-width-input)
solid
var(--color-border)
;
border-radius: var(--border-radius-element);
width: 175px;
height: calc(175px * 1.5);
padding: $padding;
span {
text-align: start;
overflow: hidden;
text-overflow: ellipsis;
font-weight: normal;
flex-basis: var(--default-line-height);
}
}
.template-btn:hover .template-delete-overlay {
background-color: var(--color-box-shadow);
svg { visibility: visible; }
}
.template-icon {
flex-basis: 100%;
display: flex;
border-radius: var(--border-radius-element);
background-size: cover;
margin-bottom: $padding;
svg { color: var(--color-text-lighter); }
}
.template-delete-overlay {
border-radius: var(--border-radius-element);
display: flex;
align-items: center;
justify-content: center;
flex-basis: 100%;
svg {
visibility: hidden;
color: var(--color-error);
}
}
.new-template-btn {
.template-icon { justify-content: center; }
span {
text-align: center;
font-weight: bold;
}
}
</style>

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

@ -4,51 +4,8 @@
* SPDX-FileCopyrightText: 2014-2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
script('richdocuments', 'richdocuments-admin');
script('files', 'jquery.fileupload');
\OCP\Util::addScript('richdocuments', 'richdocuments-admin');
/** @var array $_ */
?>
<div id="admin-vue" data-initial="<?php p(json_encode($_['settings'], true)); ?>"></div>
<?php if ($_['settings']['templatesAvailable'] === true) { ?>
<form class="section hidden" id="richdocuments-templates" method="post" action="/template/">
<input name="files" class="hidden-visually" id="add-template" type="file" />
<h2>
<?php p($l->t('Global templates')) ?>
<label for="add-template" class="icon-add" title="<?php p($l->t('Add a new template')); ?>"></label>
</h2>
<div id="emptycontent" class="<?php p(empty($_['settings']['templates'])?:'hidden') ?>">
<div class="icon-file"></div>
<h2>
<?php p($l->t('No templates defined.')); ?>
</h2>
<label for="add-template"><?php p($l->t('Add a new one?')); ?></label>
</div>
<ul class="<?php p(!empty($_['settings']['templates'])?:'hidden') ?>">
<li class="hidden template-model">
<figure>
<img src="" alt="<?php p($l->t('template preview')) ?>" />
<figcaption></figcaption>
</figure>
<a href="" class="delete-template icon-delete"></a>
<div class="delete-cover"></div>
</li>
<?php foreach ($_['settings']['templates'] as $template) {?>
<li data-filename="<?php p($template['name']); ?>">
<figure>
<?php if (isset($template['preview'])) { ?>
<img src="<?php p($template['preview']) ?>?y=297&x=210" alt="<?php p($l->t('template preview')) ?>" />
<?php } else { ?>
<div class="templatePlaceholder"></div>
<?php } ?>
<figcaption><?php p($template['name']) ?></figcaption>
</figure>
<?php if (isset($template['delete'])) { ?><a href="<?php p($template['delete']) ?>" class="delete-template icon-delete"></a><?php } ?>
<div class="delete-cover"></div>
</li>
<?php } ?>
</ul>
</form>
<?php } ?>
<div id="admin-vue" data-initial="<?php p(json_encode($_['settings'], true)); ?>"></div>