Skip template picker if none available

Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
John Molakvoæ (skjnldsv) 2021-01-19 16:38:51 +01:00 committed by Julius Härtl
parent 7e6d69d166
commit 4f90766ba3
No known key found for this signature in database
GPG Key ID: 4C614C6ED2CDE6DF
14 changed files with 295 additions and 105 deletions

View File

@ -64,12 +64,12 @@ class TemplateController extends OCSController {
*/ */
public function path(string $templatePath = '', bool $copySystemTemplates = false) { public function path(string $templatePath = '', bool $copySystemTemplates = false) {
try { try {
$this->templateManager->setTemplatePath($templatePath); $templatePath = $this->templateManager->initializeTemplateDirectory($templatePath, null, $copySystemTemplates);
if ($copySystemTemplates) { return new DataResponse([
$this->templateManager->initializeTemplateDirectory($templatePath); 'template_path' => $templatePath,
} 'templates' => $this->templateManager->listCreators()
return new DataResponse(); ]);
} catch (GenericFileException $e) { } catch (\Exception $e) {
throw new OCSForbiddenException($e->getMessage()); throw new OCSForbiddenException($e->getMessage());
} }
} }

View File

@ -294,7 +294,7 @@ class ViewController extends Controller {
if (class_exists(LoadViewer::class)) { if (class_exists(LoadViewer::class)) {
$this->eventDispatcher->dispatchTyped(new LoadViewer()); $this->eventDispatcher->dispatchTyped(new LoadViewer());
} }
$this->initialState->provideInitialState('template_path', $this->templateManager->hasTemplateDirectory() ? $this->templateManager->getTemplatePath() : null); $this->initialState->provideInitialState('templates_path', $this->templateManager->hasTemplateDirectory() ? $this->templateManager->getTemplatePath() : null);
$this->initialState->provideInitialState('templates', $this->templateManager->listCreators()); $this->initialState->provideInitialState('templates', $this->templateManager->listCreators());
$params = []; $params = [];

View File

@ -30,9 +30,9 @@
@change="onCheck"> @change="onCheck">
<label :for="id" class="template-picker__label"> <label :for="id" class="template-picker__label">
<div class="template-picker__preview"> <div class="template-picker__preview"
:class="failedPreview ? 'template-picker__preview--failed' : ''">
<img class="template-picker__image" <img class="template-picker__image"
:class="failedPreview ? 'template-picker__image--failed' : ''"
:src="realPreviewUrl" :src="realPreviewUrl"
alt="" alt=""
draggable="false" draggable="false"
@ -40,7 +40,7 @@
</div> </div>
<span class="template-picker__title"> <span class="template-picker__title">
{{ basename }} {{ nameWithoutExt }}
</span> </span>
</label> </label>
</li> </li>
@ -100,6 +100,14 @@ export default {
}, },
computed: { computed: {
/**
* Strip away extension from name
* @returns {string}
*/
nameWithoutExt() {
return this.basename.indexOf('.') > -1 ? this.basename.split('.').slice(0, -1).join('.') : this.basename
},
id() { id() {
return `template-picker-${this.fileid}` return `template-picker-${this.fileid}`
}, },
@ -107,7 +115,7 @@ export default {
realPreviewUrl() { realPreviewUrl() {
// If original preview failed, fallback to mime icon // If original preview failed, fallback to mime icon
if (this.failedPreview && this.mimeIcon) { if (this.failedPreview && this.mimeIcon) {
return generateUrl(this.mimeIcon) return this.mimeIcon
} }
if (this.previewUrl) { if (this.previewUrl) {
@ -149,7 +157,6 @@ export default {
align-items: center; align-items: center;
flex: 1 1; flex: 1 1;
flex-direction: column; flex-direction: column;
margin: var(--margin);
&, * { &, * {
cursor: pointer; cursor: pointer;
@ -162,32 +169,42 @@ export default {
} }
&__preview { &__preview {
display: flex; display: block;
overflow: hidden; overflow: hidden;
// Stretch so all entries are the same width // Stretch so all entries are the same width
flex: 1 1; flex: 1 1;
width: var(--width); width: var(--width);
min-height: var(--width); min-height: var(--height);
max-height: var(--height); max-height: var(--height);
padding: var(--margin); padding: 0;
border: var(--border) solid var(--color-border); border: var(--border) solid var(--color-border);
border-radius: var(--border-radius-large); border-radius: var(--border-radius-large);
input:checked + label > & { input:checked + label > & {
border-color: var(--color-primary); border-color: var(--color-primary);
} }
&--failed {
// Make sure to properly center fallback icon
display: flex;
}
} }
&__image { &__image {
max-width: 100%; max-width: 100%;
background-color: var(--color-main-background); background-color: var(--color-main-background);
&--failed { object-fit: cover;
width: calc(var(--margin) * 8); }
// Center mime icon
margin: auto; // Failed preview, fallback to mime icon
background-color: transparent !important; &__preview--failed &__image {
} width: calc(var(--margin) * 8);
// Center mime icon
margin: auto;
background-color: transparent !important;
object-fit: initial;
} }
&__title { &__title {

View File

@ -0,0 +1,29 @@
/**
* @copyright Copyright (c) 2021 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/>.
*
*/
import { generateOcsUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios'
export const getTemplates = async function() {
const response = await axios.get(generateOcsUrl('apps/files/api/v1', 2) + 'templates')
return response.data.ocs.data
}

View File

@ -23,9 +23,14 @@
import { getLoggerBuilder } from '@nextcloud/logger' import { getLoggerBuilder } from '@nextcloud/logger'
import { loadState } from '@nextcloud/initial-state' import { loadState } from '@nextcloud/initial-state'
import { translate as t, translatePlural as n } from '@nextcloud/l10n' import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import { generateOcsUrl } from '@nextcloud/router'
import { getCurrentDirectory } from './utils/davUtils'
import axios from '@nextcloud/axios'
import Vue from 'vue' import Vue from 'vue'
import TemplatePickerView from './views/TemplatePicker' import TemplatePickerView from './views/TemplatePicker'
import { getCurrentUser } from '@nextcloud/auth'
import { showError } from '@nextcloud/dialogs'
// Set up logger // Set up logger
const logger = getLoggerBuilder() const logger = getLoggerBuilder()
@ -47,8 +52,10 @@ TemplatePickerRoot.id = 'template-picker'
document.body.appendChild(TemplatePickerRoot) document.body.appendChild(TemplatePickerRoot)
// Retrieve and init templates // Retrieve and init templates
const templates = loadState('files', 'templates', []) let templates = loadState('files', 'templates', [])
let templatesPath = loadState('files', 'templates_path', false)
logger.debug('Templates providers', templates) logger.debug('Templates providers', templates)
logger.debug('Templates folder', { templatesPath })
// Init vue app // Init vue app
const View = Vue.extend(TemplatePickerView) const View = Vue.extend(TemplatePickerView)
@ -60,33 +67,77 @@ const TemplatePicker = new View({
}) })
TemplatePicker.$mount('#template-picker') TemplatePicker.$mount('#template-picker')
// Init template engine after load // Init template engine after load to make sure it's the last injected entry
window.addEventListener('DOMContentLoaded', function() { window.addEventListener('DOMContentLoaded', function() {
// Init template files menu if (!templatesPath) {
templates.forEach((provider, index) => { logger.debug('Templates folder not initialized')
const initTemplatesPlugin = {
const newTemplatePlugin = {
attach(menu) { attach(menu) {
const fileList = menu.fileList
// only attach to main file list, public view is not supported yet
if (fileList.id !== 'files' && fileList.id !== 'files.public') {
return
}
// register the new menu entry // register the new menu entry
menu.addMenuEntry({ menu.addMenuEntry({
id: `template-new-${provider.app}-${index}`, id: 'template-init',
displayName: provider.label, displayName: t('files', 'Set up templates folder'),
templateName: provider.label + provider.extension, templateName: t('files', 'Templates'),
iconClass: provider.iconClass || 'icon-file', iconClass: 'icon-template-add',
fileType: 'file', fileType: 'file',
actionHandler(name) { actionHandler(name) {
TemplatePicker.open(name, provider) initTemplatesFolder(name)
}, },
}) })
}, },
} }
OC.Plugins.register('OCA.Files.NewFileMenu', newTemplatePlugin) OC.Plugins.register('OCA.Files.NewFileMenu', initTemplatesPlugin)
}) }
}) })
// Init template files menu
templates.forEach((provider, index) => {
const newTemplatePlugin = {
attach(menu) {
const fileList = menu.fileList
// only attach to main file list, public view is not supported yet
if (fileList.id !== 'files' && fileList.id !== 'files.public') {
return
}
// register the new menu entry
menu.addMenuEntry({
id: `template-new-${provider.app}-${index}`,
displayName: provider.label,
templateName: provider.label + provider.extension,
iconClass: provider.iconClass || 'icon-file',
fileType: 'file',
actionHandler(name) {
TemplatePicker.open(name, provider)
},
})
},
}
OC.Plugins.register('OCA.Files.NewFileMenu', newTemplatePlugin)
})
/**
* Init the template directory
*
* @param {string} name the templates folder name
*/
const initTemplatesFolder = async function(name) {
const templatePath = (getCurrentDirectory() + `/${name}`).replace('//', '/')
try {
logger.debug('Initializing the templates directory', { templatePath })
const response = await axios.post(generateOcsUrl('apps/files/api/v1/templates', 2) + 'path', {
templatePath,
copySystemTemplates: true,
})
// Go to template directory
OCA.Files.App.currentFileList.changeDirectory(templatePath, true, true)
templates = response.data.ocs.data.templates
templatesPath = response.data.ocs.data.template_path
} catch (error) {
logger.error('Unable to initialize the templates directory')
showError(t('files', 'Unable to initialize the templates directory'))
}
}

View File

@ -23,7 +23,7 @@
import { generateRemoteUrl } from '@nextcloud/router' import { generateRemoteUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth' import { getCurrentUser } from '@nextcloud/auth'
const getRootPath = function() { export const getRootPath = function() {
if (getCurrentUser()) { if (getCurrentUser()) {
return generateRemoteUrl(`dav/files/${getCurrentUser().uid}`) return generateRemoteUrl(`dav/files/${getCurrentUser().uid}`)
} else { } else {
@ -31,12 +31,22 @@ const getRootPath = function() {
} }
} }
const isPublic = function() { export const isPublic = function() {
return !getCurrentUser() return !getCurrentUser()
} }
const getToken = function() { export const getToken = function() {
return document.getElementById('sharingToken') && document.getElementById('sharingToken').value return document.getElementById('sharingToken') && document.getElementById('sharingToken').value
} }
export { getRootPath, getToken, isPublic } /**
* Return the current directory, fallback to root
* @returns {string}
*/
export const getCurrentDirectory = function() {
const currentDirInfo = OCA?.Files?.App?.currentFileList?.dirInfo
|| { path: '/', name: '' }
// Make sure we don't have double slashes
return `${currentDirInfo.path}/${currentDirInfo.name}`.replace(/\/\//gi, '/')
}

View File

@ -29,7 +29,7 @@
<form class="templates-picker__form" <form class="templates-picker__form"
:style="style" :style="style"
@submit.prevent.stop="onSubmit"> @submit.prevent.stop="onSubmit">
<h3>{{ t('files', 'Pick a template') }}</h3> <h2>{{ t('files', 'Pick a template for {name}', { name: nameWithoutExt }) }}</h2>
<!-- Templates list --> <!-- Templates list -->
<ul class="templates-picker__list"> <ul class="templates-picker__list">
@ -55,11 +55,11 @@
<input type="submit" <input type="submit"
class="primary" class="primary"
:value="t('files', 'Create')" :value="t('files', 'Create')"
:aria-label="t('files', 'Create a new file with the ')"> :aria-label="t('files', 'Create a new file with the selected template')">
</div> </div>
</form> </form>
<EmptyContent class="templates-picker__loading" v-if="loading" icon="icon-loading"> <EmptyContent v-if="loading" class="templates-picker__loading" icon="icon-loading">
{{ t('files', 'Creating file') }} {{ t('files', 'Creating file') }}
</EmptyContent> </EmptyContent>
</Modal> </Modal>
@ -68,11 +68,12 @@
<script> <script>
import { generateOcsUrl } from '@nextcloud/router' import { generateOcsUrl } from '@nextcloud/router'
import { showError } from '@nextcloud/dialogs' import { showError } from '@nextcloud/dialogs'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent' import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
import Modal from '@nextcloud/vue/dist/Components/Modal' import Modal from '@nextcloud/vue/dist/Components/Modal'
import { getCurrentDirectory } from '../utils/davUtils'
import { getTemplates } from '../services/Templates'
import TemplatePreview from '../components/TemplatePreview' import TemplatePreview from '../components/TemplatePreview'
const border = 2 const border = 2
@ -107,6 +108,14 @@ export default {
}, },
computed: { computed: {
/**
* Strip away extension from name
* @returns {string}
*/
nameWithoutExt() {
return this.name.indexOf('.') > -1 ? this.name.split('.').slice(0, -1).join('.') : this.name
},
emptyTemplate() { emptyTemplate() {
return { return {
basename: t('files', 'Blank'), basename: t('files', 'Blank'),
@ -131,7 +140,7 @@ export default {
'--width': width + 'px', '--width': width + 'px',
'--border': border + 'px', '--border': border + 'px',
'--fullwidth': width + 2 * margin + 2 * border + 'px', '--fullwidth': width + 2 * margin + 2 * border + 'px',
'--height': this.ratio ? width * this.ratio + 'px' : null, '--height': this.provider.ratio ? Math.round(width / this.provider.ratio) + 'px' : null,
} }
}, },
}, },
@ -142,11 +151,27 @@ export default {
* @param {string} name the file name to create * @param {string} name the file name to create
* @param {object} provider the template provider picked * @param {object} provider the template provider picked
*/ */
open(name, provider) { async open(name, provider) {
this.checked = this.emptyTemplate.fileid this.checked = this.emptyTemplate.fileid
this.name = name this.name = name
this.opened = true
this.provider = provider this.provider = provider
const templates = await getTemplates()
const fetchedProvider = templates.find((fetchedProvider) => fetchedProvider.app === provider.app && fetchedProvider.label === provider.label)
if (fetchedProvider === null) {
throw new Error('Failed to match provider in results')
}
this.provider = fetchedProvider
// If there is no templates available, just create an empty file
if (fetchedProvider.templates.length === 0) {
this.onSubmit()
return
}
// Else, open the picker
this.opened = true
}, },
/** /**
@ -170,7 +195,7 @@ export default {
async onSubmit() { async onSubmit() {
this.loading = true this.loading = true
const currentDirectory = this.getCurrentDirectory() const currentDirectory = getCurrentDirectory()
const fileList = OCA?.Files?.App?.currentFileList const fileList = OCA?.Files?.App?.currentFileList
try { try {
@ -197,24 +222,13 @@ export default {
this.close() this.close()
} catch (error) { } catch (error) {
this.logger.error('Error while creating the new file from template', error) this.logger.error('Error while creating the new file from template')
console.error(error)
showError(this.t('files', 'Unable to create new file from template')) showError(this.t('files', 'Unable to create new file from template'))
} finally { } finally {
this.loading = false this.loading = false
} }
}, },
/**
* Return the current directory, fallback to root
* @returns {string}
*/
getCurrentDirectory() {
const currentDirInfo = OCA?.Files?.App?.currentFileList?.dirInfo
|| { path: '/', name: '' }
// Make sure we don't have double slashes
return `${currentDirInfo.path}/${currentDirInfo.name}`.replace(/\/\//gi, '/')
},
}, },
} }
</script> </script>
@ -225,6 +239,12 @@ export default {
padding: calc(var(--margin) * 2); padding: calc(var(--margin) * 2);
// Will be handled by the buttons // Will be handled by the buttons
padding-bottom: 0; padding-bottom: 0;
h2 {
text-align: center;
font-weight: bold;
margin: var(--margin) 0 calc(var(--margin) * 2);
}
} }
&__list { &__list {
@ -233,18 +253,20 @@ export default {
grid-auto-columns: 1fr; 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 // 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); max-width: calc(var(--fullwidth) * 6);
grid-template-columns: repeat(auto-fit, minmax(var(--fullwidth), 1fr)); grid-template-columns: repeat(auto-fit, var(--fullwidth));
// Make sure all rows are the same height // Make sure all rows are the same height
grid-auto-rows: 1fr; grid-auto-rows: 1fr;
// Center the columns set
justify-content: center;
} }
&__buttons { &__buttons {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: calc(var(--margin) * 2) var(--margin); padding: calc(var(--margin) * 2) var(--margin);
position: sticky; position: sticky;
// Make sure the templates list doesn't weirdly peak under when scrolled. Happens on some rare occasions bottom: 0;
bottom: -1px; background-image: linear-gradient(0, var(--gradient-main-background));
background-color: var(--color-main-background);
} }
// Make sure we're relative for the loading emptycontent on top // Make sure we're relative for the loading emptycontent on top

View File

@ -7,6 +7,9 @@
--color-main-background: #{$color-main-background}; --color-main-background: #{$color-main-background};
--color-main-background-translucent: #{$color-main-background-translucent}; --color-main-background-translucent: #{$color-main-background-translucent};
// To use like this: background-image: linear-gradient(0, var(--gradient-main-background));
--gradient-main-background: var(--color-main-background) 0%, var(--color-main-background-translucent) 85%, transparent 100%;
--color-background-hover: #{$color-background-hover}; --color-background-hover: #{$color-background-hover};
--color-background-dark: #{$color-background-dark}; --color-background-dark: #{$color-background-dark};
--color-background-darker: #{$color-background-darker}; --color-background-darker: #{$color-background-darker};

View File

@ -229,6 +229,7 @@ audio, canvas, embed, iframe, img, input, object, video {
@include icon-black-white('quota', 'actions', 1, true); @include icon-black-white('quota', 'actions', 1, true);
@include icon-black-white('rename', 'actions', 1, true); @include icon-black-white('rename', 'actions', 1, true);
@include icon-black-white('screen', 'actions', 1, true); @include icon-black-white('screen', 'actions', 1, true);
@include icon-black-white('template-add', 'actions', 1, true);
.icon-screen-white { .icon-screen-white {
filter: drop-shadow(1px 1px 4px var(--color-box-shadow)); filter: drop-shadow(1px 1px 4px var(--color-box-shadow));

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21.33 21.33" width="16" height="16"><path fill="none" d="M0 0h24v24H0z"/><path d="M15.33 18h-12V6H10V4H3.33a2 2 0 00-2 2v12c0 1.1.9 2 2 2h12a2 2 0 002-2v-6.67h-2zM17.33 1.33h-2V4h-2.66v2h2.66v2.67h2V6H20V4h-2.67z"/><path d="M5.33 14.33h8v2h-8zm8-1.33v-2h-8v2zm-8-5.33h8v2h-8z"/></svg>

After

Width:  |  Height:  |  Size: 337 B

View File

@ -34,6 +34,7 @@ use OCP\Files\GenericFileException;
use OCP\Files\IRootFolder; use OCP\Files\IRootFolder;
use OCP\Files\Node; use OCP\Files\Node;
use OCP\Files\NotFoundException; use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use OCP\Files\Template\CreatedFromTemplateEvent; use OCP\Files\Template\CreatedFromTemplateEvent;
use OCP\Files\Template\ICustomTemplateProvider; use OCP\Files\Template\ICustomTemplateProvider;
use OCP\Files\Template\ITemplateManager; use OCP\Files\Template\ITemplateManager;
@ -103,7 +104,15 @@ class TemplateManager implements ITemplateManager {
return $this->providers; return $this->providers;
} }
public function listCreators(): array { public function listCreators():? array {
if ($this->types === null) {
return null;
}
usort($this->types, function (TemplateFileCreator $a, TemplateFileCreator $b) {
return $a->getOrder() - $b->getOrder();
});
return array_map(function (TemplateFileCreator $entry) { return array_map(function (TemplateFileCreator $entry) {
return array_merge($entry->jsonSerialize(), [ return array_merge($entry->jsonSerialize(), [
'templates' => $this->getTemplateFiles($entry) 'templates' => $this->getTemplateFiles($entry)
@ -154,7 +163,10 @@ class TemplateManager implements ITemplateManager {
* @throws \OC\User\NoUserException * @throws \OC\User\NoUserException
*/ */
private function getTemplateFolder(): Node { private function getTemplateFolder(): Node {
return $this->rootFolder->getUserFolder($this->userId)->get($this->getTemplatePath()); if ($this->getTemplatePath() !== '') {
return $this->rootFolder->getUserFolder($this->userId)->get($this->getTemplatePath());
}
throw new NotFoundException();
} }
private function getTemplateFiles(TemplateFileCreator $type): array { private function getTemplateFiles(TemplateFileCreator $type): array {
@ -220,10 +232,10 @@ class TemplateManager implements ITemplateManager {
} }
public function getTemplatePath(): string { public function getTemplatePath(): string {
return $this->config->getUserValue($this->userId, 'core', 'templateDirectory', $this->l10n->t('Templates') . '/'); return $this->config->getUserValue($this->userId, 'core', 'templateDirectory', '');
} }
public function initializeTemplateDirectory(string $path = null, string $userId = null): void { public function initializeTemplateDirectory(string $path = null, string $userId = null, $copyTemplates = true): string {
if ($userId !== null) { if ($userId !== null) {
$this->userId = $userId; $this->userId = $userId;
} }
@ -232,6 +244,8 @@ class TemplateManager implements ITemplateManager {
$defaultTemplateDirectory = \OC::$SERVERROOT . '/core/skeleton/Templates'; $defaultTemplateDirectory = \OC::$SERVERROOT . '/core/skeleton/Templates';
$skeletonPath = $this->config->getSystemValue('skeletondirectory', $defaultSkeletonDirectory); $skeletonPath = $this->config->getSystemValue('skeletondirectory', $defaultSkeletonDirectory);
$skeletonTemplatePath = $this->config->getSystemValue('templatedirectory', $defaultTemplateDirectory); $skeletonTemplatePath = $this->config->getSystemValue('templatedirectory', $defaultTemplateDirectory);
$isDefaultSkeleton = $skeletonPath === $defaultSkeletonDirectory;
$isDefaultTemplates = $skeletonTemplatePath === $defaultTemplateDirectory;
$userLang = $this->l10nFactory->getUserLanguage(); $userLang = $this->l10nFactory->getUserLanguage();
try { try {
@ -239,39 +253,64 @@ class TemplateManager implements ITemplateManager {
$userFolder = $this->rootFolder->getUserFolder($this->userId); $userFolder = $this->rootFolder->getUserFolder($this->userId);
$userTemplatePath = $path ?? $l10n->t('Templates') . '/'; $userTemplatePath = $path ?? $l10n->t('Templates') . '/';
// All locations are default so we just need to rename the directory to the users language // Initial user setup without a provided path
if ($skeletonPath === $defaultSkeletonDirectory && $skeletonTemplatePath === $defaultTemplateDirectory && $userFolder->nodeExists('Templates')) { if ($path === null) {
$newPath = $userFolder->getPath() . '/' . $userTemplatePath; // All locations are default so we just need to rename the directory to the users language
if ($newPath !== $userFolder->get('Templates')->getPath()) { if ($isDefaultSkeleton && $isDefaultTemplates && $userFolder->nodeExists('Templates')) {
$userFolder->get('Templates')->move($newPath); $newPath = $userFolder->getPath() . '/' . $userTemplatePath;
if ($newPath !== $userFolder->get('Templates')->getPath()) {
$userFolder->get('Templates')->move($newPath);
}
$this->setTemplatePath($userTemplatePath);
return $userTemplatePath;
} }
$this->setTemplatePath($userTemplatePath);
return;
}
// A custom template directory is specified if ($isDefaultSkeleton && !empty($skeletonTemplatePath) && !$isDefaultTemplates && $userFolder->nodeExists('Templates')) {
if (!empty($skeletonTemplatePath) && $skeletonTemplatePath !== $defaultTemplateDirectory) {
// In case the shipped template files are in place we remove them
if ($skeletonPath === $defaultSkeletonDirectory && $userFolder->nodeExists('Templates')) {
$shippedSkeletonTemplates = $userFolder->get('Templates'); $shippedSkeletonTemplates = $userFolder->get('Templates');
$shippedSkeletonTemplates->delete(); $shippedSkeletonTemplates->delete();
} }
try {
$userFolder->get($userTemplatePath);
} catch (NotFoundException $e) {
$folder = $userFolder->newFolder($userTemplatePath);
$localizedSkeletonTemplatePath = $this->getLocalizedTemplatePath($skeletonTemplatePath, $userLang);
if (!empty($localizedSkeletonTemplatePath) && file_exists($localizedSkeletonTemplatePath)) {
\OC_Util::copyr($localizedSkeletonTemplatePath, $folder);
$userFolder->getStorage()->getScanner()->scan($userTemplatePath, Scanner::SCAN_RECURSIVE);
}
}
$this->setTemplatePath($userTemplatePath);
} }
try {
$folder = $userFolder->newFolder($userTemplatePath);
} catch (NotPermittedException $e) {
$folder = $userFolder->get($userTemplatePath);
}
$folderIsEmpty = count($folder->getDirectoryListing()) === 0;
if (!$copyTemplates) {
$this->setTemplatePath($userTemplatePath);
return $userTemplatePath;
}
if (!$isDefaultTemplates && $folderIsEmpty) {
$localizedSkeletonTemplatePath = $this->getLocalizedTemplatePath($skeletonTemplatePath, $userLang);
if (!empty($localizedSkeletonTemplatePath) && file_exists($localizedSkeletonTemplatePath)) {
\OC_Util::copyr($localizedSkeletonTemplatePath, $folder);
$userFolder->getStorage()->getScanner()->scan($userTemplatePath, Scanner::SCAN_RECURSIVE);
$this->setTemplatePath($userTemplatePath);
return $userTemplatePath;
}
}
if ($path !== null && $isDefaultSkeleton && $isDefaultTemplates && $folderIsEmpty) {
$localizedSkeletonPath = $this->getLocalizedTemplatePath($skeletonPath . '/Templates', $userLang);
if (!empty($localizedSkeletonPath) && file_exists($localizedSkeletonPath)) {
\OC_Util::copyr($localizedSkeletonPath, $folder);
$userFolder->getStorage()->getScanner()->scan($userTemplatePath, Scanner::SCAN_RECURSIVE);
$this->setTemplatePath($userTemplatePath);
return $userTemplatePath;
}
}
$this->setTemplatePath($path ?? '');
return $this->getTemplatePath();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->logger->error('Failed to rename templates directory to user language ' . $userLang . ' for ' . $userId, ['app' => 'files_templates']); $this->logger->error('Failed to initialize templates directory to user language ' . $userLang . ' for ' . $userId, ['app' => 'files_templates', 'exception' => $e]);
} }
$this->setTemplatePath('');
return $this->getTemplatePath();
} }
private function getLocalizedTemplatePath(string $skeletonTemplatePath, string $userLang) { private function getLocalizedTemplatePath(string $skeletonTemplatePath, string $userLang) {

View File

@ -449,9 +449,9 @@ class OC_Util {
// update the file cache // update the file cache
$userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE); $userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
/** @var ITemplateManager $templateManaer */ /** @var ITemplateManager $templateManager */
$templateManaer = \OC::$server->get(ITemplateManager::class); $templateManager = \OC::$server->get(ITemplateManager::class);
$templateManaer->initializeTemplateDirectory(null, $userId); $templateManager->initializeTemplateDirectory(null, $userId);
} }
} }

View File

@ -56,7 +56,7 @@ interface ITemplateManager {
* @return array * @return array
* @since 21.0.0 * @since 21.0.0
*/ */
public function listCreators(): array; public function listCreators():? array;
/** /**
* @return bool * @return bool
@ -82,7 +82,7 @@ interface ITemplateManager {
* @param string|null $userId * @param string|null $userId
* @since 21.0.0 * @since 21.0.0
*/ */
public function initializeTemplateDirectory(string $path = null, string $userId = null): void; public function initializeTemplateDirectory(string $path = null, string $userId = null, $copyTemplates = true): string;
/** /**
* @param string $filePath * @param string $filePath

View File

@ -35,6 +35,7 @@ final class TemplateFileCreator implements \JsonSerializable {
protected $fileExtension; protected $fileExtension;
protected $iconClass; protected $iconClass;
protected $ratio = null; protected $ratio = null;
protected $order = 100;
/** /**
* @since 21.0.0 * @since 21.0.0
@ -80,11 +81,27 @@ final class TemplateFileCreator implements \JsonSerializable {
/** /**
* @since 21.0.0 * @since 21.0.0
*/ */
public function setRatio(float $ratio) { public function setRatio(float $ratio): TemplateFileCreator {
$this->ratio = $ratio; $this->ratio = $ratio;
return $this; return $this;
} }
/**
* @param int $order order in which the create action shall be listed
* @since 21.0.0
*/
public function setOrder(int $order): TemplateFileCreator {
$this->order = $order;
return $this;
}
/**
* @since 21.0.0
*/
public function getOrder(): int {
return $this->order;
}
/** /**
* @since 21.0.0 * @since 21.0.0
*/ */