mirror of
https://github.com/typemill/typemill.git
synced 2025-07-31 03:10:19 +02:00
V2.4.0 finish readymade themes
This commit is contained in:
@@ -8,6 +8,7 @@ use Typemill\Models\Validation;
|
||||
use Typemill\Models\Extension;
|
||||
use Typemill\Models\Settings;
|
||||
use Typemill\Static\Translations;
|
||||
use Typemill\Static\Slug;
|
||||
|
||||
class ControllerApiSystemThemes extends Controller
|
||||
{
|
||||
@@ -69,4 +70,124 @@ class ControllerApiSystemThemes extends Controller
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
|
||||
public function updateReadymade(Request $request, Response $response)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
$themename = $params['theme'] ?? false;
|
||||
$themeinput = $params['settings'] ?? false;
|
||||
$readymadetitle = $params['readymadetitle'] ?? false;
|
||||
$readymadedesc = $params['readymadedesc'] ?? false;
|
||||
|
||||
$extension = new Extension();
|
||||
$formdefinitions = $extension->getThemeDefinition($themename);
|
||||
$formdefinitions = $this->addDatasets($formdefinitions['forms']['fields']);
|
||||
$themedata = [];
|
||||
|
||||
# validate input
|
||||
$validator = new Validation();
|
||||
$validatedOutput = $validator->recursiveValidation($formdefinitions, $themeinput);
|
||||
if(!empty($validator->errors))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => Translations::translate('Please correct your input.'),
|
||||
'errors' => $validator->errors
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
$validator = $validator->returnValidator(['themename' => $themename, 'title' => $readymadetitle, 'description' => $readymadedesc]);
|
||||
$validator->rule('required', ['themename', 'title', 'description']);
|
||||
$validator->rule('lengthBetween', 'description', 3, 100)->message("Length between 3 - 100");
|
||||
$validator->rule('noHTML', 'description')->message(" contains HTML");
|
||||
$validator->rule('regex', 'themename', '/^[a-zA-Z0-9\-]{3,40}$/')->message("only a-zA-Z0-9 with 3 - 40 chars allowed");
|
||||
$validator->rule('regex', 'title', '/^[a-zA-Z0-9\-\_ ]{3,20}$/')->message("only a-zA-Z0-9 with 3 - 20 chars allowed");
|
||||
|
||||
if(!$validator->validate())
|
||||
{
|
||||
$message = 'There was an error, please try again';
|
||||
$errors = $validator->errors();
|
||||
$firstKey = array_key_first($errors);
|
||||
if(isset($errors[$firstKey][0]))
|
||||
{
|
||||
$message = $firstKey . ': ' . $errors[$firstKey][0];
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => $message
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
$readymadeSlug = Slug::createSlug($readymadetitle);
|
||||
|
||||
$readymade = [
|
||||
$readymadeSlug => [
|
||||
'name' => $readymadetitle,
|
||||
'description' => $readymadedesc,
|
||||
'delete' => true,
|
||||
'settings' => $validatedOutput
|
||||
]
|
||||
];
|
||||
|
||||
$extension->storeThemeReadymade($themename, $readymadeSlug, $readymade);
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => Translations::translate('Readymade has been saved'),
|
||||
'readymade' => $readymade,
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
|
||||
public function deleteReadymade(Request $request, Response $response)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
$themename = $params['theme'] ?? false;
|
||||
$readymadeslug = $params['readymadeslug'] ?? false;
|
||||
|
||||
$validation = new Validation();
|
||||
$validator = $validation->returnValidator($params);
|
||||
$validator->rule('required', ['theme', 'readymadeslug']);
|
||||
$validator->rule('regex', 'theme', '/^[a-zA-Z0-9\-]{3,40}$/')->message("only a-zA-Z0-9 with 3 - 40 chars allowed");
|
||||
$validator->rule('regex', 'readymadeslug', '/^[a-zA-Z0-9\-\_ ]{3,20}$/')->message("only a-zA-Z0-9 with 3 - 20 chars allowed");
|
||||
|
||||
if(!$validator->validate())
|
||||
{
|
||||
$message = 'There was an error, please try again';
|
||||
$errors = $validator->errors();
|
||||
$firstKey = array_key_first($errors);
|
||||
if(isset($errors[$firstKey][0]))
|
||||
{
|
||||
$message = $firstKey . ': ' . $errors[$firstKey][0];
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => $message
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
$extension = new Extension();
|
||||
$result = $extension->deleteThemeReadymade($themename, $readymadeslug);
|
||||
|
||||
if($result !== true)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => Translations::translate('We could not delete the readymade.'),
|
||||
'errors' => $result
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => Translations::translate('readymade has been deleted')
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
}
|
||||
|
@@ -74,7 +74,6 @@ class ControllerWebSystem extends Controller
|
||||
$extension = new Extension();
|
||||
$themeDefinitions = $extension->getThemeDetails($this->settings['theme']);
|
||||
$themeSettings = $extension->getThemeSettings($this->settings['themes']);
|
||||
$readymades = [];
|
||||
|
||||
# add userroles and other datasets
|
||||
foreach($themeDefinitions as $name => $definitions)
|
||||
@@ -95,10 +94,10 @@ class ControllerWebSystem extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($definitions['readymades']))
|
||||
{
|
||||
$readymades[$name] = $definitions['readymades'];
|
||||
}
|
||||
# get stored indvidual readymades
|
||||
$builtinReadymades = $definitions['readymades'] ?? [];
|
||||
$individualReadymades = $extension->getThemeReadymades($name);
|
||||
$themeDefinitions[$name]['readymades'] = $builtinReadymades + $individualReadymades;
|
||||
}
|
||||
|
||||
$license = [];
|
||||
@@ -115,7 +114,6 @@ class ControllerWebSystem extends Controller
|
||||
'systemnavi' => $systemNavigation,
|
||||
'settings' => $themeSettings,
|
||||
'definitions' => $themeDefinitions,
|
||||
'readymades' => $readymades,
|
||||
'theme' => $this->settings['theme'],
|
||||
'license' => $license,
|
||||
'labels' => $this->c->get('translations'),
|
||||
|
@@ -99,6 +99,50 @@ class Extension
|
||||
return $themeSettings;
|
||||
}
|
||||
|
||||
public function getThemeReadymades($themeName)
|
||||
{
|
||||
$folder = 'readymades' . DIRECTORY_SEPARATOR . $themeName;
|
||||
$readymadeFolder = $this->storage->getFolderPath('dataFolder', $folder);
|
||||
|
||||
$readymadeFiles = scandir($readymadeFolder);
|
||||
|
||||
$readymades = [];
|
||||
foreach($readymadeFiles as $readymadeFile)
|
||||
{
|
||||
if (!in_array($readymadeFile, array(".","..")) && substr($readymadeFile, 0, 1) != '.')
|
||||
{
|
||||
$readymadeData = $this->storage->getYaml('dataFolder', $folder, $readymadeFile);
|
||||
if($readymadeData && !empty($readymadeData))
|
||||
{
|
||||
$readymades = $readymades + $readymadeData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $readymades;
|
||||
}
|
||||
|
||||
public function storeThemeReadymade($themeName, $readymadeName, $data)
|
||||
{
|
||||
$folder = 'readymades' . DIRECTORY_SEPARATOR . $themeName;
|
||||
|
||||
$result = $this->storage->updateYaml('dataFolder', $folder, $readymadeName . '.yaml', $data);
|
||||
}
|
||||
|
||||
public function deleteThemeReadymade($themeName, $readymadeName)
|
||||
{
|
||||
$folder = 'readymades' . DIRECTORY_SEPARATOR . $themeName;
|
||||
|
||||
$result = $this->storage->deleteFile('dataFolder', $folder, $readymadeName . '.yaml');
|
||||
|
||||
if(!$result)
|
||||
{
|
||||
return $this->storage->getError();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getPluginDetails($userSettings = NULL)
|
||||
{
|
||||
$plugins = $this->getPlugins();
|
||||
|
@@ -32,7 +32,6 @@
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
.fade-enter-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
@@ -46,6 +45,19 @@
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade-item {
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
.editableinput {
|
||||
border: none;
|
||||
outline: none;
|
||||
|
@@ -718,6 +718,18 @@ video {
|
||||
top: 2.5rem;
|
||||
}
|
||||
|
||||
.left-1 {
|
||||
left: 0.25rem;
|
||||
}
|
||||
|
||||
.right-1 {
|
||||
right: 0.25rem;
|
||||
}
|
||||
|
||||
.bottom-1 {
|
||||
bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.z-10 {
|
||||
z-index: 10;
|
||||
}
|
||||
@@ -746,6 +758,10 @@ video {
|
||||
margin: 0.25rem;
|
||||
}
|
||||
|
||||
.m-2 {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.my-2 {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
@@ -826,6 +842,10 @@ video {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.ml-2 {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.mb-16 {
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
@@ -850,10 +870,6 @@ video {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.ml-2 {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
@@ -886,6 +902,10 @@ video {
|
||||
margin-top: 1.75rem;
|
||||
}
|
||||
|
||||
.mt-auto {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
@@ -962,6 +982,14 @@ video {
|
||||
height: 12rem;
|
||||
}
|
||||
|
||||
.h-64 {
|
||||
height: 16rem;
|
||||
}
|
||||
|
||||
.h-40 {
|
||||
height: 10rem;
|
||||
}
|
||||
|
||||
.max-h-80 {
|
||||
max-height: 20rem;
|
||||
}
|
||||
@@ -1038,14 +1066,14 @@ video {
|
||||
width: 83.333333%;
|
||||
}
|
||||
|
||||
.w-10 {
|
||||
width: 2.5rem;
|
||||
}
|
||||
|
||||
.w-3\/5 {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.w-7 {
|
||||
width: 1.75rem;
|
||||
}
|
||||
|
||||
.w-3\/4 {
|
||||
width: 75%;
|
||||
}
|
||||
@@ -1066,8 +1094,8 @@ video {
|
||||
width: 91.666667%;
|
||||
}
|
||||
|
||||
.w-7 {
|
||||
width: 1.75rem;
|
||||
.w-10 {
|
||||
width: 2.5rem;
|
||||
}
|
||||
|
||||
.max-w-md {
|
||||
@@ -1102,6 +1130,10 @@ video {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.border-collapse {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
@@ -1216,10 +1248,6 @@ video {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.border {
|
||||
border-width: 1px;
|
||||
}
|
||||
@@ -1662,10 +1690,6 @@ video {
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
|
||||
.pb-1 {
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -1798,16 +1822,16 @@ video {
|
||||
color: rgb(120 113 108 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-stone-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(214 211 209 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-stone-700 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(68 64 60 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-stone-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(214 211 209 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-red-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(239 68 68 / var(--tw-text-opacity));
|
||||
@@ -2013,6 +2037,11 @@ video {
|
||||
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-rose-600:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(225 29 72 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:text-stone-800:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(41 37 36 / var(--tw-text-opacity));
|
||||
@@ -2047,6 +2076,12 @@ video {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.hover\:shadow-lg:hover {
|
||||
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.focus\:border-blue-600:focus {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(37 99 235 / var(--tw-border-opacity));
|
||||
@@ -2321,6 +2356,10 @@ video {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.lg\:w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.lg\:flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ const app = Vue.createApp({
|
||||
template: `<Transition name="initial" appear>
|
||||
<div class="w-full">
|
||||
<ul>
|
||||
<li v-for="(theme,themename) in formDefinitions" class="w-full my-8 bg-stone-100 dark:bg-stone-700 border border-stone-200">
|
||||
<li v-for="(theme,themename) in formDefinitions" :key="themename" class="w-full my-8 bg-stone-100 dark:bg-stone-700 border border-stone-200">
|
||||
<p v-if="versions[themename] !== undefined"><a href="https://themes.typemill.net" class="block p-2 text-center bg-rose-500 text-white">Please update to version {{ versions[themename].version }}</a></p>
|
||||
<div class="flex justify-between w-full px-8 py-3 border-b border-white" :class="getActiveClass(themename)">
|
||||
<p class="py-2">License: {{ theme.license }}</p>
|
||||
@@ -35,31 +35,56 @@ const app = Vue.createApp({
|
||||
</div>
|
||||
</div>
|
||||
<form class="w-full p-8" v-if="current == themename">
|
||||
<!-- <div v-if="theme.readymadesNext">
|
||||
<fieldset class="flex flex-wrap justify-between block border-2 border-stone-200 p-4 my-8">
|
||||
<div v-if="theme.readymades">
|
||||
<fieldset class="block border-2 border-stone-200 p-4 my-8">
|
||||
<legend class="text-lg font-medium">Readymades</legend>
|
||||
<p class="w-full bg-stone-200 mb p-2">Readymades help you to setup your theme with prefilled settings. Load some readymades, check out the frontend and adjust the settings as needed. Find more informations about specific readymades on the <a class="text-teal-500" :href="themeurl(themename)">theme's website.</p>
|
||||
<p class="w-full text-center mb-4 p-2 bg-rose-500 text-white">If you load and save a readymade, then your individual settins will be overwritten and are lost!</p>
|
||||
<ul class="flex flex-wrap justify-between">
|
||||
<li class="w-1/3 p-2 flex">
|
||||
<div class="border-2 border-stone-200">
|
||||
<button class="w-full center bg-stone-200 p-2 hover:bg-stone-300 transition duration-100" @click.prevent="loadReadymade('individual')">Load individual</button>
|
||||
<div class="p-3">
|
||||
<p>Load your stored individual settings.</p>
|
||||
<p class="w-full mb p-2">Readymades are predefined settings. Store your own readymades or load readymades to quickly setup your theme.</p>
|
||||
<ul>
|
||||
<transition-group name="fade" tag="ul" class="flex flex-wrap">
|
||||
<li class="w-1/3 p-2" v-for="(readysetup,readyname) in theme.readymades" :key="readyname" class="fade-item">
|
||||
<div class="border-2 border-stone-200 hover:shadow-lg transition duration-100 ease-in-out">
|
||||
<div class="w-full font-medium p-2 text-center bg-stone-200">{{ readysetup.name }}</div>
|
||||
<div class="p-3 h-40">
|
||||
<p>{{ readysetup.description }}</p>
|
||||
</div>
|
||||
<div v-if="readysetup.delete" class="mt-auto w-full flex">
|
||||
<button v-if="readysetup.delete" class="w-1/2 p-2 text-center bg-rose-500 text-stone-50 hover:bg-rose-600"
|
||||
@click.prevent="deleteReadymade(readyname)"
|
||||
>delete</button>
|
||||
<button class="w-1/2 p-2 bg-stone-700 text-white text-center hover:bg-stone-900"
|
||||
@click.prevent="loadReadymade(readyname)"
|
||||
>load</button>
|
||||
</div>
|
||||
<div v-else class="mt-auto w-full">
|
||||
<button class="p-2 w-full bg-stone-700 text-white text-center hover:bg-stone-900"
|
||||
@click.prevent="loadReadymade(readyname)"
|
||||
>load</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="w-1/3 p-2 flex" v-for="(readysetup,readyname) in theme.readymades">
|
||||
<div class="border-2 border-stone-200">
|
||||
<button class="w-full center bg-stone-200 p-2 hover:bg-stone-300 transition duration-100" @click.prevent="loadReadymade(readyname)">Load {{ readysetup.name }}</button>
|
||||
<div class="p-3">
|
||||
<p>{{ readysetup.description }}</p>
|
||||
</li>
|
||||
<li class="w-1/3 p-2" :key="'addnewreadymade'">
|
||||
<div class="flex flex-col border-2 border-stone-200 hover:shadow-lg transition duration-100 ease-in-out">
|
||||
<input
|
||||
type = "text"
|
||||
v-model = "readymadeTitle"
|
||||
@input = "checkTitle()"
|
||||
placeholder = "Add a title"
|
||||
class = "w-full font-medium p-2 text-center bg-stone-200">
|
||||
<textarea
|
||||
v-model = "readymadeDescription"
|
||||
class = "p-3 h-40"
|
||||
@input = "checkDescription()"
|
||||
placeholder = "Add a description and store the current settings as a new readymade."></textarea>
|
||||
<button class="p-2 w-full bg-stone-700 text-white text-center hover:bg-stone-900"
|
||||
@click.prevent="storeReadymade()"
|
||||
>store as readymade</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</li>
|
||||
</transition-group>
|
||||
</ul>
|
||||
<div v-if="readymadeError" class="w-100 p-2 m-2 text-stone-50 text-center bg-rose-500">{{ readymadeError }}</div>
|
||||
</fieldset>
|
||||
</div> -->
|
||||
</div>
|
||||
<div v-for="(fieldDefinition, fieldname) in theme.forms.fields">
|
||||
<fieldset class="flex flex-wrap justify-between border-2 border-stone-200 p-4 my-8" v-if="fieldDefinition.type == 'fieldset'">
|
||||
<legend class="text-lg font-medium">{{ fieldDefinition.legend }}</legend>
|
||||
@@ -111,19 +136,21 @@ const app = Vue.createApp({
|
||||
</Transition>`,
|
||||
data() {
|
||||
return {
|
||||
current: '',
|
||||
formDefinitions: data.definitions,
|
||||
formData: data.settings,
|
||||
readymades: data.readymades,
|
||||
theme: data.theme,
|
||||
license: data.license,
|
||||
message: '',
|
||||
messageClass: '',
|
||||
errors: {},
|
||||
versions: false,
|
||||
userroles: false,
|
||||
showModal: false,
|
||||
modalMessage: 'default',
|
||||
current: '',
|
||||
formDefinitions: data.definitions,
|
||||
formData: data.settings,
|
||||
readymadeTitle: '',
|
||||
readymadeDescription: '',
|
||||
readymadeError: false,
|
||||
theme: data.theme,
|
||||
license: data.license,
|
||||
message: '',
|
||||
messageClass: '',
|
||||
errors: {},
|
||||
versions: false,
|
||||
userroles: false,
|
||||
showModal: false,
|
||||
modalMessage: 'default',
|
||||
}
|
||||
},
|
||||
components: {
|
||||
@@ -247,6 +274,8 @@ const app = Vue.createApp({
|
||||
},
|
||||
loadReadymade(name)
|
||||
{
|
||||
this.readymadeError = false;
|
||||
|
||||
if(this.readymades[this.current] && this.readymades[this.current].individual === undefined)
|
||||
{
|
||||
this.readymades[this.current].individual = { 'settings' : this.formData[this.current] };
|
||||
@@ -258,6 +287,88 @@ const app = Vue.createApp({
|
||||
eventBus.$emit('codeareaupdate');
|
||||
}
|
||||
},
|
||||
checkTitle()
|
||||
{
|
||||
if(this.readymadeTitle.length > 20)
|
||||
{
|
||||
this.readymadeTitle = this.readymadeTitle.substring(0, 20);
|
||||
}
|
||||
if(this.readymadeTitle.match(/^[a-zA-Z0-9\- ]*$/))
|
||||
{
|
||||
this.readymadeError = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.readymadeError = "Only characters [a-zA-Z0-9- ] are allowed."
|
||||
}
|
||||
},
|
||||
checkDescription()
|
||||
{
|
||||
if(this.readymadeDescription.length > 100)
|
||||
{
|
||||
this.readymadeDescription = this.readymadeDescription.substring(0, 100);
|
||||
}
|
||||
},
|
||||
storeReadymade()
|
||||
{
|
||||
this.readymadeError = false;
|
||||
|
||||
var rself = this;
|
||||
|
||||
tmaxios.post('/api/v1/treadymade',{
|
||||
'theme': this.current,
|
||||
'settings': this.formData[this.current],
|
||||
'readymadetitle': this.readymadeTitle,
|
||||
'readymadedesc': this.readymadeDescription
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
if(response.data.readymade !== undefined)
|
||||
{
|
||||
rself.formDefinitions[rself.current].readymades = Object.assign(rself.formDefinitions[rself.current].readymades, response.data.readymade);
|
||||
|
||||
rself.readymadeTitle = '';
|
||||
rself.readymadeDescription = '';
|
||||
}
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
if(error.response)
|
||||
{
|
||||
if(error.response.data.message !== undefined)
|
||||
{
|
||||
rself.readymadeError = error.response.data.message;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
deleteReadymade(name)
|
||||
{
|
||||
this.readymadeError = false;
|
||||
|
||||
var rself = this;
|
||||
|
||||
tmaxios.delete('/api/v1/treadymade',{
|
||||
data: {
|
||||
'theme': this.current,
|
||||
'readymadeslug': name
|
||||
}
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
delete rself.formDefinitions[rself.current].readymades[name];
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
if(error.response)
|
||||
{
|
||||
if(error.response.data.message !== undefined)
|
||||
{
|
||||
rself.readymadeError = error.response.data.message;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
save()
|
||||
{
|
||||
this.reset();
|
||||
@@ -316,9 +427,10 @@ const app = Vue.createApp({
|
||||
},
|
||||
reset()
|
||||
{
|
||||
this.readymadeError = false;
|
||||
this.errors = {};
|
||||
this.message = '';
|
||||
this.messageClass = '';
|
||||
this.messageClass = '';
|
||||
}
|
||||
},
|
||||
})
|
@@ -34,6 +34,8 @@ $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) {
|
||||
$group->post('/licensetestcall', ControllerApiSystemLicense::class . ':testLicenseServerCall')->setName('api.license.testcall')->add(new ApiAuthorization($acl, 'user', 'update')); # admin
|
||||
$group->post('/themecss', ControllerApiSystemThemes::class . ':updateThemeCss')->setName('api.themecss.set')->add(new ApiAuthorization($acl, 'system', 'update')); # manager
|
||||
$group->post('/theme', ControllerApiSystemThemes::class . ':updateTheme')->setName('api.theme.set')->add(new ApiAuthorization($acl, 'system', 'update')); # manager
|
||||
$group->post('/treadymade', ControllerApiSystemThemes::class . ':updateReadymade')->setName('api.treadymade.set')->add(new ApiAuthorization($acl, 'system', 'update')); # manager
|
||||
$group->delete('/treadymade', ControllerApiSystemThemes::class . ':deleteReadymade')->setName('api.treadymade.delete')->add(new ApiAuthorization($acl, 'system', 'update')); # manager
|
||||
$group->post('/plugin', ControllerApiSystemPlugins::class . ':updatePlugin')->setName('api.plugin.set')->add(new ApiAuthorization($acl, 'system', 'update')); # manager
|
||||
$group->post('/extensions', ControllerApiSystemExtensions::class . ':activateExtension')->setName('api.extension.activate')->add(new ApiAuthorization($acl, 'system', 'update')); # manager
|
||||
$group->post('/versioncheck', ControllerApiSystemVersions::class . ':checkVersions')->setName('api.versioncheck')->add(new ApiAuthorization($acl, 'system', 'update')); # manager
|
||||
|
@@ -1,6 +1,6 @@
|
||||
name: Cyanine Theme
|
||||
version: 2.1.0
|
||||
description: Cyanine is a modern and flexible multi-purpose theme and the standard theme for typemill.
|
||||
version: 2.2.0
|
||||
description: 'Cyanine is a modern and flexible multi-purpose theme and the standard theme for Typemill.'
|
||||
author: Trendschau
|
||||
homepage: https://trendschau.net
|
||||
license: MIT
|
||||
@@ -213,43 +213,66 @@ forms:
|
||||
|
||||
bloghomepage:
|
||||
type: fieldset
|
||||
legend: Post Configuration (Blog or News)
|
||||
legend: 'Post Configuration (Blog or News)'
|
||||
fields:
|
||||
blogimage:
|
||||
type: checkbox
|
||||
label: Hero Images
|
||||
checkboxlabel: Show hero images in all lists of posts
|
||||
label: 'Hero Images'
|
||||
checkboxlabel: 'Show hero images in all lists of posts'
|
||||
blog:
|
||||
type: checkbox
|
||||
label: Homepage Posts
|
||||
checkboxlabel: Show posts on the homepage
|
||||
label: 'Posts on Homepage'
|
||||
checkboxlabel: 'Show posts on the homepage'
|
||||
blogfolder:
|
||||
type: text
|
||||
label: Homepage Posts Folder
|
||||
placeholder: /blog
|
||||
description: Add the relative pasts to the folder with posts that should appear on the homepage
|
||||
label: 'Folder for Posts on Homepage'
|
||||
placeholder: '/blog'
|
||||
description: 'Add the relative pasts to the folder with posts that should appear on the homepage'
|
||||
blogintro:
|
||||
type: checkbox
|
||||
label: Homepage Intro Text
|
||||
checkboxlabel: Show the text of the homepage before the list of posts
|
||||
label: 'Homepage Intro Text'
|
||||
checkboxlabel: 'Show the text of the homepage and list posts below'
|
||||
|
||||
landing:
|
||||
type: fieldset
|
||||
legend: Landingpage
|
||||
legend: 'Landingpage'
|
||||
fields:
|
||||
landingpage:
|
||||
type: checkbox
|
||||
checkboxlabel: Activate a landing page with segments on the homepage
|
||||
checkboxlabel: 'Activate a landing page with different segments on the homepage. Configure the segments in the fieldsets below.'
|
||||
introPosition:
|
||||
type: number
|
||||
label: 'Position of Intro Segment'
|
||||
description: 'A intro segment with a title, a slogan, a call to action, and a background-image. Use 0 to disable this section.'
|
||||
css: 'lg:w-full'
|
||||
infoPosition:
|
||||
type: number
|
||||
label: 'Position of Info Segment'
|
||||
description: 'A content segment with pure makrdown. Use 0 to disable the section.'
|
||||
css: 'lg:w-full'
|
||||
teaserPosition:
|
||||
type: number
|
||||
label: 'Position of Teaser Segment'
|
||||
description: 'Use up to three horizontal teasers with call to action buttons. Use 0 to disable the section.'
|
||||
css: 'lg:w-full'
|
||||
contrastPosition:
|
||||
type: number
|
||||
label: 'Position of Contrast Segment'
|
||||
description: 'A content segment wiht pure markdown and inverted colors for contrast. Use 0 to disable the section.'
|
||||
naviPosition:
|
||||
type: number
|
||||
label: 'Position of Navigation Segment'
|
||||
description: 'A segment with the navigation/table of contents. Use 0 to disable the section.'
|
||||
newsPosition:
|
||||
type: number
|
||||
label: 'Position of News Segment'
|
||||
description: 'A segment with three content-teasers for latest news or posts from a folder. Use 0 to disable the section.'
|
||||
css: 'lg:w-full'
|
||||
|
||||
landingpageIntro:
|
||||
type: fieldset
|
||||
legend: Landingpage Intro Segment
|
||||
legend: 'Landingpage Intro Segment'
|
||||
fields:
|
||||
introPosition:
|
||||
type: number
|
||||
label: Position of Intro Segment
|
||||
description: Use 0 to disable the section
|
||||
css: 'lg:w-full'
|
||||
introFullsize:
|
||||
type: checkbox
|
||||
label: Full Screen
|
||||
@@ -280,7 +303,7 @@ forms:
|
||||
type: text
|
||||
label: Background Image Opacity
|
||||
placeholder: 0.8
|
||||
description: 0 is fully transparent, 1 is without transparency
|
||||
description: 0 is without transparency, 1 is fully transparent and invisible
|
||||
introImageBlendmode:
|
||||
type: select
|
||||
label: Blend mode for background image
|
||||
@@ -308,10 +331,6 @@ forms:
|
||||
type: fieldset
|
||||
legend: Landingpage Info Segment
|
||||
fields:
|
||||
infoPosition:
|
||||
type: number
|
||||
label: Position of Info Segment
|
||||
description: Use 0 to disable the section
|
||||
infoMarkdown:
|
||||
type: textarea
|
||||
label: Use Markdown
|
||||
@@ -320,10 +339,6 @@ forms:
|
||||
type: fieldset
|
||||
legend: Landingpage Teaser Segment
|
||||
fields:
|
||||
teaserPosition:
|
||||
type: number
|
||||
label: Position of Teaser Segment
|
||||
description: Use 0 to disable the section
|
||||
teaser1title:
|
||||
type: text
|
||||
label: Teaser 1 Title
|
||||
@@ -377,10 +392,6 @@ forms:
|
||||
type: fieldset
|
||||
legend: Landingpage Contrast Segment
|
||||
fields:
|
||||
contrastPosition:
|
||||
type: number
|
||||
label: Position of Contrast Segment
|
||||
description: Use 0 to disable the section
|
||||
contrastTitle:
|
||||
type: text
|
||||
label: Title
|
||||
@@ -400,10 +411,6 @@ forms:
|
||||
type: fieldset
|
||||
legend: Landingpage Navigation Segment
|
||||
fields:
|
||||
naviPosition:
|
||||
type: number
|
||||
label: Position of Navi Segment
|
||||
description: Use 0 to disable the section
|
||||
naviTitle:
|
||||
type: text
|
||||
label: Title for navigation
|
||||
@@ -415,27 +422,22 @@ forms:
|
||||
type: fieldset
|
||||
legend: Landingpage News Segment
|
||||
fields:
|
||||
newsPosition:
|
||||
type: number
|
||||
label: Position of News Segment
|
||||
description: Use 0 to disable the section
|
||||
css: 'lg:w-half'
|
||||
newsHeadline:
|
||||
type: text
|
||||
label: Headline for news-segment
|
||||
placeholder: News
|
||||
css: 'lg:w-half'
|
||||
css: 'lg:w-full'
|
||||
newsFolder:
|
||||
type: text
|
||||
label: List entries from folder
|
||||
placeholder: /blog
|
||||
description: Add a path to a folder from which you want to list entries
|
||||
css: 'lg:w-half'
|
||||
css: 'lg:w-full'
|
||||
newsLabel:
|
||||
type: text
|
||||
label: Label for read more link
|
||||
placeholder: All News
|
||||
css: 'lg:w-half'
|
||||
css: 'lg:w-full'
|
||||
|
||||
fieldsetAuthor:
|
||||
type: fieldset
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<aside class="grid-header ph3 pv3">
|
||||
|
||||
<header>
|
||||
|
||||
|
||||
<div class="logo">
|
||||
<p class="pa0 ma0">
|
||||
<a class="link f1 fw9" href="{{ base_url }}" title="My Title">
|
||||
@@ -44,50 +44,51 @@
|
||||
<header>
|
||||
|
||||
<h1>{{ title }}</h1>
|
||||
|
||||
{% if (settings.themes.cyanine.datePosition.top or settings.themes.cyanine.authorPosition.top or settings.themes.cyanine.gitPosition.top or settings.themes.cyanine.printPosition.top) %}
|
||||
<div class="f5 pv1 flex justify-between">
|
||||
<div class="byline">
|
||||
{% if settings.themes.cyanine.datePosition.top %}
|
||||
<time pubdate datetime="{{ published }}" class="pr2">{{ settings.themes.cyanine.dateIntro }} {{ published|date(settings.themes.cyanine.dateFormat) }}</time>
|
||||
{% endif %}
|
||||
{% if settings.themes.cyanine.authorPosition.top %}
|
||||
<adress class="pr2">{{ settings.themes.cyanine.authorIntro }} {{ metatabs.meta.author|default(settings.author) }}</adress>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="funcicons">
|
||||
{% if settings.themes.cyanine.gitPosition.top %}
|
||||
<a class="link" title="edit on github" href="{{ settings.themes.cyanine.gitLink }}{{ item.path }}">{% if settings.themes.cyanine.editIcon %}<svg class="icon baseline icon-edit"><use xlink:href="#icon-edit"></use></svg>{% else %}{{ settings.themes.cyanine.editText }}{% endif %}</a>
|
||||
{% endif %}
|
||||
{% if settings.themes.cyanine.printPosition.top %}
|
||||
<a class="link" title="open printer dialogue" href="#" onclick="if (window.print) {window.print();}">{% if settings.themes.cyanine.printIcon %}<svg class="icon baseline icon-printer"><use xlink:href="#icon-printer"></use></svg>{% else %}{{ settings.themes.cyanine.printText }}{% endif %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if ("top" in settings.themes.cyanine.datePosition or "top" in settings.themes.cyanine.authorPosition or "top" in settings.themes.cyanine.gitPosition or "top" in settings.themes.cyanine.printPosition) %}
|
||||
<div class="f5 pv1 flex justify-between">
|
||||
<div class="byline">
|
||||
{% if "top" in settings.themes.cyanine.datePosition %}
|
||||
<time pubdate datetime="{{ published }}" class="pr2">{{ settings.themes.cyanine.dateIntro }} {{ published|date(settings.themes.cyanine.dateFormat) }}</time>
|
||||
{% endif %}
|
||||
{% if "top" in settings.themes.cyanine.authorPosition %}
|
||||
<adress class="pr2">{{ settings.themes.cyanine.authorIntro }} {{ metatabs.meta.author|default(settings.author) }}</adress>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="funcicons">
|
||||
{% if "top" in settings.themes.cyanine.gitPosition %}
|
||||
<a class="link" title="edit on github" href="{{ settings.themes.cyanine.gitLink }}{{ item.path }}">{% if settings.themes.cyanine.editIcon %}<svg class="icon baseline icon-pencil"><use xlink:href="#icon-pencil"></use></svg>{% else %}{{ settings.themes.cyanine.editText }}{% endif %}</a>
|
||||
{% endif %}
|
||||
{% if "top" in settings.themes.cyanine.printPosition %}
|
||||
<a class="link" title="open printer dialogue" href="#" onclick="if (window.print) {window.print();}">{% if settings.themes.cyanine.printIcon %}<svg class="icon baseline icon-printer"><use xlink:href="#icon-printer"></use></svg>{% else %}{{ settings.themes.cyanine.printText }}{% endif %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</header>
|
||||
|
||||
{{ content }}
|
||||
|
||||
{% if (settings.themes.cyanine.datePosition.bottom or settings.themes.cyanine.authorPosition.bottom or settings.themes.cyanine.gitPosition.bottom or settings.themes.cyanine.printPosition.bottom) %}
|
||||
<div class="f5 pv1 flex justify-between">
|
||||
<div class="byline">
|
||||
{% if settings.themes.cyanine.datePosition.bottom %}
|
||||
<time pubdate datetime="{{ published }}" class="pr2">{{ settings.themes.cyanine.dateIntro }} {{ published|date(settings.themes.cyanine.dateFormat) }}</time>
|
||||
{% endif %}
|
||||
{% if settings.themes.cyanine.authorPosition.bottom %}
|
||||
<adress class="pr2">{{ settings.themes.cyanine.authorIntro }} {{ metatabs.meta.author|default(settings.author) }}</adress>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="funcicons">
|
||||
{% if settings.themes.cyanine.gitPosition.bottom %}
|
||||
<a class="link" title="edit on github" href="{{ settings.themes.cyanine.gitLink }}{{ item.path }}">{% if settings.themes.cyanine.editIcon %}<svg class="icon baseline icon-edit"><use xlink:href="#icon-edit"></use></svg>{% else %}{{ settings.themes.cyanine.editText }}{% endif %}</a>
|
||||
{% endif %}
|
||||
{% if settings.themes.cyanine.printPosition.bottom %}
|
||||
<a class="link" title="open printer dialogue" href="#" onclick="if (window.print) {window.print();}">{% if settings.themes.cyanine.printIcon %}<svg class="icon baseline icon-printer"><use xlink:href="#icon-printer"></use></svg>{% else %}{{ settings.themes.cyanine.printText }}{% endif %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if ("bottom" in settings.themes.cyanine.datePosition or "bottom" in settings.themes.cyanine.authorPosition or "bottom" in settings.themes.cyanine.gitPosition or "bottom" in settings.themes.cyanine.printPosition) %}
|
||||
<div class="f5 pv1 flex justify-between">
|
||||
<div class="byline">
|
||||
{% if "bottom" in settings.themes.cyanine.datePosition %}
|
||||
<time pubdate datetime="{{ published }}" class="pr2">{{ settings.themes.cyanine.dateIntro }} {{ published|date(settings.themes.cyanine.dateFormat) }}</time>
|
||||
{% endif %}
|
||||
{% if "bottom" in settings.themes.cyanine.authorPosition %}
|
||||
<adress class="pr2">{{ settings.themes.cyanine.authorIntro }} {{ metatabs.meta.author|default(settings.author) }}</adress>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="funcicons">
|
||||
{% if "bottom" in settings.themes.cyanine.gitPosition %}
|
||||
<a class="link" title="edit on github" href="{{ settings.themes.cyanine.gitLink }}{{ item.path }}">{% if settings.themes.cyanine.editIcon %}<svg class="icon baseline icon-pencil"><use xlink:href="#icon-pencil"></use></svg>{% else %}{{ settings.themes.cyanine.editText }}{% endif %}</a>
|
||||
{% endif %}
|
||||
{% if "bottom" in settings.themes.cyanine.printPosition %}
|
||||
<a class="link" title="open printer dialogue" href="#" onclick="if (window.print) {window.print();}">{% if settings.themes.cyanine.printIcon %}<svg class="icon baseline icon-printer"><use xlink:href="#icon-printer"></use></svg>{% else %}{{ settings.themes.cyanine.printText }}{% endif %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</article>
|
||||
|
Reference in New Issue
Block a user