1
0
mirror of https://github.com/typemill/typemill.git synced 2025-07-18 21:11:13 +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

@@ -1,23 +0,0 @@
meta:
title: 'Get Help'
description: 'If you need any help, then please read the documentation on typemill.net first. Some short video-tutorials are in work right'
heroimage: null
heroimagealt: null
owner: null
author: 'Sebastian Schürmanns'
allowedrole: null
alloweduser: null
manualdate: null
modified: '2021-06-13'
created: null
time: null
navtitle: ''
hide: false
seo:
heroimage: media/live/bildschirmfoto-zu-2019-08-30-20-46-29.png
heroimagealt: Alt-Text
customfields:
myfield:
bla:
- text
- test

View File

@@ -0,0 +1,15 @@
meta:
navtitle: 'colors and fonts'
hide: false
title: 'Colors and Fonts'
description: 'First of all cyanine supports individual logos. If you want to use our logo, then please upload it in the system settings. Cyanine will automatically replace the title text with your logo. '
heroimage: null
heroimagealt: null
owner: null
author: trendschau
allowedrole: null
alloweduser: null
manualdate: null
modified: '2021-10-04'
created: '2020-06-11'
time: 20-37-12

View File

@@ -0,0 +1,15 @@
meta:
title: 'The Landingpage'
description: "Cyanine provides an optional landingpage with six segments: \nIntro with the content of the home page and an additional link/button.\nInfo with individual markdown content.\nTeaser with two elements. Each element has a headline, a text and a link/button.\nContrast with a headline, text-input and a"
heroimage: ''
heroimagealt: null
owner: trendschau
author: 'Sebastian Schürmanns'
allowedrole: null
alloweduser: null
manualdate: null
modified: '2021-05-18'
created: '2021-06-17'
time: 16-23-19
navtitle: landingpage
hide: false

File diff suppressed because one or more lines are too long

View File

@@ -4,11 +4,11 @@
noindex: false
path: /00-welcome
keyPath: 0
/welcome/setup-your-website:
/welcome/markdown-test:
navtitle: ''
hide: false
noindex: false
path: /00-welcome/00-setup-your-website.md
path: /00-welcome/00-markdown-test.md
keyPath: '0.0'
/welcome/manage-access:
navtitle: ''
@@ -16,23 +16,23 @@
noindex: false
path: /00-welcome/01-manage-access.md
keyPath: '0.1'
/welcome/write-content:
navtitle: ''
hide: false
noindex: false
path: /00-welcome/02-write-content.md
keyPath: '0.2'
/welcome/get-help:
navtitle: ''
hide: false
noindex: false
path: /00-welcome/03-get-help.md
keyPath: '0.3'
/welcome/markdown-test:
path: /00-welcome/02-get-help.md
keyPath: '0.2'
/welcome/setup-your-website:
navtitle: ''
hide: false
noindex: false
path: /00-welcome/04-markdown-test.md
path: /00-welcome/03-setup-your-website.md
keyPath: '0.3'
/welcome/write-content:
navtitle: ''
hide: false
noindex: false
path: /00-welcome/04-write-content.md
keyPath: '0.4'
/cyanine-theme:
navtitle: ''
@@ -40,17 +40,17 @@
noindex: false
path: /01-cyanine-theme
keyPath: 1
/cyanine-theme/landingpage:
navtitle: ''
hide: false
noindex: false
path: /01-cyanine-theme/00-landingpage.md
keyPath: '1.0'
/cyanine-theme/colors-and-fonts:
navtitle: ''
hide: false
noindex: false
path: /01-cyanine-theme/01-colors-and-fonts.md
path: /01-cyanine-theme/00-colors-and-fonts.md
keyPath: '1.0'
/cyanine-theme/landingpage:
navtitle: ''
hide: false
noindex: false
path: /01-cyanine-theme/01-landingpage.md
keyPath: '1.1'
/cyanine-theme/footer:
navtitle: ''

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:

View File

@@ -8,7 +8,8 @@ module.exports = {
},
opacity: {
'0': '0',
}
},
visibility: ["group-hover"],
},
},
plugins: [],