1
0
mirror of https://github.com/typemill/typemill.git synced 2025-08-05 05:37:45 +02:00

Navigation nearly ready

This commit is contained in:
trendschau
2023-03-25 09:08:35 +01:00
parent b66fc088a2
commit 6fa681888b
29 changed files with 1395 additions and 313 deletions

View File

@@ -0,0 +1,335 @@
<?php
namespace Typemill\Controllers;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Routing\RouteContext;
use Typemill\Models\Navigation;
use Typemill\Models\Validation;
use Typemill\Models\StorageWrapper;
class ControllerApiAuthorArticle extends Controller
{
public function sortArticle(Request $request, Response $response, $args)
{
$params = $request->getParsedBody();
# input validation
$validate = new Validation();
$result = $validate->navigationSort($params);
if(!$result)
{
$response->getBody()->write(json_encode([
'message' => 'Data not valid. Please refresh the page and try again.',
'errors' => $result
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
}
# set variables
$itemKeyPath = explode('.', $params['item_id']);
$parentKeyFrom = explode('.', $params['parent_id_from']);
$parentKeyTo = explode('.', $params['parent_id_to']);
$urlinfo = $this->c->get('urlinfo');
$langattr = $this->settings['langattr'];
# get navigation
$navigation = new Navigation();
$draftNavigation = $navigation->getDraftNavigation($urlinfo, $langattr);
# validate user rights
$acl = $this->c->get('acl');
# if user has no right to update content from others (eg admin or editor)
if(!$acl->isAllowed($request->getAttribute('c_userrole'), 'content', 'update'))
{
# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
if(!$this->checkContentOwnership())
{
$response->getBody()->write(json_encode([
'message' => 'You are not allowed to move that content.',
'navigation' => $draftNavigation
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
}
}
$item = $navigation->getItemWithKeyPath($draftNavigation, $itemKeyPath);
$extendedNavigation = $navigation->getExtendedNavigation($urlinfo, $langattr);
$pageinfo = $extendedNavigation[$params['url']] ?? false;
if(!$pageinfo)
{
$response->getBody()->write(json_encode([
'message' => 'page not found',
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(404);
}
# if an item is moved to the first level
if($parentKeyTo == '')
{
# create empty and default values so that the logic below still works
$newFolder = new \stdClass();
$newFolder->path = '';
$folderContent = $draftNavigation;
}
else
{
# get the target folder from navigation
$newFolder = $navigation->getItemWithKeyPath($draftNavigation, $parentKeyTo);
# get the content of the target folder
$folderContent = $newFolder->folderContent;
}
# if the item has been moved within the same folder
if($parentKeyFrom == $parentKeyTo)
{
# no need to ping search engines
$ping = false;
# get key of item
$itemKey = end($itemKeyPath);
reset($itemKeyPath);
# delete item from folderContent
unset($folderContent[$itemKey]);
}
else
{
# let us ping search engines
$ping = true;
# rename links in extended file
#$navigation->renameExtended($item, $newFolder);
# an active file has been moved to another folder, so send new url with response
if($params['active'] == 'active')
{
$url = $urlinfo['baseurl'] . '/tm/content/' . $this->settings['editor'] . $newFolder->urlRelWoF . '/' . $item->slug;
}
}
# add item to newFolder
array_splice($folderContent, $params['index_new'], 0, array($item));
# move and rename files in the new folder
$index = 0;
$writeError = false;
$storage = new StorageWrapper('\Typemill\Models\Storage');
foreach($folderContent as $folderItem)
{
if(!$storage->moveFile($folderItem, $newFolder->path, $index))
{
$writeError = true;
}
$index++;
}
if($writeError)
{
$response->getBody()->write(json_encode([
'message' => 'Something went wrong. Please refresh the page and check, if all folders and files are writable.',
'navigation' => $draftNavigation,
'url' => false
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(404);
}
# if everything worked, we have to recreate the navigation
$navigation->clearNavigation();
/*
# get item for url and set it active again
if(isset($this->params['url']))
{
$activeItem = Folder::getItemForUrl($this->structureDraft, $this->params['url'], $this->uri->getBaseUrl());
}
# update the structure for website
$this->setFreshStructureLive();
# update the navigation
$this->setFreshNavigation();
# update the sitemap
$this->updateSitemap($ping);
# dispatch event
$this->c->dispatcher->dispatch('onPageSorted', new OnPageSorted($this->params));
*/
$response->getBody()->write(json_encode([
'navigation' => $navigation->getDraftNavigation($urlinfo, $langattr),
'message' => '',
'url' => false
]));
return $response->withHeader('Content-Type', 'application/json');
}
public function createArticle(Request $request, Response $response, $args)
{
# validate user rights
$acl = $this->c->get('acl');
# if user has no right to update content from others (eg admin or editor)
if(!$acl->isAllowed($request->getAttribute('c_userrole'), 'mycontent', 'create'))
{
$response->getBody()->write(json_encode([
'message' => 'You are not allowed to create content.',
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(403);
}
$params = $request->getParsedBody();
# input validation
$validate = new Validation();
$result = $validate->validateNaviItem($params);
if(!$result)
{
$response->getBody()->write(json_encode([
'message' => 'Input not valid.',
'errors' => $result
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
}
# set variables
$urlinfo = $this->c->get('urlinfo');
$langattr = $this->settings['langattr'];
# get navigation
$navigation = new Navigation();
$draftNavigation = $navigation->getDraftNavigation($urlinfo, $langattr);
if($params['folder_id'] == 'root')
{
$folderContent = $draftNavigation;
}
else
{
# get the ids (key path) for item, old folder and new folder
$folderKeyPath = explode('.', $params['folder_id']);
# get the item from structure
$folder = $navigation->getItemWithKeyPath($draftNavigation, $folderKeyPath);
if(!$folder)
{
$response->getBody()->write(json_encode([
'message' => 'We could not find this page. Please refresh and try again.'
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
}
$folderContent = $folder->folderContent;
}
$name = $params['item_name'];
$slug = Folder::createSlug($this->params['item_name'], $this->settings);
# initialize index
$index = 0;
# iterate through the whole content of the new folder
$writeError = false;
$folderPath = isset($folder) ? $folder->path : '';
foreach($folderContent as $folderItem)
{
# check, if the same name as new item, then return an error
if($folderItem->slug == $slug)
{
return $response->withJson(array('navigation' => $draftNavigation, 'errors' => 'There is already a page with this name. Please choose another name.', 'url' => $url), 404);
}
if(!$writeYaml->moveElement($folderItem, $folderPath, $index))
{
$writeError = true;
}
$index++;
}
if($writeError)
{
return $response->withJson(array('data' => $this->structureDraft, 'errors' => 'Something went wrong. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404);
}
# add prefix number to the name
$namePath = $index > 9 ? $index . '-' . $slug : '0' . $index . '-' . $slug;
$folderPath = 'content' . $folder->path;
# create default content
$content = json_encode(['# ' . $name, 'Content']);
if($this->params['type'] == 'file')
{
if(!$writeYaml->writeFile($folderPath, $namePath . '.txt', $content))
{
return $response->withJson(array('data' => $this->structureDraft, 'errors' => 'We could not create the file. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404);
}
}
elseif($this->params['type'] == 'folder')
{
if(!$writeYaml->checkPath($folderPath . DIRECTORY_SEPARATOR . $namePath))
{
return $response->withJson(array('data' => $this->structureDraft, 'errors' => 'We could not create the folder. Please refresh the page and check, if all folders and files are writable.', 'url' => $url), 404);
}
$this->writeCache->writeFile($folderPath . DIRECTORY_SEPARATOR . $namePath, 'index.txt', $content);
# always redirect to a folder
$url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor'] . $folder->urlRelWoF . '/' . $slug;
}
# get extended structure
$extended = $writeYaml->getYaml('cache', 'structure-extended.yaml');
# create the url for the item
$urlWoF = $folder->urlRelWoF . '/' . $slug;
# $urlWoF = '/' . $slug;
# add the navigation name to the item htmlspecialchars needed for french language
$extended[$urlWoF] = ['hide' => false, 'navtitle' => $name];
# store the extended structure
$writeYaml->updateYaml('cache', 'structure-extended.yaml', $extended);
# update the structure for editor
$this->setFreshStructureDraft();
# get item for url and set it active again
if(isset($this->params['url']))
{
$activeItem = Folder::getItemForUrl($this->structureDraft, $this->params['url'], $this->uri->getBaseUrl());
}
# activate this if you want to redirect after creating the page...
# $url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor'] . $folder->urlRelWoF . '/' . $slug;
return $response->withJson(array('data' => $this->structureDraft, 'errors' => false, 'url' => $url));
}
}

View File

@@ -40,6 +40,35 @@ class Navigation
$this->extendedNaviName = 'navi-extended.txt';
}
public function clearNavigation(array $deleteitems = NULL )
{
# clear cache
$this->extendedNavigation = false;
$this->draftNavigation = false;
$this->basicDraftNavigation = false;
$this->liveNavigation = false;
$this->basicLiveNavigation = false;
# clear files
$navifiles = [
'extended' => $this->extendedNaviName,
'draft' => $this->draftNaviName,
'live' => $this->liveNaviName
];
if($deleteitems)
{
$navifiles = array_intersect_key($navifiles, $deleteitems);
}
foreach($navifiles as $navifile)
{
$result = $this->storage->deleteFile($this->naviFolder, $navifile);
}
return $result;
}
public function getMainNavigation($userrole, $acl, $urlinfo, $editor)
{
$mainnavi = $this->storage->getYaml('system/typemill/settings', 'mainnavi.yaml');
@@ -76,7 +105,6 @@ class Navigation
return $allowedmainnavi;
}
# get the navigation with draft files for author environment
public function getDraftNavigation($urlinfo, $language, $userrole = null, $username = null)
{

View File

@@ -192,9 +192,52 @@ class Storage
return false;
}
public function moveFile()
# used to sort the navigation / files
public function moveFile($item, $folderPath, $index, $date = null)
{
$filetypes = array('md', 'txt', 'yaml');
# set new order as string
$newOrder = ($index < 10) ? '0' . $index : $index;
$newPath = $this->contentFolder . $folderPath . DIRECTORY_SEPARATOR . $newOrder . '-' . $item->slug;
if($item->elementType == 'folder')
{
$oldPath = $this->basePath . 'content' . $item->path;
if(@rename($oldPath, $newPath))
{
return true;
}
return false;
}
# create old path but without filetype
$oldPath = substr($item->path, 0, strpos($item->path, "."));
$oldPath = $this->contentFolder . $oldPath;
$result = true;
foreach($filetypes as $filetype)
{
$oldFilePath = $oldPath . '.' . $filetype;
$newFilePath = $newPath . '.' . $filetype;
#check if file with filetype exists and rename
if($oldFilePath != $newFilePath && file_exists($oldFilePath))
{
if(@rename($oldFilePath, $newFilePath))
{
$result = $result;
}
else
{
$result = false;
}
}
}
return $result;
}
/**
@@ -498,54 +541,5 @@ class Storage
return $result;
}
public function moveElement($item, $folderPath, $index, $date = null)
{
$filetypes = array('md', 'txt', 'yaml');
# set new order as string
$newOrder = ($index < 10) ? '0' . $index : $index;
# create new path with foldername or filename but without file-type
# $newPath = $this->basePath . 'content' . $folderPath . DIRECTORY_SEPARATOR . $newOrder . '-' . str_replace(" ", "-", $item->name);
$newPath = $this->basePath . 'content' . $folderPath . DIRECTORY_SEPARATOR . $newOrder . '-' . $item->slug;
if($item->elementType == 'folder')
{
$oldPath = $this->basePath . 'content' . $item->path;
if(@rename($oldPath, $newPath))
{
return true;
}
return false;
}
# create old path but without filetype
$oldPath = substr($item->path, 0, strpos($item->path, "."));
$oldPath = $this->basePath . 'content' . $oldPath;
$result = true;
foreach($filetypes as $filetype)
{
$oldFilePath = $oldPath . '.' . $filetype;
$newFilePath = $newPath . '.' . $filetype;
#check if file with filetype exists and rename
if($oldFilePath != $newFilePath && file_exists($oldFilePath))
{
if(@rename($oldFilePath, $newFilePath))
{
$result = $result;
}
else
{
$result = false;
}
}
}
return $result;
}
*/
}

View File

@@ -382,6 +382,66 @@ class Validation
return $v->errors();
}
/**
* validation for resort navigation
*
* @param array $params with form data.
* @return true or $v->errors with array of errors to use in json-response
*/
public function navigationSort(array $params)
{
$v = new Validator($params);
$v->rule('required', ['item_id', 'parent_id_from', 'parent_id_to']);
$v->rule('regex', 'item_id', '/^[0-9.]+$/i');
$v->rule('regex', 'parent_id_from', '/^[a-zA-Z0-9.]+$/i');
$v->rule('regex', 'parent_id_to', '/^[a-zA-Z0-9.]+$/i');
$v->rule('integer', 'index_new');
$v->rule('integer', 'index_old');
if($v->validate())
{
return true;
}
return $v->errors();
}
/**
* validation for new navigation items
*
* @param array $params with form data.
* @return true or $v->errors with array of errors to use in json-response
*/
public function navigationItem(array $params)
{
$v = new Validator($params);
$v->rule('required', ['folder_id', 'item_name', 'type', 'url']);
$v->rule('regex', 'folder_id', '/^(root)|([0-9.]+)$/i');
$v->rule('navigation', 'item_name');
$v->rule('lengthBetween', 'item_name', 1, 60);
$v->rule('in', 'type', ['file', 'folder']);
if($v->validate())
{
return true;
}
else
{
return $v->errors();
}
}
/**
* validation for system settings
@@ -463,80 +523,7 @@ class Validation
}
}
/**
* validation for resort navigation
*
* @param array $params with form data.
* @return true or $v->errors with array of errors to use in json-response
*/
public function navigationSort(array $params)
{
$v = new Validator($params);
$v->rule('required', ['item_id', 'parent_id_from', 'parent_id_to']);
$v->rule('regex', 'item_id', '/^[0-9.]+$/i');
$v->rule('regex', 'parent_id_from', '/^[a-zA-Z0-9.]+$/i');
$v->rule('regex', 'parent_id_to', '/^[a-zA-Z0-9.]+$/i');
$v->rule('integer', 'index_new');
if($v->validate())
{
return true;
}
else
{
return $v->errors();
}
}
/**
* validation for new navigation items
*
* @param array $params with form data.
* @return true or $v->errors with array of errors to use in json-response
*/
public function navigationItem(array $params)
{
$v = new Validator($params);
$v->rule('required', ['folder_id', 'item_name', 'type', 'url']);
$v->rule('regex', 'folder_id', '/^[0-9.]+$/i');
# $v->rule('noSpecialChars', 'item_name');
$v->rule('navigation', 'item_name');
$v->rule('lengthBetween', 'item_name', 1, 60);
$v->rule('in', 'type', ['file', 'folder']);
if($v->validate())
{
return true;
}
else
{
return $v->errors();
}
}
public function navigationBaseItem(array $params)
{
$v = new Validator($params);
$v->rule('required', ['item_name', 'type', 'url']);
# $v->rule('noSpecialChars', 'item_name');
$v->rule('navigation', 'item_name');
$v->rule('lengthBetween', 'item_name', 1, 40);
$v->rule('in', 'type', ['file', 'folder']);
if($v->validate())
{
return true;
}
else
{
return $v->errors();
}
}
/**
* validation for dynamic fields ( settings for themes and plugins)

View File

@@ -5,18 +5,10 @@
<h1 class="text-3xl font-bold mb-4">{{ translate('Visual Editor') }} </h1>
<div id="veditor" v-cloak></div>
<div id="editor" v-cloak></div>
{% endblock %}
{% block javascript %}
<script src="{{ base_url() }}/system/typemill/author/js/highlight.min.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vue-system.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vue-translate.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vue-shared.js?v={{ settings.version }}"></script>
<script>
app.mount('#veditor');
</script>
{% endblock %}

View File

@@ -620,6 +620,14 @@ video {
pointer-events: none;
}
.visible {
visibility: visible;
}
.invisible {
visibility: hidden;
}
.static {
position: static;
}
@@ -636,6 +644,11 @@ video {
position: relative;
}
.sticky {
position: -webkit-sticky;
position: sticky;
}
.inset-0 {
top: 0px;
right: 0px;
@@ -688,6 +701,10 @@ video {
margin: auto;
}
.m-1 {
margin: 0.25rem;
}
.my-2 {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
@@ -723,28 +740,45 @@ video {
margin-bottom: 0.75rem;
}
.mx-1 {
margin-left: 0.25rem;
margin-right: 0.25rem;
}
.mt-6 {
margin-top: 1.5rem;
}
.mb-1 {
margin-bottom: 0.25rem;
}
.mt-3 {
margin-top: 0.75rem;
}
.mb-4 {
margin-bottom: 1rem;
}
.mr-3 {
margin-right: 0.75rem;
}
.mb-1 {
margin-bottom: 0.25rem;
}
.mr-1 {
margin-right: 0.25rem;
}
.mr-4 {
margin-right: 1rem;
}
.mt-3 {
margin-top: 0.75rem;
}
.mb-3 {
margin-bottom: 0.75rem;
}
.mr-3 {
margin-right: 0.75rem;
.ml-3 {
margin-left: 0.75rem;
}
.mt-5 {
@@ -771,10 +805,6 @@ video {
margin-top: 1rem;
}
.mt-auto {
margin-top: auto;
}
.mt-8 {
margin-top: 2rem;
}
@@ -783,16 +813,16 @@ video {
margin-top: 1.75rem;
}
.mr-4 {
margin-right: 1rem;
}
.mr-2 {
margin-right: 0.5rem;
}
.ml-3 {
margin-left: 0.75rem;
.ml-1 {
margin-left: 0.25rem;
}
.-ml-1 {
margin-left: -0.25rem;
}
.block {
@@ -839,6 +869,10 @@ video {
height: 1.5rem;
}
.h-0 {
height: 0px;
}
.h-12 {
height: 3rem;
}
@@ -851,14 +885,6 @@ video {
height: 20rem;
}
.h-64 {
height: 16rem;
}
.h-0 {
height: 0px;
}
.h-48 {
height: 12rem;
}
@@ -879,6 +905,10 @@ video {
width: 100%;
}
.w-1\/4 {
width: 25%;
}
.w-2\/5 {
width: 40%;
}
@@ -895,6 +925,10 @@ video {
width: 1.5rem;
}
.w-0 {
width: 0px;
}
.w-5 {
width: 1.25rem;
}
@@ -931,14 +965,6 @@ video {
width: 75%;
}
.w-1\/4 {
width: 25%;
}
.w-0 {
width: 0px;
}
.max-w-md {
max-width: 28rem;
}
@@ -975,6 +1001,23 @@ video {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
@-webkit-keyframes spin {
to {
transform: rotate(360deg);
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.animate-spin {
-webkit-animation: spin 1s linear infinite;
animation: spin 1s linear infinite;
}
.cursor-pointer {
cursor: pointer;
}
@@ -983,10 +1026,8 @@ video {
resize: both;
}
.appearance-none {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
.list-none {
list-style-type: none;
}
.flex-col {
@@ -1025,12 +1066,6 @@ video {
justify-content: space-around;
}
.space-x-8 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(2rem * var(--tw-space-x-reverse));
margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
@@ -1076,10 +1111,22 @@ video {
border-right-width: 8px;
}
.border-l-4 {
border-left-width: 4px;
}
.border-b {
border-bottom-width: 1px;
}
.border-b-8 {
border-bottom-width: 8px;
}
.border-t-8 {
border-top-width: 8px;
}
.border-b-2 {
border-bottom-width: 2px;
}
@@ -1096,18 +1143,6 @@ video {
border-right-width: 2px;
}
.border-l-4 {
border-left-width: 4px;
}
.border-b-8 {
border-bottom-width: 8px;
}
.border-t-8 {
border-top-width: 8px;
}
.border-solid {
border-style: solid;
}
@@ -1117,6 +1152,11 @@ video {
border-color: rgb(209 213 219 / var(--tw-border-opacity));
}
.border-teal-500 {
--tw-border-opacity: 1;
border-color: rgb(20 184 166 / var(--tw-border-opacity));
}
.border-stone-200 {
--tw-border-opacity: 1;
border-color: rgb(231 229 228 / var(--tw-border-opacity));
@@ -1137,11 +1177,6 @@ video {
border-color: rgb(214 211 209 / var(--tw-border-opacity));
}
.border-teal-500 {
--tw-border-opacity: 1;
border-color: rgb(20 184 166 / var(--tw-border-opacity));
}
.border-stone-700 {
--tw-border-opacity: 1;
border-color: rgb(68 64 60 / var(--tw-border-opacity));
@@ -1177,11 +1212,6 @@ video {
border-right-color: transparent;
}
.border-b-blue-600 {
--tw-border-opacity: 1;
border-bottom-color: rgb(37 99 235 / var(--tw-border-opacity));
}
.border-b-white {
--tw-border-opacity: 1;
border-bottom-color: rgb(255 255 255 / var(--tw-border-opacity));
@@ -1202,16 +1232,6 @@ video {
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.bg-rose-500 {
--tw-bg-opacity: 1;
background-color: rgb(244 63 94 / var(--tw-bg-opacity));
}
.bg-teal-500 {
--tw-bg-opacity: 1;
background-color: rgb(20 184 166 / var(--tw-bg-opacity));
}
.bg-stone-100 {
--tw-bg-opacity: 1;
background-color: rgb(245 245 244 / var(--tw-bg-opacity));
@@ -1222,16 +1242,26 @@ video {
background-color: rgb(68 64 60 / var(--tw-bg-opacity));
}
.bg-red-100 {
--tw-bg-opacity: 1;
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
}
.bg-stone-200 {
--tw-bg-opacity: 1;
background-color: rgb(231 229 228 / var(--tw-bg-opacity));
}
.bg-rose-500 {
--tw-bg-opacity: 1;
background-color: rgb(244 63 94 / var(--tw-bg-opacity));
}
.bg-teal-500 {
--tw-bg-opacity: 1;
background-color: rgb(20 184 166 / var(--tw-bg-opacity));
}
.bg-red-100 {
--tw-bg-opacity: 1;
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
}
.bg-stone-900 {
--tw-bg-opacity: 1;
background-color: rgb(28 25 23 / var(--tw-bg-opacity));
@@ -1242,6 +1272,10 @@ video {
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
}
.bg-transparent {
background-color: transparent;
}
.bg-opacity-90 {
--tw-bg-opacity: 0.9;
}
@@ -1254,6 +1288,10 @@ video {
background-position: center;
}
.p-1 {
padding: 0.25rem;
}
.p-2 {
padding: 0.5rem;
}
@@ -1274,10 +1312,6 @@ video {
padding: 1.5rem;
}
.p-1 {
padding: 0.25rem;
}
.py-5 {
padding-top: 1.25rem;
padding-bottom: 1.25rem;
@@ -1333,6 +1367,10 @@ video {
padding-right: 0.25rem;
}
.pl-2 {
padding-left: 0.5rem;
}
.pl-3 {
padding-left: 0.75rem;
}
@@ -1361,8 +1399,8 @@ video {
padding-top: 0.5rem;
}
.pl-8 {
padding-left: 2rem;
.pb-4 {
padding-bottom: 1rem;
}
.pt-4 {
@@ -1373,12 +1411,16 @@ video {
padding-bottom: 0.75rem;
}
.pr-8 {
padding-right: 2rem;
.pl-4 {
padding-left: 1rem;
}
.pb-4 {
padding-bottom: 1rem;
.pl-8 {
padding-left: 2rem;
}
.pl-12 {
padding-left: 3rem;
}
.text-left {
@@ -1416,6 +1458,16 @@ video {
line-height: 1rem;
}
.text-3xl {
font-size: 1.875rem;
line-height: 2.25rem;
}
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
@@ -1426,21 +1478,11 @@ video {
line-height: 1.75rem;
}
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
.text-3xl {
font-size: 1.875rem;
line-height: 2.25rem;
}
.font-normal {
font-weight: 400;
}
@@ -1519,27 +1561,42 @@ video {
color: rgb(6 182 212 / var(--tw-text-opacity));
}
.text-stone-700 {
--tw-text-opacity: 1;
color: rgb(68 64 60 / var(--tw-text-opacity));
}
.text-stone-200 {
--tw-text-opacity: 1;
color: rgb(231 229 228 / var(--tw-text-opacity));
}
.text-stone-500 {
--tw-text-opacity: 1;
color: rgb(120 113 108 / var(--tw-text-opacity));
}
.underline {
-webkit-text-decoration-line: underline;
text-decoration-line: underline;
}
.accent-pink-300 {
accent-color: #f9a8d4;
}
.accent-white {
accent-color: #fff;
}
.accent-teal-500 {
accent-color: #14b8a6;
}
.opacity-0 {
opacity: 0;
}
.opacity-25 {
opacity: 0.25;
}
.opacity-75 {
opacity: 0.75;
}
.shadow-lg {
--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);
@@ -1598,26 +1655,6 @@ video {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.checked\:bg-white:checked {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.checked\:bg-blue-500:checked {
--tw-bg-opacity: 1;
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
}
.checked\:bg-teal-500:checked {
--tw-bg-opacity: 1;
background-color: rgb(20 184 166 / var(--tw-bg-opacity));
}
.checked\:text-white:checked {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.hover\:border-b-4:hover {
border-bottom-width: 4px;
}
@@ -1637,11 +1674,21 @@ video {
border-color: rgb(20 184 166 / var(--tw-border-opacity));
}
.hover\:border-stone-50:hover {
--tw-border-opacity: 1;
border-color: rgb(250 250 249 / var(--tw-border-opacity));
}
.hover\:bg-gray-200:hover {
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
}
.hover\:bg-stone-50:hover {
--tw-bg-opacity: 1;
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
}
.hover\:bg-stone-900:hover {
--tw-bg-opacity: 1;
background-color: rgb(28 25 23 / var(--tw-bg-opacity));
@@ -1687,9 +1734,19 @@ video {
background-color: rgb(6 182 212 / var(--tw-bg-opacity));
}
.hover\:bg-stone-50:hover {
.hover\:bg-stone-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
background-color: rgb(68 64 60 / var(--tw-bg-opacity));
}
.hover\:bg-stone-500:hover {
--tw-bg-opacity: 1;
background-color: rgb(120 113 108 / var(--tw-bg-opacity));
}
.hover\:bg-teal-500:hover {
--tw-bg-opacity: 1;
background-color: rgb(20 184 166 / var(--tw-bg-opacity));
}
.hover\:text-white:hover {
@@ -1697,6 +1754,16 @@ video {
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.hover\:text-stone-50:hover {
--tw-text-opacity: 1;
color: rgb(250 250 249 / var(--tw-text-opacity));
}
.hover\:text-stone-100:hover {
--tw-text-opacity: 1;
color: rgb(245 245 244 / var(--tw-text-opacity));
}
.hover\:underline:hover {
-webkit-text-decoration-line: underline;
text-decoration-line: underline;
@@ -1707,6 +1774,11 @@ video {
border-color: rgb(37 99 235 / var(--tw-border-opacity));
}
.focus\:border-stone-100:focus {
--tw-border-opacity: 1;
border-color: rgb(245 245 244 / var(--tw-border-opacity));
}
.focus\:bg-white:focus {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
@@ -1717,6 +1789,11 @@ video {
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
}
.focus\:bg-stone-100:focus {
--tw-bg-opacity: 1;
background-color: rgb(245 245 244 / var(--tw-bg-opacity));
}
.focus\:text-gray-700:focus {
--tw-text-opacity: 1;
color: rgb(55 65 81 / var(--tw-text-opacity));
@@ -1738,6 +1815,10 @@ video {
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
}
.group:hover .group-hover\:visible {
visibility: visible;
}
@media (prefers-color-scheme: dark) {
.dark\:text-gray-400 {
--tw-text-opacity: 1;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,635 @@
const navigation = Vue.createApp({
template: `
<div class="mr-3">
<div class="flex w-100 mb-4">
<button class="w-1/2 ml-1 hover:bg-stone-700 hover:text-stone-50 border border-stone-200 px-2 py-1 transition duration-100" @click.prevent="collapseNavigation()">collapse all</button>
<button class="w-1/2 mr-1 hover:bg-stone-700 hover:text-stone-50 border border-stone-200 px-2 py-1 transition duration-100" @click.prevent="expandNavigation()">expand all</button>
</div>
<div class="flex w-full mb-1 font-bold">
<div class="border-l-4 border-teal-500 published"></div>
<a class="flex-grow p-1 hover:bg-teal-500 hover:text-stone-50 pl-2 text-bold transition duration-100" :href="getHomeUrl()">Home</a>
</div>
<div class="pl-2 pl-4 pl-8 pl-12"></div>
<navilevel :navigation="navigation" />
</div>`,
data: function () {
return {
navigation: data.navigation,
isExpended: false,
expanded: [],
}
},
mounted: function(){
var expanded = localStorage.getItem('expanded');
if(expanded !== null)
{
var expandedArray = expanded.split(',');
var expandedLength = expandedArray.length;
var cleanExpandedArray = [];
for(var i = 0; i < expandedLength; i++)
{
if(typeof expandedArray[i] === 'string' && expandedArray[i] != '')
{
cleanExpandedArray.push(expandedArray[i]);
}
}
this.expanded = expanded.split(',');
}
eventBus.$on('toggleFolder', this.toggleFolder);
},
methods: {
getHomeUrl()
{
return tmaxios.defaults.baseURL + '/tm/content/visual';
},
toggleFolder: function(name)
{
var index = this.expanded.indexOf(name);
if (index > -1)
{
this.expanded.splice(index, 1);
// this.expandNavigation = false;
}
else
{
this.expanded.push(name);
}
localStorage.setItem("expanded", this.expanded.toString());
},
expandNavigation()
{
this.expanded = this.getFolderNames(this.navigation, []);
localStorage.setItem("expanded", this.expanded.toString());
},
collapseNavigation()
{
this.expanded = [];
localStorage.removeItem('expanded');
},
getFolderNames(navigation, result)
{
for (const item of navigation)
{
if (item.elementType == 'folder')
{
result.push(item.name);
this.getFolderNames(item.folderContent, result);
}
}
return result;
}
}
});
navigation.component('draggable', vuedraggable);
navigation.component('navilevel',{
template: `
<draggable
@start="onStart"
@end="onEnd"
:move="checkMove"
:list="navigation"
v-bind="dragOptions"
class="dragArea"
tag="ul"
item-key="keyPath"
:component-data="{
id: parentId ? parentId : false
}"
>
<template #item="{ element }">
<li :class="element.elementType" :id="element.keyPath" :data-url="element.urlRelWoF" :data-active="element.active">
<div class="flex w-full mb-1 relative" :class="element.elementType == 'folder' ? 'font-bold' : ''">
<div class="border-l-4 border-teal-500" :class="element.status"></div>
<a :href="getUrl(element.urlRelWoF)" class="flex-grow p-1 hover:bg-teal-500 hover:text-stone-50" :class="getNaviClass(element.active, element.activeParent, element.keyPathArray)">
{{ element.name }}
</a>
<div v-if="load == element.keyPath" class="p-1 absolute right-0">
<svg class="animate-spin h-5 w-5 text-stone-700" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
<div v-if="element.elementType == 'folder'" class=" p-1 bg-transparent absolute right-0" @click="callToggle(element.name)">
<svg v-if="isExpanded(element.name)" class="icon icon-cheveron-up">
<use xlink:href="#icon-cheveron-up"></use>
</svg>
<svg v-else class="icon icon-cheveron-down">
<use xlink:href="#icon-cheveron-down"></use>
</svg>
</div>
</div>
<navilevel v-show="isExpanded(element.name)" v-if="element.elementType == 'folder'" :list="element.folderContent" :navigation="element.folderContent" :parentId="element.keyPath" />
</li>
</template>
<template #footer>
<li>
<div class="flex w-full mb-1 group">
<div class="border-l-4 border-stone-200"></div>
<div class="flex-grow">
<input :class="navilevel" class="w-full p-1 bg-stone-50 border-2 border-stone-50 focus:outline-none" placeholder="..." v-model="newItem">
</div>
<!-- <div class="w-1/4 invisible group-hover:visible"> -->
<div class="flex">
<button title="add a file" @click="addItem('file', parentId)" class="text-stone-500 bg-stone-100 hover:text-stone-100 hover:bg-stone-700 p-1 border-2 border-stone-50 transition duration-100">
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 28">
<path fill="currentColor" d="M22.937 5.938c0.578 0.578 1.062 1.734 1.062 2.562v18c0 0.828-0.672 1.5-1.5 1.5h-21c-0.828 0-1.5-0.672-1.5-1.5v-25c0-0.828 0.672-1.5 1.5-1.5h14c0.828 0 1.984 0.484 2.562 1.062zM16 2.125v5.875h5.875c-0.094-0.266-0.234-0.531-0.344-0.641l-4.891-4.891c-0.109-0.109-0.375-0.25-0.641-0.344zM22 26v-16h-6.5c-0.828 0-1.5-0.672-1.5-1.5v-6.5h-12v24h20zM6 12.5c0-0.281 0.219-0.5 0.5-0.5h11c0.281 0 0.5 0.219 0.5 0.5v1c0 0.281-0.219 0.5-0.5 0.5h-11c-0.281 0-0.5-0.219-0.5-0.5v-1zM17.5 16c0.281 0 0.5 0.219 0.5 0.5v1c0 0.281-0.219 0.5-0.5 0.5h-11c-0.281 0-0.5-0.219-0.5-0.5v-1c0-0.281 0.219-0.5 0.5-0.5h11zM17.5 20c0.281 0 0.5 0.219 0.5 0.5v1c0 0.281-0.219 0.5-0.5 0.5h-11c-0.281 0-0.5-0.219-0.5-0.5v-1c0-0.281 0.219-0.5 0.5-0.5h11z"></path>
</svg>
</button>
<button title="add a folder" @click="addItem('folder', parentId)" class="text-stone-500 bg-stone-100 hover:text-stone-100 hover:bg-stone-700 p-1 border-2 border-stone-50 transition duration-100">
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 28">
<path fill="currentColor" d="M24 20.5v-11c0-0.828-0.672-1.5-1.5-1.5h-11c-0.828 0-1.5-0.672-1.5-1.5v-1c0-0.828-0.672-1.5-1.5-1.5h-5c-0.828 0-1.5 0.672-1.5 1.5v15c0 0.828 0.672 1.5 1.5 1.5h19c0.828 0 1.5-0.672 1.5-1.5zM26 9.5v11c0 1.922-1.578 3.5-3.5 3.5h-19c-1.922 0-3.5-1.578-3.5-3.5v-15c0-1.922 1.578-3.5 3.5-3.5h5c1.922 0 3.5 1.578 3.5 3.5v0.5h10.5c1.922 0 3.5 1.578 3.5 3.5z"></path>
</svg>
</button>
<!-- <button title="add a link" @click="addItem('link', parentId)" class="text-stone-500 bg-stone-100 hover:text-stone-100 hover:bg-stone-700 p-1 border-2 border-stone-50 transition duration-100">
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32">
<path fill="currentColor" d="M13.757 19.868c-0.416 0-0.832-0.159-1.149-0.476-2.973-2.973-2.973-7.81 0-10.783l6-6c1.44-1.44 3.355-2.233 5.392-2.233s3.951 0.793 5.392 2.233c2.973 2.973 2.973 7.81 0 10.783l-2.743 2.743c-0.635 0.635-1.663 0.635-2.298 0s-0.635-1.663 0-2.298l2.743-2.743c1.706-1.706 1.706-4.481 0-6.187-0.826-0.826-1.925-1.281-3.094-1.281s-2.267 0.455-3.094 1.281l-6 6c-1.706 1.706-1.706 4.481 0 6.187 0.635 0.635 0.635 1.663 0 2.298-0.317 0.317-0.733 0.476-1.149 0.476z"></path>
<path fill="currentColor" d="M8 31.625c-2.037 0-3.952-0.793-5.392-2.233-2.973-2.973-2.973-7.81 0-10.783l2.743-2.743c0.635-0.635 1.664-0.635 2.298 0s0.635 1.663 0 2.298l-2.743 2.743c-1.706 1.706-1.706 4.481 0 6.187 0.826 0.826 1.925 1.281 3.094 1.281s2.267-0.455 3.094-1.281l6-6c1.706-1.706 1.706-4.481 0-6.187-0.635-0.635-0.635-1.663 0-2.298s1.663-0.635 2.298 0c2.973 2.973 2.973 7.81 0 10.783l-6 6c-1.44 1.44-3.355 2.233-5.392 2.233z"></path>
</svg>
</button> -->
</div>
</div>
</li>
</template>
</draggable>`,
props: {
navigation: {
type: Array,
required: true
},
parentId: {
default: 'root'
}
},
data: function () {
return {
navilevel: '',
load: '?',
freeze: false,
newItem: '',
format: /[@#*()=\[\]{};:"\\|,.<>\/]/,
}
},
computed:
{
dragOptions()
{
return {
animation: 150,
group: "file",
disabled: this.freeze,
ghostClass: "ghost",
};
},
// this.value when input = v-model
// this.list when input != v-model
realValue()
{
return this.value ? this.value : this.list;
}
},
methods:
{
getNaviClass(active, activeParent, keyPathArray)
{
var naviclass = 'pl-' + (keyPathArray.length * 2);
this.navilevel = naviclass;
if(active){ naviclass += " active" }
if(activeParent){ naviclass += " activeParent" }
return naviclass;
},
getUrl(segment)
{
return tmaxios.defaults.baseURL + '/tm/content/visual' + segment;
},
callToggle(name)
{
eventBus.$emit('toggleFolder', name);
},
isExpanded(name)
{
if(this.$root.expanded.indexOf(name) > -1)
{
return true;
}
return false;
},
onStart(evt)
{
/* delete error messages if exist */
// publishController.errors.message = false;
},
checkMove(evt)
{
/* do we want to keep that restriction, no folder into folders? */
if(evt.dragged.classList.contains('folder') && evt.from.parentNode.id != evt.to.parentNode.id)
{
console.info("moved folder to another folder");
return false;
}
if(evt.dragged.dataset.active == 'active' && !editor.draftDisabled)
{
console.info("moved page is active, save your changes first");
// publishController.errors.message = "Please save your changes before you move the file";
return false;
}
return true;
},
onEnd(evt)
{
if(evt.from.parentNode.id == evt.to.parentNode.id && evt.oldIndex == evt.newIndex)
{
return
}
this.freeze = true;
this.load = evt.item.id;
var self = this;
// self.errors = {title: false, content: false, message: false};
tmaxios.post('/api/v1/article/sort',{
'item_id': evt.item.id,
'parent_id_from': evt.from.parentNode.id,
'parent_id_to': evt.to.parentNode.id,
'index_old': evt.oldIndex,
'index_new': evt.newIndex,
'active': evt.item.dataset.active,
'url': evt.item.dataset.url,
// 'url': document.getElementById("path").value,
// 'csrf_name': document.getElementById("csrf_name").value,
// 'csrf_value': document.getElementById("csrf_value").value,
})
.then(function (response)
{
self.load = '?';
self.freeze = false;
if(response.data.url)
{
window.location.replace(response.data.url);
}
if(response.data.navigation)
{
self.$root.$data.navigation = response.data.navigation;
}
})
.catch(function (error)
{
if(error.response.data.errors.message)
{
// publishController.errors.message = error.response.data.errors;
}
});
},
addItem(type, parent)
{
// publishController.errors.message = false;
if(this.format.test(this.newItem) || !this.newItem || this.newItem.length > 40)
{
// publishController.errors.message = 'Special Characters are not allowed. Length between 1 and 40.';
return;
}
self = this;
self.freeze = true;
// self.errors = {title: false, content: false, message: false};
tmaxios.post('/api/v1/article',{
'item_name': this.newItem,
'folder_id': parent,
'type': type,
// 'url': document.getElementById("path").value,
// 'csrf_name': document.getElementById("csrf_name").value,
// 'csrf_value': document.getElementById("csrf_value").value,
})
.then(function (response) {
self.freeze = false;
if(response.data.url)
{
window.location.replace(response.data.url);
}
if(response.data.navigation)
{
self.items = response.data.navigation;
self.newItem = '';
}
})
.catch(function (error)
{
// publishController.errors.message = error.response.data.errors;
});
},
emitter(value) {
this.$emit("input", value);
},
},
});
navigation.mount('#contentNavigation');
/*
data: function () {
return {
title: "Navigation",
navigation: data.navigation,
homepage: false,
editormode: 'visual',
freeze: false,
modalWindow: false,
format: /[@#*()=\[\]{};:"\\|,.<>\/]/,
folderName: '',
showForm: false,
newItem: '',
collapse: [],
}
},
<draggable class="navi-list list-none" tag="ul"
@start="onStart"
@end="onEnd"
:list="items"
:move="checkMove"
group="file"
animation="150"
:disabled="freeze"
item-key="items.length">
<navilevel
v-for="item in items"
ref="draggit"
:freeze="freeze"
:name="item.name"
:hide="item.hide"
:active="item.active"
:parent="item.activeParent"
:level="item.keyPath"
:root="root"
:url="item.urlRelWoF"
:id="item.keyPath"
:key="item.keyPath"
:elementtype="item.elementType"
:contains="item.contains"
:filetype="item.fileType"
:status="item.status"
:folder="item.folderContent"
:collapse="collapse"
></navilevel>
</draggable>
data: function () {
return {
title: "Navigation",
items: data.navigation,
homepage: false,
editormode: 'visual',
freeze: false,
modalWindow: false,
format: /[@#*()=\[\]{};:"\\|,.<>\/]/,
folderName: '',
showForm: false,
newItem: '',
collapse: [],
}
},
checkMove: function(evt){
/* this.$refs.draggit[0].checkMove(evt); *
if(evt.dragged.classList.contains('folder') && evt.from.parentNode.id != evt.to.parentNode.id)
{
return false;
}
if(evt.dragged.firstChild.className == 'active' && !editor.draftDisabled)
{
publishController.errors.message = "Please save your changes before you move the file";
return false;
}
return true;
},
onStart: function(evt){
this.$refs.draggit[0].onStart(evt);
},
onEnd: function(evt){
this.$refs.draggit[0].onEnd(evt);
},
addFile : function(type)
{
publishController.errors.message = false;
if(this.format.test(this.newItem) || !this.newItem || this.newItem.length > 40)
{
publishController.errors.message = 'Special Characters are not allowed. Length between 1 and 40.';
return;
}
self = this;
self.freeze = true;
self.errors = {title: false, content: false, message: false};
myaxios.post('/api/v1/baseitem',{
'item_name': this.newItem,
'type': type,
'url': document.getElementById("path").value,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
})
.then(function (response) {
self.freeze = false;
if(response.data.url)
{
window.location.replace(response.data.url);
}
if(response.data.data)
{
self.items = response.data.data;
self.newItem = '';
self.showForm = false;
}
})
.catch(function (error)
{
publishController.errors.message = error.response.data.errors;
});
},
getNavi: function()
{
publishController.errors.message = false;
var self = this;
self.freeze = true;
self.errors = {title: false, content: false, message: false};
var activeItem = document.getElementById("path").value;
var url = this.root + '/api/v1/navigation?url=' + activeItem;
var method = 'GET';
myaxios.get('/api/v1/navigation',{
params: {
'url': activeItem,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
}
})
.then(function (response) {
self.freeze = false;
if(response.data.data)
{
self.items = response.data.data;
self.newItem = '';
self.homepage = response.data.homepage;
}
})
.catch(function (error)
{
if(error.response.data.errors)
{
publishController.errors.message = error.response.data.errors;
}
});
}
}
})
checkMove : function(evt)
{
if(evt.dragged.classList.contains('folder') && evt.from.parentNode.id != evt.to.parentNode.id)
{
return false;
}
if(evt.dragged.firstChild.className == 'active' && !editor.draftDisabled)
{
publishController.errors.message = "Please save your changes before you move the file";
return false;
}
return true;
},
onStart : function(evt)
{
/* delete error messages if exist *
publishController.errors.message = false;
},
getUrl : function(root, url)
{
return root + '/tm/content/' + this.$root.$data.editormode + url;
},
checkActive : function(active,parent)
{
if(active && !parent)
{
return 'active';
}
return 'inactive';
},
checkActive : function(active,parent)
{
if(active && !parent)
{
return 'active';
}
return 'inactive';
},
addFile : function(type)
{
publishController.errors.message = false;
if(this.format.test(this.newItem) || !this.newItem || this.newItem.length > 40)
{
publishController.errors.message = 'Special Characters are not allowed. Length between 1 and 40.';
return;
}
self = this;
self.freeze = true;
self.errors = {title: false, content: false, message: false};
myaxios.post('/api/v1/baseitem',{
'item_name': this.newItem,
'type': type,
'url': document.getElementById("path").value,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
})
.then(function (response) {
self.freeze = false;
if(response.data.url)
{
window.location.replace(response.data.url);
}
if(response.data.data)
{
self.items = response.data.data;
self.newItem = '';
self.showForm = false;
}
})
.catch(function (error)
{
publishController.errors.message = error.response.data.errors;
});
},
addFile : function(type)
{
publishController.errors.message = false;
if(this.$root.$data.format.test(this.newItem) || !this.newItem || this.newItem.length > 60)
{
publishController.errors.message = 'Special Characters are not allowed. Length between 1 and 60.';
return;
}
var self = this;
self.$root.$data.freeze = true;
self.errors = {title: false, content: false, message: false};
myaxios.post('/api/v1/article',{
'folder_id': this.$el.id,
'item_name': this.newItem,
'type': type,
'url': document.getElementById("path").value,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
})
.then(function (response) {
self.$root.$data.freeze = false;
if(response.data.url)
{
window.location.replace(response.data.url);
}
if(response.data.data)
{
// evt.item.classList.remove("load");
self.$root.$data.items = response.data.data;
self.newItem = '';
self.showForm = false;
}
})
.catch(function (error)
{
if(error.response.data.errors)
{
publishController.errors.message = error.response.data.errors;
}
});
},
}
})
navigation.mount('#contentNavigation');
*/

File diff suppressed because one or more lines are too long

View File

@@ -36,7 +36,7 @@
<div class="max-w-6xl m-auto mt-7 flex justify-between" id="main" data-url="{{ base_url() }}">
<aside class="w-1/4">
{% include 'partials/contentNavi.twig' %}
<div id="contentNavigation" v-cloak></div>
</aside>
<article class="w-3/4 bg-stone-50 shadow-md p-8">
{% block content %}{% endblock %}
@@ -59,6 +59,9 @@
</script>
<script src="{{ base_url() }}/system/typemill/author/js/vue.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vue-eventbus.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/sortable.min.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vuedraggable.umd.min.js?v={{ settings.version }}"></script>
<script type="module" src="{{ base_url() }}/system/typemill/author/js/vue-contentnavi.js?v={{ settings.version }}"></script>
{% block javascript %}{% endblock %}

View File

@@ -177,6 +177,12 @@
<symbol id="icon-square-brackets" viewBox="0 0 21 21">
<path d="M 4.791 18.885 L 4.791 20.518 L 0 20.518 L 0 0 L 4.791 0 L 4.791 1.622 L 2.052 1.622 L 2.052 18.885 L 4.791 18.885 Z M 20.958 0 L 20.958 20.518 L 16.178 20.518 L 16.178 18.885 L 18.906 18.885 L 18.906 1.622 L 16.178 1.622 L 16.178 0 L 20.958 0 Z M 6.542 4.952 A 1.326 1.326 0 0 0 6.404 5.11 Q 6.102 5.516 6.102 6.166 A 2.167 2.167 0 0 0 6.15 6.638 A 1.453 1.453 0 0 0 6.553 7.38 A 1.472 1.472 0 0 0 7.616 7.82 A 1.702 1.702 0 0 0 7.626 7.82 A 1.445 1.445 0 0 0 8.669 7.385 Q 9.109 6.95 9.109 6.166 A 2.149 2.149 0 0 0 9.058 5.685 A 1.429 1.429 0 0 0 8.658 4.958 A 1.482 1.482 0 0 0 7.595 4.522 Q 6.982 4.522 6.542 4.952 Z M 12.311 4.952 A 1.326 1.326 0 0 0 12.173 5.11 Q 11.87 5.516 11.87 6.166 A 2.167 2.167 0 0 0 11.919 6.638 A 1.453 1.453 0 0 0 12.321 7.38 A 1.472 1.472 0 0 0 13.385 7.82 A 1.702 1.702 0 0 0 13.394 7.82 A 1.445 1.445 0 0 0 14.437 7.385 Q 14.878 6.95 14.878 6.166 A 2.149 2.149 0 0 0 14.827 5.685 A 1.429 1.429 0 0 0 14.427 4.958 A 1.482 1.482 0 0 0 13.363 4.522 Q 12.751 4.522 12.311 4.952 Z M 9.06 14.192 A 1.427 1.427 0 0 0 8.653 13.455 Q 8.196 13.02 7.584 13.02 A 1.442 1.442 0 0 0 6.542 13.449 A 1.326 1.326 0 0 0 6.404 13.607 Q 6.102 14.013 6.102 14.663 A 2.679 2.679 0 0 0 6.102 14.698 Q 6.105 14.959 6.16 15.18 A 1.407 1.407 0 0 0 6.542 15.866 A 1.455 1.455 0 0 0 7.595 16.296 Q 8.207 16.296 8.658 15.866 A 1.405 1.405 0 0 0 9.056 15.154 A 2.131 2.131 0 0 0 9.109 14.663 A 2.134 2.134 0 0 0 9.06 14.192 Z M 14.829 14.192 A 1.427 1.427 0 0 0 14.421 13.455 Q 13.965 13.02 13.353 13.02 A 1.442 1.442 0 0 0 12.311 13.449 A 1.326 1.326 0 0 0 12.173 13.607 Q 11.87 14.013 11.87 14.663 A 2.679 2.679 0 0 0 11.87 14.698 Q 11.874 14.959 11.928 15.18 A 1.407 1.407 0 0 0 12.311 15.866 A 1.455 1.455 0 0 0 13.363 16.296 Q 13.976 16.296 14.427 15.866 A 1.405 1.405 0 0 0 14.825 15.154 A 2.131 2.131 0 0 0 14.878 14.663 A 2.134 2.134 0 0 0 14.829 14.192 Z" />
</symbol>
<symbol id="icon-cheveron-down" viewBox="0 0 20 20">
<path d="M9.293 12.95l0.707 0.707 5.657-5.657-1.414-1.414-4.243 4.242-4.243-4.242-1.414 1.414z"></path>
</symbol>
<symbol id="icon-cheveron-up" viewBox="0 0 20 20">
<path d="M10.707 7.050l-0.707-0.707-5.657 5.657 1.414 1.414 4.243-4.242 4.243 4.242 1.414-1.414z"></path>
</symbol>
{{ assets.renderSvg() }}
<defs>
</svg>

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -12,6 +12,7 @@ use Typemill\Controllers\ControllerApiSystemExtensions;
use Typemill\Controllers\ControllerApiSystemLicense;
use Typemill\Controllers\ControllerApiSystemUsers;
use Typemill\Controllers\ControllerApiImage;
use Typemill\Controllers\ControllerApiAuthorArticle;
$app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) {
@@ -40,6 +41,10 @@ $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) {
$group->put('/image', ControllerApiMedia::class . ':publishImage')->setName('api.image.publish');
$group->delete('/image', ControllerApiMedia::class . ':deleteImage')->setName('api.image.delete');
# ARTICLE
$group->post('/article/sort', ControllerApiAuthorArticle::class . ':sortArticle')->setName('api.article.sort')->add(new ApiAuthorization($acl, 'content', 'view')); # author
$group->post('/article', ControllerApiAuthorArticle::class . ':createArticle')->setName('api.article.create')->add(new ApiAuthorization($acl, 'content', 'view')); # author
})->add(new ApiAuthentication());

View File

@@ -6,14 +6,18 @@ member:
- 'view'
- 'update'
- 'delete'
author:
name: author
contributor:
name: contributor
inherits: member
permissions:
mycontent:
- 'view'
- 'create'
- 'update'
author:
name: author
inherits: contributor
permissions:
content:
- 'view'
editor: