add font upload, list, delete
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
This commit is contained in:
Родитель
25ae0782c8
Коммит
df776ef81a
|
@ -52,6 +52,10 @@ return [
|
|||
['name' => 'settings#updateWatermarkSettings', 'url' => 'settings/watermark', 'verb' => 'POST'],
|
||||
['name' => 'settings#checkSettings', 'url' => 'settings/check', 'verb' => 'GET'],
|
||||
['name' => 'settings#demoServers', 'url' => 'settings/demo', 'verb' => 'GET'],
|
||||
['name' => 'settings#getFontNames', 'url' => 'settings/fonts', 'verb' => 'GET'],
|
||||
['name' => 'settings#getFontFile', 'url' => 'settings/fonts/{name}', 'verb' => 'GET'],
|
||||
['name' => 'settings#deleteFontFile', 'url' => 'settings/fonts/{name}', 'verb' => 'DELETE'],
|
||||
['name' => 'settings#uploadFontFile', 'url' => 'settings/fonts', 'verb' => 'POST'],
|
||||
|
||||
//Mobile access
|
||||
['name' => 'directView#show', 'url' => '/direct/{token}', 'verb' => 'GET'],
|
||||
|
|
|
@ -11,8 +11,11 @@
|
|||
|
||||
namespace OCA\Richdocuments\Controller;
|
||||
|
||||
use Exception;
|
||||
use OCA\Richdocuments\Service\CapabilitiesService;
|
||||
use OCA\Richdocuments\Service\DemoService;
|
||||
use OCA\Richdocuments\Service\FontService;
|
||||
use OCA\Richdocuments\UploadException;
|
||||
use OCA\Richdocuments\WOPI\DiscoveryManager;
|
||||
use OCA\Richdocuments\WOPI\Parser;
|
||||
use \OCP\AppFramework\Controller;
|
||||
|
@ -26,8 +29,13 @@ use \OCP\IL10N;
|
|||
use OCA\Richdocuments\AppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\PreConditionNotMetException;
|
||||
use OCP\Util;
|
||||
|
||||
class SettingsController extends Controller{
|
||||
public const FONT_MIME_TYPES = [
|
||||
'font/ttf',
|
||||
];
|
||||
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
/** @var AppConfig */
|
||||
|
@ -46,6 +54,10 @@ class SettingsController extends Controller{
|
|||
private $demoService;
|
||||
/** @var ILogger */
|
||||
private $logger;
|
||||
/**
|
||||
* @var FontService
|
||||
*/
|
||||
private $fontService;
|
||||
|
||||
public function __construct($appName,
|
||||
IRequest $request,
|
||||
|
@ -56,6 +68,7 @@ class SettingsController extends Controller{
|
|||
Parser $wopiParser,
|
||||
CapabilitiesService $capabilitiesService,
|
||||
DemoService $demoService,
|
||||
FontService $fontService,
|
||||
ILogger $logger,
|
||||
$userId
|
||||
) {
|
||||
|
@ -69,6 +82,7 @@ class SettingsController extends Controller{
|
|||
$this->demoService = $demoService;
|
||||
$this->logger = $logger;
|
||||
$this->userId = $userId;
|
||||
$this->fontService = $fontService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -284,4 +298,94 @@ class SettingsController extends Controller{
|
|||
return new JSONResponse($response);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @PublicPage
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function getFontNames(): JSONResponse {
|
||||
$response = $this->fontService->getFontFileNames();
|
||||
return new JSONResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @PublicPage
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
* @param string $name
|
||||
* @return DataResponse
|
||||
*/
|
||||
public function getFontFile(string $name): DataResponse {
|
||||
$fontFileContent = $this->fontService->getFontFile($name);
|
||||
return new DataResponse($fontFileContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return DataResponse
|
||||
*/
|
||||
public function deleteFontFile(string $name): DataResponse {
|
||||
$this->fontService->deleteFontFile($name);
|
||||
return new DataResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function uploadFontFile(): JSONResponse {
|
||||
try {
|
||||
$file = $this->getUploadedFile('fontfile');
|
||||
if (isset($file['tmp_name'], $file['name'], $file['type'])) {
|
||||
if (!in_array($file['type'], self::FONT_MIME_TYPES, true)) {
|
||||
return new JSONResponse(['error' => 'Font type not supported'], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
$newFileResource = fopen($file['tmp_name'], 'rb');
|
||||
if ($newFileResource === false) {
|
||||
throw new Exception('Could not read file');
|
||||
}
|
||||
$newFileName = $file['name'];
|
||||
$uploadResult = $this->fontService->uploadFontFile($newFileName, $newFileResource);
|
||||
return new JSONResponse($uploadResult);
|
||||
}
|
||||
return new JSONResponse(['error' => 'No uploaded file'], Http::STATUS_BAD_REQUEST);
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('Upload error', ['exception' => $e]);
|
||||
return new JSONResponse(['error' => 'Upload error'], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return array
|
||||
* @throws UploadException
|
||||
*/
|
||||
private function getUploadedFile(string $key): array {
|
||||
$file = $this->request->getUploadedFile($key);
|
||||
$error = null;
|
||||
$phpFileUploadErrors = [
|
||||
UPLOAD_ERR_OK => $this->l10n->t('The file was uploaded'),
|
||||
UPLOAD_ERR_INI_SIZE => $this->l10n->t('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
|
||||
UPLOAD_ERR_FORM_SIZE => $this->l10n->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
|
||||
UPLOAD_ERR_PARTIAL => $this->l10n->t('The file was only partially uploaded'),
|
||||
UPLOAD_ERR_NO_FILE => $this->l10n->t('No file was uploaded'),
|
||||
UPLOAD_ERR_NO_TMP_DIR => $this->l10n->t('Missing a temporary folder'),
|
||||
UPLOAD_ERR_CANT_WRITE => $this->l10n->t('Could not write file to disk'),
|
||||
UPLOAD_ERR_EXTENSION => $this->l10n->t('A PHP extension stopped the file upload'),
|
||||
];
|
||||
|
||||
if (empty($file)) {
|
||||
$error = $this->l10n->t('No file uploaded or file size exceeds maximum of %s', [Util::humanFileSize(Util::uploadLimit())]);
|
||||
}
|
||||
if (!empty($file) && array_key_exists('error', $file) && $file['error'] !== UPLOAD_ERR_OK) {
|
||||
$error = $phpFileUploadErrors[$file['error']];
|
||||
}
|
||||
if ($error !== null) {
|
||||
throw new UploadException($error);
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2022 Julien Veyssier <eneiluj@posteo.net>
|
||||
*
|
||||
* @author Julien Veyssier <eneiluj@posteo.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Richdocuments\Service;
|
||||
|
||||
|
||||
use OCP\Files\IAppData;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\SimpleFS\ISimpleFile;
|
||||
use OCP\Files\SimpleFS\ISimpleFolder;
|
||||
|
||||
class FontService {
|
||||
|
||||
/**
|
||||
* @var IAppData
|
||||
*/
|
||||
private $appData;
|
||||
|
||||
public function __construct(IAppData $appData) {
|
||||
$this->appData = $appData;
|
||||
}
|
||||
|
||||
private function getFontAppDataDir(): ISimpleFolder {
|
||||
try {
|
||||
return $this->appData->getFolder('fonts');
|
||||
} catch (NotFoundException $e) {
|
||||
return $this->appData->newFolder('fonts');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of available font files
|
||||
*
|
||||
*/
|
||||
public function getFontFileNames(): array {
|
||||
$fontDir = $this->getFontAppDataDir();
|
||||
return array_map(
|
||||
function (ISimpleFile $f) use ($fontDir) {
|
||||
return $f->getName();
|
||||
},
|
||||
$fontDir->getDirectoryListing()
|
||||
);
|
||||
}
|
||||
|
||||
public function uploadFontFile(string $fileName, $newFileResource): array {
|
||||
$fontDir = $this->getFontAppDataDir();
|
||||
$newFile = $fontDir->newFile($fileName, $newFileResource);
|
||||
return [
|
||||
'size' => $newFile->getSize(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getFontFile(string $fileName): string {
|
||||
$fontDir = $this->getFontAppDataDir();
|
||||
return $fontDir->getFile($fileName)->getContent();
|
||||
}
|
||||
|
||||
public function deleteFontFile(string $fileName): void {
|
||||
$fontDir = $this->getFontAppDataDir();
|
||||
if ($fontDir->fileExists($fileName)) {
|
||||
$fontDir->getFile($fileName)->delete();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ namespace OCA\Richdocuments\Settings;
|
|||
use OCA\Richdocuments\AppConfig;
|
||||
use OCA\Richdocuments\Service\CapabilitiesService;
|
||||
use OCA\Richdocuments\Service\DemoService;
|
||||
use OCA\Richdocuments\Service\FontService;
|
||||
use OCA\Richdocuments\Service\InitialStateService;
|
||||
use OCA\Richdocuments\TemplateManager;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
|
@ -51,6 +52,10 @@ class Admin implements ISettings {
|
|||
|
||||
/** @var InitialStateService */
|
||||
private $initialState;
|
||||
/**
|
||||
* @var FontService
|
||||
*/
|
||||
private $fontService;
|
||||
|
||||
public function __construct(
|
||||
IConfig $config,
|
||||
|
@ -58,6 +63,7 @@ class Admin implements ISettings {
|
|||
TemplateManager $manager,
|
||||
CapabilitiesService $capabilitiesService,
|
||||
DemoService $demoService,
|
||||
FontService $fontService,
|
||||
InitialStateService $initialStateService
|
||||
) {
|
||||
$this->config = $config;
|
||||
|
@ -66,6 +72,7 @@ class Admin implements ISettings {
|
|||
$this->capabilitiesService = $capabilitiesService;
|
||||
$this->demoService = $demoService;
|
||||
$this->initialState = $initialStateService;
|
||||
$this->fontService = $fontService;
|
||||
}
|
||||
|
||||
public function getForm() {
|
||||
|
@ -89,7 +96,8 @@ class Admin implements ISettings {
|
|||
'demo_servers' => $this->demoService->fetchDemoServers(),
|
||||
'web_server' => strtolower($_SERVER['SERVER_SOFTWARE']),
|
||||
'os_family' => PHP_VERSION_ID >= 70200 ? PHP_OS_FAMILY : PHP_OS,
|
||||
'platform' => php_uname('m')
|
||||
'platform' => php_uname('m'),
|
||||
'fonts' => $this->fontService->getFontFileNames(),
|
||||
],
|
||||
],
|
||||
'blank'
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2022 Julien Veyssier <eneiluj@posteo.net>
|
||||
*
|
||||
* @author Julien Veyssier <eneiluj@posteo.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Richdocuments;
|
||||
|
||||
class UploadException extends \Exception {
|
||||
}
|
|
@ -273,6 +273,16 @@
|
|||
:hint="t('richdocuments', 'List of IPV4 and IPV6 IP-addresses and subnets that are allowed to perform requests of the WOPI endpoints. If no allow list is specified all hosts will be allowed. E.g. 10.0.0.20,10.0.4.0/24')"
|
||||
:disabled="updating"
|
||||
@update="updateWopiAllowlist" />
|
||||
|
||||
<SettingsInputFile
|
||||
:label="t('richdocuments', 'Upload extra font file')"
|
||||
:uploading="uploadingFont"
|
||||
:mimetypes="fontMimes"
|
||||
@change="uploadFont" />
|
||||
<SettingsFontList
|
||||
:fonts="settings.fonts"
|
||||
:label="t('richdocuments', 'Available fonts')"
|
||||
@deleted="onFontDeleted" />
|
||||
</div>
|
||||
|
||||
<div v-if="isSetup" id="secure-view-settings" class="section">
|
||||
|
@ -355,11 +365,14 @@ import { showWarning } from '@nextcloud/dialogs'
|
|||
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
|
||||
import Modal from '@nextcloud/vue/dist/Components/Modal'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import SettingsCheckbox from './SettingsCheckbox'
|
||||
import SettingsInputText from './SettingsInputText'
|
||||
import SettingsSelectTag from './SettingsSelectTag'
|
||||
import SettingsSelectGroup from './SettingsSelectGroup'
|
||||
import SettingsExternalApps from './SettingsExternalApps'
|
||||
import SettingsInputFile from './SettingsInputFile'
|
||||
import SettingsFontList from './SettingsFontList'
|
||||
|
||||
import '@nextcloud/dialogs/styles/toast.scss'
|
||||
|
||||
|
@ -367,6 +380,7 @@ const SERVER_STATE_OK = 0
|
|||
const SERVER_STATE_LOADING = 1
|
||||
const SERVER_STATE_CONNECTION_ERROR = 2
|
||||
const PROTOCOL_MISMATCH = 3
|
||||
const fontMimes = ['font/ttf']
|
||||
|
||||
export default {
|
||||
name: 'AdminSettings',
|
||||
|
@ -377,6 +391,8 @@ export default {
|
|||
SettingsSelectGroup,
|
||||
Multiselect,
|
||||
SettingsExternalApps,
|
||||
SettingsInputFile,
|
||||
SettingsFontList,
|
||||
Modal,
|
||||
},
|
||||
props: {
|
||||
|
@ -401,6 +417,8 @@ export default {
|
|||
appUrl: generateUrl('/settings/apps/app-bundles/richdocumentscode'),
|
||||
approvedDemoModal: false,
|
||||
updating: false,
|
||||
uploadingFont: false,
|
||||
fontMimes,
|
||||
groups: [],
|
||||
tags: [],
|
||||
uiVisible: {
|
||||
|
@ -425,6 +443,7 @@ export default {
|
|||
allTagsList: [],
|
||||
text: '',
|
||||
},
|
||||
fonts: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
@ -472,6 +491,7 @@ export default {
|
|||
}
|
||||
Vue.set(this.settings, 'edit_groups', this.settings.edit_groups ? this.settings.edit_groups.split('|') : null)
|
||||
Vue.set(this.settings, 'use_groups', this.settings.use_groups ? this.settings.use_groups.split('|') : null)
|
||||
Vue.set(this.settings, 'fonts', this.initial.fonts ? this.initial.fonts : [])
|
||||
|
||||
this.uiVisible.canonical_webroot = !!(this.settings.canonical_webroot && this.settings.canonical_webroot !== '')
|
||||
this.uiVisible.external_apps = !!(this.settings.external_apps && this.settings.external_apps !== '')
|
||||
|
@ -639,6 +659,43 @@ export default {
|
|||
|
||||
return url.protocol
|
||||
},
|
||||
uploadFont(event) {
|
||||
// TODO define font format list
|
||||
const files = event.target.files
|
||||
const file = files[0]
|
||||
if (!fontMimes.includes(file.type)) {
|
||||
showError(t('text', 'Font format not supported'))
|
||||
return
|
||||
}
|
||||
this.uploadingFont = true
|
||||
|
||||
// Clear input to ensure that the change event will be emitted if
|
||||
// the same file is picked again.
|
||||
event.target.value = ''
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('fontfile', file)
|
||||
const url = generateUrl('/apps/richdocuments/settings/fonts')
|
||||
axios.post(url, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
}).then((response) => {
|
||||
// TODO reload font list
|
||||
this.settings.fonts.push(file.name)
|
||||
}).catch((error) => {
|
||||
console.error(error)
|
||||
showError(error?.response?.data?.error)
|
||||
}).then(() => {
|
||||
this.uploadingFont = false
|
||||
})
|
||||
},
|
||||
onFontDeleted(name) {
|
||||
const index = this.settings.fonts.indexOf(name)
|
||||
if (index !== -1) {
|
||||
this.settings.fonts.splice(index, 1)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2022 Julien Veyssier <eneiluj@posteo.net>
|
||||
-
|
||||
- @author Julien Veyssier <eneiluj@posteo.net>
|
||||
-
|
||||
- @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 class="settings-font">
|
||||
<label>{{ name }}</label>
|
||||
<button
|
||||
:class="{
|
||||
'icon-delete': true,
|
||||
svg: true,
|
||||
'loading-small': disabled,
|
||||
}"
|
||||
:disabled="disabled"
|
||||
@click="onDeleteClick" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from '@nextcloud/axios'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
|
||||
export default {
|
||||
name: 'SettingsFont',
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
disabled: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
onDeleteClick() {
|
||||
console.debug('DELETE', this.name)
|
||||
this.disabled = true
|
||||
const url = generateUrl('/apps/richdocuments/settings/fonts/') + encodeURIComponent(this.name)
|
||||
axios.delete(url).then((response) => {
|
||||
this.$emit('deleted')
|
||||
}).catch((error) => {
|
||||
console.error(error)
|
||||
showError(error?.response?.data?.error)
|
||||
}).then(() => {
|
||||
this.disabled = false
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.settings-font {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,77 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2022 Julien Veyssier <eneiluj@posteo.net>
|
||||
-
|
||||
- @author Julien Veyssier <eneiluj@posteo.net>
|
||||
-
|
||||
- @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 class="settings-entry">
|
||||
<b>{{ label }}</b>
|
||||
<SettingsFont v-for="name in sortedFonts"
|
||||
:key="name"
|
||||
:name="name"
|
||||
@deleted="$emit('deleted', name)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SettingsFont from './SettingsFont'
|
||||
|
||||
export default {
|
||||
name: 'SettingsFontList',
|
||||
components: {
|
||||
SettingsFont,
|
||||
},
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
fonts: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sortedFonts() {
|
||||
return this.fonts.slice().sort((a, b) => {
|
||||
const la = a.toLowerCase()
|
||||
const lb = b.toLowerCase()
|
||||
return la > lb
|
||||
? 1
|
||||
: la < lb
|
||||
? -1
|
||||
: 0
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
label {
|
||||
padding: 15px 0 15px 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,104 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2022 Julien Veyssier <eneiluj@posteo.net>
|
||||
-
|
||||
- @author Julien Veyssier <eneiluj@posteo.net>
|
||||
-
|
||||
- @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 class="settings-entry">
|
||||
<label :for="id">{{ label }}</label><br>
|
||||
<button
|
||||
id="uploadlogo"
|
||||
:class="{
|
||||
'icon-upload': true,
|
||||
svg: true,
|
||||
'loading-small': uploading,
|
||||
}"
|
||||
:disabled="uploading"
|
||||
@click="onUploadClick" />
|
||||
<em v-if="hint !== ''">{{ hint }}</em>
|
||||
<input :id="id"
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
aria-hidden="true"
|
||||
class="hidden-visually"
|
||||
:accept="acceptedMimeTypes"
|
||||
@change="$emit('change', $event)">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let uuid = 0
|
||||
export default {
|
||||
name: 'SettingsInputFile',
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
hint: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
uploading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
mimetypes: {
|
||||
type: Array,
|
||||
default: () => ['*'],
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
id() {
|
||||
return 'settings-file-' + this.uuid
|
||||
},
|
||||
acceptedMimeTypes() {
|
||||
return this.mimetypes.join(',')
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
beforeCreate() {
|
||||
this.uuid = uuid.toString()
|
||||
uuid += 1
|
||||
},
|
||||
methods: {
|
||||
onUploadClick() {
|
||||
this.$refs.fileInput.click()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.settings-entry {
|
||||
padding: 15px 0 15px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
Загрузка…
Ссылка в новой задаче