1
0
mirror of https://github.com/typemill/typemill.git synced 2025-07-16 12:06:21 +02:00

RC2: Fix frontend navigation, darkmode, and more

This commit is contained in:
trendschau
2023-10-12 19:17:59 +02:00
parent 5d1383692d
commit 7f89c4bcbb
31 changed files with 616 additions and 261 deletions

10
composer.lock generated
View File

@ -227,12 +227,12 @@
"source": {
"type": "git",
"url": "https://github.com/Gregwar/Captcha.git",
"reference": "5b8323637b502d3dbcbe28a74633dad8aed63452"
"reference": "229d3cdfe33d6f1349e0aec94a26e9205a6db08e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Gregwar/Captcha/zipball/5b8323637b502d3dbcbe28a74633dad8aed63452",
"reference": "5b8323637b502d3dbcbe28a74633dad8aed63452",
"url": "https://api.github.com/repos/Gregwar/Captcha/zipball/229d3cdfe33d6f1349e0aec94a26e9205a6db08e",
"reference": "229d3cdfe33d6f1349e0aec94a26e9205a6db08e",
"shasum": ""
},
"require": {
@ -275,9 +275,9 @@
],
"support": {
"issues": "https://github.com/Gregwar/Captcha/issues",
"source": "https://github.com/Gregwar/Captcha/tree/master"
"source": "https://github.com/Gregwar/Captcha/tree/v1.2.1"
},
"time": "2023-05-08T15:17:31+00:00"
"time": "2023-09-26T13:45:37+00:00"
},
{
"name": "jbroadway/urlify",

View File

@ -14,5 +14,5 @@ meta:
time: 22-09-48
reference: null
referencetype: null
hide: false
hide: true
noindex: false

View File

@ -6,9 +6,5 @@ If you found a bug or if you have a question, then please open a new issue on [G
Do you need professional help, an individual theme or a special plugin? You can hire us at [Trendschau Digital](https://trendschau.net/typemill-development).
[recording 2023 05 11 at 235818 (GIF, 1.30 MB)](media/files/recording-2023-05-11-at-235818.gif){.tm-download file-gif}
[Contributions](https://github.com/typemill/typemill#contributors--supporters), [donations](https://www.paypal.me/typemill) and [feedback](https://github.com/typemill/typemill/issues) for this open source project are always welcome.
![alt](media/live/youtube-6i2-uv88gke.jpeg){.center loading="lazy"}

View File

@ -0,0 +1,166 @@
# ToDos Version 2
[TOC]
## System settings
* DONE: Migrate from backend to frontend with vue and api
* DONE: Redesign
* DONE: License feature
* DONE: Enhance with plugins
## Visual Editor
* DONE: Refactor and redesign
* DONE: Fix toc component in new block
* DONE: Fix hr component in new block
* DONE: finish shortcode component
* DONE: Fix inline formats
* DONE: fix lenght of page
* DONE: Fix design of new block at the end (background color)
* DONE: Move Block
* DONE: Fix headline design
* DONE: Fix save on two enter
* DONE: fix quote design
* DONE: Fix toc preview
* DONE: disable enable
* DONE: Add load sign (from navigation)
* DONE: File is not published from tmp to media/files if you save the block.
## Raw Editor
* DONE: Refactor and redesign
* DONE: Integrate highlighting
## Navigation
* DONE: Refactor and redesign
* DONE: fix status in navigation
* DONE: refresh navigation after changes
## Publish Controller
* DONE: Refactor and redesign
* DONE: Create
* DONE: publish
* DONE: unpublish
* DONE: discard
* DONE: delete
* DONE: save draft
* DONE: switch to raw
## Meta Tabs
* DONE: Refactor and redesign
* DONE: Enhance with plugins
## Medialib
* DONE: Refactor and redesign
## Posts
* DONE: Refactor and redesign
## Plugins
* Asset Class in progress
## Frontend
* DONE: Refactor
* DONE: Test restrictions
## Other big tasks
* DONE: System setup
* DONE: Recover Password
## Medium tasks
* DONE: Merge processAssets modell
* DONE: Table of content duplicated for published pages
* DONE: Session handling: csrf fail and session start error if restrictions are active
* DONE: Image and files for meta
## Open tasks
* DONE: Sitemap and ping
* DONE: Version check
* DONE: Proxy support
* DONE: SVG checker: https://github.com/TribalSystems/SVG-Sanitizer
* DONE: Backend form builder
* DONE: Image generation on the fly
* DONE: Delete folder in base level
* DONE: Make folder delete easier with glob or scandir
* DONE: fix error messages (check models)
* DONE: error status codes (check middleware)
* DONE: Warn if open another block
* DONE: Customfields not styled yet
* DOING: Fix error api systemnavi + validate
* FIXED: System stores html or sends wrong error messsages
* FIXED: Wrong frontend navigation if unpublished pages
* DONE: Icon for hidden pages
* DOING: Responsive design
* DONE: Captcha integration
* DONE: Solution for logo and favicon
* FIXED: Raw editor jumps if you edit long text at the end
* Reference feature
* Typemill Utilities
* Markdown secure rendering
* finish youtube component
* Handle formdata centrally ???
* BUG: Error fields in account form not styled correctly
## later
* Clear cache
* Show security Log
* User search only for +10 users
* For api translations should be done completely in backoffice
* Change translation files so they are loaded in settings instead of adding them manually to settings-defaults.yaml
## Cleanups:
* DONE: Events
* DONE: Error messages
* DONE: Translations
## Info: Select userroles
* Userroles for file restriction: in vue-blox-components loaded via api
* Userroles for userfields: in php model user getUserFields()
* Userroles for meta: in php controller apiAuthorMeta getMeta()
* Plugins and themes: in php model extension getThemeDefinitions()
## Info: License Check
* On activation in apiControllerExtension. It checks the license in yaml.
* In plugin php code with setPremiumLicense
* In static plugins, it checks manual premium list and method setPremiumLicense and more
## Plugins
* MAKER: Rebuild search
* MAKER: Rebuild contactform with shortcode
## Status codes
| Status code | Description |
|---|---|
| 200 ok | cell |
| 400 bad request | The request was unacceptable due to missing or invalid parameter. |
| 401 unauthorized | The request requires an authorization. |
| (402 request failed) | The parameters where there but the request failed for other reasons. |
| 403 forbidden | The user is authenticated but he has not enough rights. |
| 404 not found | new |
| 500 internal server error | new |
## Upgrade
* Delete content of system folder
* Upload new content of system folder with folders typemill and vendor
* Delete settings file
* upload new index.php file
* Upload new htaccess file

View File

@ -1 +1 @@
["# ToDos Version 2","[TOC]","## System settings","* DONE: Migrate from backend to frontend with vue and api\n* DONE: Redesign\n* DONE: License feature\n* DONE: Enhance with plugins","## Visual Editor","* DONE: Refactor and redesign\n* DONE: Fix toc component in new block\n* DONE: Fix hr component in new block\n* DONE: finish shortcode component\n* DONE: Fix inline formats\n* DONE: fix lenght of page\n* DONE: Fix design of new block at the end (background color)\n* DONE: Move Block\n* DONE: Fix headline design\n* DONE: Fix save on two enter\n* DONE: fix quote design\n* DONE: Fix toc preview\n* DONE: disable enable \n* DONE: Add load sign (from navigation)\n* DONE: File is not published from tmp to media\/files if you save the block.","## Raw Editor","* DONE: Refactor and redesign\n* DONE: Integrate highlighting","## Navigation","* DONE: Refactor and redesign\n* DONE: fix status in navigation\n* DONE: refresh navigation after changes","## Publish Controller","* DONE: Refactor and redesign\n* DONE: Create \n* DONE: publish\n* DONE: unpublish\n* DONE: discard\n* DONE: delete\n* DONE: save draft\n* DONE: switch to raw","## Meta Tabs","* DONE: Refactor and redesign\n* DONE: Enhance with plugins","## Medialib","* DONE: Refactor and redesign","## Posts","* DONE: Refactor and redesign","## Plugins","* Asset Class in progress","## Frontend","* DONE: Refactor\n* DONE: Test restrictions","## Other big tasks","* DONE: System setup\n* DONE: Recover Password","## Medium tasks","* DONE: Merge processAssets modell\n* DONE: Table of content duplicated for published pages\n* DONE: Session handling: csrf fail and session start error if restrictions are active\n* DONE: Image and files for meta","## Open tasks","* DONE: Sitemap and ping\n* DONE: Version check\n* DONE: Proxy support\n* DONE: SVG checker: https:\/\/github.com\/TribalSystems\/SVG-Sanitizer\n* DONE: Backend form builder\n* DONE: Image generation on the fly\n* DONE: Delete folder in base level\n* DONE: Make folder delete easier with glob or scandir\n* DONE: fix error messages (check models)\n* DONE: error status codes (check middleware)\n* DONE: Warn if open another block\n* DONE: Customfields not styled yet\n* DOING: Fix error api systemnavi + validate\n* FIXED: System stores html or sends wrong error messsages\n* FIXED: Wrong frontend navigation if unpublished pages\n* DONE: Icon for hidden pages\n* DOING: Responsive design\n* DONE: Captcha integration\n* DONE: Solution for logo and favicon\n* Reference feature\n* Typemill Utilities\n* Markdown secure rendering\n* finish youtube component\n* Handle formdata centrally ???\n* BUG: Raw editor jumps if you edit long text at the end\n* BUG: Error fields in account form not styled correctly","## later","* Clear cache\n* Show security Log\n* User search only for +10 users\n* For api translations should be done completely in backoffice\n* Change translation files so they are loaded in settings instead of adding them manually to settings-defaults.yaml","## Cleanups:","* DONE: Events\n* DONE: Error messages\n* DONE: Translations","## Info: Select userroles","* Userroles for file restriction: in vue-blox-components loaded via api\n* Userroles for userfields: in php model user getUserFields()\n* Userroles for meta: in php controller apiAuthorMeta getMeta()\n* Plugins and themes: in php model extension getThemeDefinitions()","## Info: License Check","* On activation in apiControllerExtension. It checks the license in yaml.\n* In plugin php code with setPremiumLicense\n* In static plugins, it checks manual premium list and method setPremiumLicense and more ","## Plugins","* MAKER: Rebuild search\n* MAKER: Rebuild contactform with shortcode","## Status codes","| Status code | Description | \n|---|---|\n| 200 ok | cell | \n| 400 bad request | The request was unacceptable due to missing or invalid parameter. | \n| 401 unauthorized | The request requires an authorization. | \n| (402 request failed) | The parameters where there but the request failed for other reasons. | \n| 403 forbidden | The user is authenticated but he has not enough rights. | \n| 404 not found | new | \n| 500 internal server error | new |"]
["# ToDos Version 2","[TOC]","## System settings","* DONE: Migrate from backend to frontend with vue and api\n* DONE: Redesign\n* DONE: License feature\n* DONE: Enhance with plugins","## Visual Editor","* DONE: Refactor and redesign\n* DONE: Fix toc component in new block\n* DONE: Fix hr component in new block\n* DONE: finish shortcode component\n* DONE: Fix inline formats\n* DONE: fix lenght of page\n* DONE: Fix design of new block at the end (background color)\n* DONE: Move Block\n* DONE: Fix headline design\n* DONE: Fix save on two enter\n* DONE: fix quote design\n* DONE: Fix toc preview\n* DONE: disable enable \n* DONE: Add load sign (from navigation)\n* DONE: File is not published from tmp to media\/files if you save the block.","## Raw Editor","* DONE: Refactor and redesign\n* DONE: Integrate highlighting","## Navigation","* DONE: Refactor and redesign\n* DONE: fix status in navigation\n* DONE: refresh navigation after changes","## Publish Controller","* DONE: Refactor and redesign\n* DONE: Create \n* DONE: publish\n* DONE: unpublish\n* DONE: discard\n* DONE: delete\n* DONE: save draft\n* DONE: switch to raw","## Meta Tabs","* DONE: Refactor and redesign\n* DONE: Enhance with plugins","## Medialib","* DONE: Refactor and redesign","## Posts","* DONE: Refactor and redesign","## Plugins","* Asset Class in progress","## Frontend","* DONE: Refactor\n* DONE: Test restrictions","## Other big tasks","* DONE: System setup\n* DONE: Recover Password","## Medium tasks","* DONE: Merge processAssets modell\n* DONE: Table of content duplicated for published pages\n* DONE: Session handling: csrf fail and session start error if restrictions are active\n* DONE: Image and files for meta","## Open tasks","* DONE: Sitemap and ping\n* DONE: Version check\n* DONE: Proxy support\n* DONE: SVG checker: https:\/\/github.com\/TribalSystems\/SVG-Sanitizer\n* DONE: Backend form builder\n* DONE: Image generation on the fly\n* DONE: Delete folder in base level\n* DONE: Make folder delete easier with glob or scandir\n* DONE: fix error messages (check models)\n* DONE: error status codes (check middleware)\n* DONE: Warn if open another block\n* DONE: Customfields not styled yet\n* DOING: Fix error api systemnavi + validate\n* FIXED: System stores html or sends wrong error messsages\n* FIXED: Wrong frontend navigation if unpublished pages\n* DONE: Icon for hidden pages\n* DOING: Responsive design\n* DONE: Captcha integration\n* DONE: Solution for logo and favicon\n* FIXED: Raw editor jumps if you edit long text at the end\n* Reference feature\n* Typemill Utilities\n* Markdown secure rendering\n* finish youtube component\n* Handle formdata centrally ???\n* BUG: Error fields in account form not styled correctly\n* DONE: Update CSS for themes\n* BUG: Codefield jumps on editing","## later","* Clear cache\n* Show security Log\n* User search only for +10 users\n* For api translations should be done completely in backoffice\n* Change translation files so they are loaded in settings instead of adding them manually to settings-defaults.yaml","## Cleanups:","* DONE: Events\n* DONE: Error messages\n* DONE: Translations","## Info: Select userroles","* Userroles for file restriction: in vue-blox-components loaded via api\n* Userroles for userfields: in php model user getUserFields()\n* Userroles for meta: in php controller apiAuthorMeta getMeta()\n* Plugins and themes: in php model extension getThemeDefinitions()","## Info: License Check","* On activation in apiControllerExtension. It checks the license in yaml.\n* In plugin php code with setPremiumLicense\n* In static plugins, it checks manual premium list and method setPremiumLicense and more ","## Plugins","* MAKER: Rebuild search\n* MAKER: Rebuild contactform with shortcode","## Status codes","| Status code | Description | \n|---|---|\n| 200 ok | cell | \n| 400 bad request | The request was unacceptable due to missing or invalid parameter. | \n| 401 unauthorized | The request requires an authorization. | \n| (402 request failed) | The parameters where there but the request failed for other reasons. | \n| 403 forbidden | The user is authenticated but he has not enough rights. | \n| 404 not found | new | \n| 500 internal server error | new |","## Upgrade","* Delete content of system folder\n* Upload new content of system folder with folders typemill and vendor\n* Delete settings file \n* upload new index.php file\n* Upload new htaccess file"]

View File

@ -0,0 +1,30 @@
.landingpageintro h1{
display: inline-block;
background: white;
padding: 4px 10px;
margin: 5px;
}
.landingpageintro p{
display: inline;
background: white;
line-height: 2.1rem;
padding: 6px 4px 4px;
}
article a[href^="http"]::after,
article a[href^="https://"]::after
{
content: "";
width: 11px;
height: 11px;
margin-left: 4px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z'/%3E%3Cpath fill-rule='evenodd' d='M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z'/%3E%3C/svg%3E");
background-position: center;
background-repeat: no-repeat;
background-size: contain;
display: inline-block;
}
a[href^="https://www.electrictoolbox.com"] {
background: none;
padding-right: 0;
}

File diff suppressed because one or more lines are too long

View File

@ -18,7 +18,7 @@
keyPath: '0.1'
/welcome/write-content:
navtitle: 'write content'
hide: false
hide: true
noindex: false
path: /00-welcome/02-write-content.md
keyPath: '0.2'
@ -38,7 +38,7 @@
navtitle: 'To Dos'
hide: false
noindex: false
path: /00-welcome/05-todos.txt
path: /00-welcome/05-todos.txtmd
keyPath: '0.5'
/cyanine-theme:
navtitle: 'cyanine theme'

File diff suppressed because one or more lines are too long

View File

@ -47,4 +47,25 @@ class ControllerApiSystemThemes extends Controller
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
}
public function updateThemeCss(Request $request, Response $response)
{
$params = $request->getParsedBody();
$themename = $params['theme'];
$themecss = $params['css'];
# validate css input
$themecss = strip_tags($themecss);
# store updated css
$settings = new Settings();
$updatedSettings = $settings->updateThemeCss($themename, $themecss);
$response->getBody()->write(json_encode([
'message' => Translations::translate('settings have been saved'),
'code' => $updatedSettings
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
}
}

View File

@ -29,13 +29,11 @@ class ControllerWebFrontend extends Controller
$userrole = $request->getAttribute('c_userrole');
$username = $request->getAttribute('c_username');
# GET THE NAVIGATION
$navigation = new Navigation();
$liveNavigation = $navigation->getLiveNavigation($urlinfo, $langattr);
$draftNavigation = $navigation->getDraftNavigation($urlinfo, $langattr);
$home = false;
# GET THE PAGINATION
$currentpage = $navigation->getCurrentPage($args);
if($currentpage)
@ -56,6 +54,7 @@ class ControllerWebFrontend extends Controller
$extendedNavigation = $navigation->getExtendedNavigation($urlinfo, $langattr);
$pageinfo = $extendedNavigation[$url] ?? false;
if(!$pageinfo)
{
return $this->c->get('view')->render($response->withStatus(404), '404.twig', [
@ -66,23 +65,46 @@ class ControllerWebFrontend extends Controller
$keyPathArray = explode(".", $pageinfo['keyPath']);
$liveNavigation = $navigation->setActiveNaviItems($liveNavigation, $keyPathArray);
#
$item = $navigation->getItemWithKeyPath($draftNavigation, $keyPathArray);
$item = $navigation->getItemWithKeyPath($liveNavigation, $keyPathArray);
# but what if parent is unpublished ??
if(!$item)
{
return $this->c->get('view')->render($response->withStatus(404), '404.twig', [
'title' => 'Page not found',
'description' => 'We did not find the page you where looking for.'
]);
}
}
$liveNavigation = $this->c->get('dispatcher')->dispatch(new OnPagetreeLoaded($liveNavigation), 'onPagetreeLoaded')->getData();
# CREATE THE BREADCRUMB
$breadcrumb = $navigation->getBreadcrumb($liveNavigation, $item->keyPathArray);
$breadcrumb = $navigation->getBreadcrumb($draftNavigation, $item->keyPathArray);
$breadcrumb = $this->c->get('dispatcher')->dispatch(new OnBreadcrumbLoaded($breadcrumb), 'onBreadcrumbLoaded')->getData();
# CHECK IF WHOLE TREE IS PUBLISHED
foreach($breadcrumb as $page)
{
if($page->status == 'unpublished')
{
return $this->c->get('view')->render($response->withStatus(404), '404.twig', [
'title' => 'Page not found',
'description' => 'We did not find the page you where looking for.'
]);
}
}
# GET THE LIVE NAVIGATION (keyPathArray does not match here!!!)
$liveNavigation = $navigation->getLiveNavigation($urlinfo, $langattr);
# STRIP OUT HIDDEN PAGES
$liveNavigation = $navigation->removeHiddenPages($liveNavigation);
# we could cache navigation without hidden pages??
# SET PAGEs ACTIVE
$liveNavigation = $navigation->setActiveNaviItems($liveNavigation, $breadcrumb);
# DISPATCH LIVE NAVIGATION
$liveNavigation = $this->c->get('dispatcher')->dispatch(new OnPagetreeLoaded($liveNavigation), 'onPagetreeLoaded')->getData();
# ADD BACKWARD-/FORWARD PAGINATION
$item = $navigation->getPagingForItem($liveNavigation, $item);

View File

@ -82,14 +82,79 @@ class Folder
$folderContent[] = $item;
}
/* store the name of the last file */
# store the name of the last file
$last = implode($nameParts);
}
}
}
return $folderContent;
}
/*
* scans content of a folder recursively and keeps the index order
* vars: folder path as string
* returns: multi-dimensional array with names of folders and files
*/
public function scanFolderCheckIndex($folderPath, $draft = false)
{
$folderItems = scandir($folderPath);
$folderContent = array();
# if it is the live version and if it is a folder that is not published, then do not show the folder and its content.
if(!$draft && !in_array('index.md', $folderItems)){ return false; }
foreach ($folderItems as $key => $item)
{
if (!in_array($item, array(".","..")) && substr($item, 0, 1) != '.')
{
if (is_dir($folderPath . DIRECTORY_SEPARATOR . $item))
{
$subFolder = $item;
$folderPublished = file_exists($folderPath . DIRECTORY_SEPARATOR . $item . DIRECTORY_SEPARATOR . 'index.md');
# scan that folder only if it is a draft or if the folder is published (contains index.md)
if($draft OR $folderPublished)
{
# we need s countable index here
$folderContent[$subFolder] = $this->scanFolder($folderPath . DIRECTORY_SEPARATOR . $subFolder, $draft);
}
}
else
{
$nameParts = $this->getStringParts($item);
$fileType = array_pop($nameParts);
if($fileType == 'md')
{
$folderContent[] = $item;
}
if($fileType == 'txt')
{
if(isset($last) && ($last == implode($nameParts)) )
{
array_pop($folderContent);
$item = $item . 'md';
}
$folderContent[] = $item;
if(!$draft)
{
$index = count($folderContent)-1;
unset($folderContent[$index]);
}
}
# store the name of the last file
$last = implode($nameParts);
}
}
}
return $folderContent;
}
/*
* Transforms array of folder item into an array of item-objects with additional information for each item

View File

@ -80,15 +80,19 @@ class Navigation extends Folder
$allowedmainnavi = [];
$activeitem = false;
foreach($mainnavi as $name => $naviitem)
{
if($acl->isAllowed($userrole, $naviitem['aclresource'], $naviitem['aclprivilege']))
{
# set the navi of current route active
$thisRoute = '/tm/' . $name;
if(strpos($urlinfo['route'], $thisRoute) !== false)
{
$naviitem['active'] = true;
$activeitem = true;
}
$allowedmainnavi[$name] = $naviitem;
@ -99,6 +103,12 @@ class Navigation extends Folder
if(isset($allowedmainnavi['system']))
{
unset($allowedmainnavi['account']);
# if no active item has been found, then it is submenu under system
if(!$activeitem)
{
$allowedmainnavi['system']['active'] = true;
}
}
# set correct editor mode according to user settings
@ -112,16 +122,19 @@ class Navigation extends Folder
public function getSystemNavigation($userrole, $acl, $urlinfo, $dispatcher, $routeparser)
{
$systemnavi = $this->storage->getYaml('systemSettings', '', 'systemnavi.yaml');
$systemnavi = $dispatcher->dispatch(new OnSystemnaviLoaded($systemnavi), 'onSystemnaviLoaded')->getData();
$systemnavi = $this->storage->getYaml('systemSettings', '', 'systemnavi.yaml');
$systemnavi = $dispatcher->dispatch(new OnSystemnaviLoaded($systemnavi), 'onSystemnaviLoaded')->getData();
$allowedsystemnavi = [];
$allowedsystemnavi = [];
$route = trim($urlinfo['route'], '/');
foreach($systemnavi as $name => $naviitem)
{
$naviitem['url'] = $routeparser->urlFor($naviitem['routename']);
$naviitem['url'] = $routeparser->urlFor($naviitem['routename']);
$itemurl = trim($naviitem['url'], '/');
if(strpos( trim($naviitem['url'], '/'), trim($urlinfo['route'], '/')))
if(strpos( $itemurl, $route ) !== false)
{
$naviitem['active'] = true;
}
@ -224,7 +237,6 @@ class Navigation extends Folder
return $this->basicLiveNavigation;
}
# creates a fresh structure with published and non-published pages for the author
public function createBasicLiveNavigation($urlinfo, $language)
{
# scan the content of the folder
@ -330,6 +342,7 @@ class Navigation extends Folder
public function getItemWithKeyPath($navigation, array $searchArray, $baseUrl = null)
{
$item = false;
# if it is the homepage
@ -352,7 +365,37 @@ class Navigation extends Folder
return $item;
}
public function setActiveNaviItems($navigation, array $searchArray)
# used with scan folder that generates own indexes for live version
public function setActiveNaviItems($navigation, $breadcrumb)
{
foreach($breadcrumb as $crumbkey => $page)
{
foreach($navigation as $itemkey => $item)
{
if($page->urlRelWoF == $item->urlRelWoF)
{
unset($breadcrumb[$crumbkey]);
if(empty($breadcrumb))
{
$navigation[$itemkey]->active = true;
}
elseif(isset($navigation[$itemkey]->folderContent))
{
$navigation[$itemkey]->activeParent = true;
$navigation[$itemkey]->folderContent = $this->setActiveNaviItems($navigation[$itemkey]->folderContent, $breadcrumb);
}
break;
}
}
}
return $navigation;
}
# used with scan folder that keeps index from draft version
public function setActiveNaviItemsWithKeyPath($navigation, array $searchArray)
{
foreach($searchArray as $key => $itemKey)
{
@ -491,6 +534,7 @@ class Navigation extends Folder
if(!isset($navigation[$searchArray[$i]])){ return false; }
$item = $navigation[$searchArray[$i]];
if($i == count($searchArray)-1)
{
$item->active = true;
@ -536,9 +580,16 @@ class Navigation extends Folder
$item->thisChapter = $this->getItemWithKeyPath($navigation, $thisChapArray);
}
$flat = $this->flatten($navigation, $item->keyPath);
$flat = $this->flatten($navigation, $item->urlRel);
$itemkey = $flat[0];
# if no previous or next is found (e.g. hidden page)
if(!is_int($itemkey))
{
return $item;
}
if($itemkey > 1)
{
$item->prevItem = $flat[$itemkey-1];
@ -551,13 +602,13 @@ class Navigation extends Folder
return $item;
}
public function flatten($navigation, $keyPath, $flat = [])
{
public function flatten($navigation, $urlRel, $flat = [])
{
foreach($navigation as $key => $item)
{
$flat[] = clone($item);
if($keyPath === $item->keyPath)
if($item->urlRel == $urlRel)
{
array_unshift($flat, count($flat));
}
@ -566,134 +617,13 @@ class Navigation extends Folder
{
$last = array_key_last($flat);
unset($flat[$last]->folderContent);
$flat = $this->flatten($item->folderContent, $keyPath, $flat);
$flat = $this->flatten($item->folderContent, $urlRel, $flat);
}
}
return $flat;
}
public function getPagingForItemOld($navigation, $item)
{
# if page is home
if(trim($item->pathWithoutType, DIRECTORY_SEPARATOR) == 'index')
{
return $item;
}
$keyPos = count($item->keyPathArray)-1;
$thisChapArray = $item->keyPathArray;
$nextItemArray = $item->keyPathArray;
$prevItemArray = $item->keyPathArray;
$item->thisChapter = false;
$item->prevItem = false;
$item->nextItem = false;
/************************
* ADD THIS CHAPTER *
************************/
if($keyPos > 0)
{
array_pop($thisChapArray);
$item->thisChapter = $this->getItemWithKeyPath($navigation, $thisChapArray);
}
/************************
* ADD NEXT ITEM *
************************/
if($item->elementType == 'folder')
{
# get the first element in the folder
$item->nextItem = isset($item->folderContent[0]) ? clone($item->folderContent[0]) : false;
}
# the item is a file or an empty folder
if(!$item->nextItem)
{
# walk to the next file in the same hierarchy
$nextItemArray[$keyPos]++;
# get the key of the last element in this hierarchy level
# if there is no chapter, then it is probably an empty first-level-folder. Count content to get the number of first level items
$lastKey = $item->thisChapter ? array_key_last($item->thisChapter->folderContent) : count($navigation);
# as long as the nextItemArray is smaller than the last key in this hierarchy level, search for the next item
# this ensures that it does not stop if key is missing (e.g. if the next page is hidden)
while( ($nextItemArray[$keyPos] <= $lastKey) && !$item->nextItem = $this->getItemWithKeyPath($navigation, $nextItemArray) )
{
$nextItemArray[$keyPos]++;
}
}
# there is no next file or folder in this level, so walk up the hierarchy to the next folder or file
while(!$item->nextItem)
{
# delete the array level with the current item, so you are in the parent folder
array_pop($nextItemArray);
# if the array is empty now, then you where in the base level already, so break
if(empty($nextItemArray)) break;
# define the key position where you are right now
$newKeyPos = count($nextItemArray)-1;
# go to the next position
$nextItemArray[$newKeyPos]++;
# search for 5 items in case there are some hidden elements
$maxlength = $nextItemArray[$newKeyPos]+5;
while( ($nextItemArray[$newKeyPos] <= $maxlength) && !$item->nextItem = $this->getItemWithKeyPath($navigation, $nextItemArray) )
{
$nextItemArray[$newKeyPos]++;
}
}
/************************
* ADD PREVIOUS ITEM *
************************/
# check if element is the first in the array
$first = ($prevItemArray[$keyPos] == 0) ? true : false;
if(!$first)
{
$prevItemArray[$keyPos]--;
while($prevItemArray[$keyPos] >= 0 && !$item->prevItem = $this->getItemWithKeyPath($navigation, $prevItemArray))
{
$prevItemArray[$keyPos]--;
}
# if no item is found, then all previous items are hidden, so set first item to true and it will walk up the array later
if(!$item->prevItem)
{
$first = true;
}
elseif($item->prevItem && $item->prevItem->elementType == 'folder' && !empty($item->prevItem->folderContent))
{
# if the previous item is a folder, the get the last item of that folder
$item->prevItem = $this->getLastItemOfFolder($item->prevItem);
}
}
# if it is the first item in the folder (or all other files are hidden)
if($first)
{
# then the previous item is the containing chapter
$item->prevItem = $item->thisChapter;
}
if($item->prevItem && $item->prevItem->elementType == 'folder'){ unset($item->prevItem->folderContent); }
if($item->nextItem && $item->nextItem->elementType == 'folder'){ unset($item->nextItem->folderContent); }
if($item->thisChapter){unset($item->thisChapter->folderContent); }
return $item;
}
public function getLastItemOfFolder($folder)
{
$lastItem = end($folder->folderContent);

View File

@ -156,6 +156,16 @@ class Settings
return false;
}
public function updateThemeCss(string $name, string $css)
{
if($this->storage->writeFile('cacheFolder', '', $name . '-custom.css', $css))
{
return true;
}
return false;
}
private function array_is_list(array $arr)
{
if ($arr === [])

View File

@ -1,6 +1,6 @@
{% extends 'layouts/layoutAuth.twig' %}
{% block title %}{{ 'Setup'|translate }}{% endblock %}
{% block title %}{{ translate('Setup') }}{% endblock %}
{% block content %}
@ -83,7 +83,7 @@
</ul>
{% else %}
<h2 class="text-4xl py-5">{{ translate('Welcome to Typemill') }}</h2>
<p>{{ translate('Hey writer, author, editor, content-guru, or website-manager.') }} {{ translate(' We hope you like Typemill, because we coded it just for you.'}}</p>
<p>{{ translate('Hey writer, author, editor, content-guru, or website-manager.') }} {{ translate(' We hope you like Typemill, because we coded it just for you.') }}</p>
<p>{{ translate('Get inspired and enjoy your writing') }}!</p>
{% endif %}
</div>

View File

@ -1022,10 +1022,6 @@ video {
width: 33.333333%;
}
.w-half {
width: 48%;
}
.w-80 {
width: 20rem;
}
@ -1046,6 +1042,10 @@ video {
width: 60%;
}
.w-half {
width: 48%;
}
.w-3\/4 {
width: 75%;
}
@ -1144,10 +1144,6 @@ video {
list-style-type: disc;
}
.flex-row {
flex-direction: row;
}
.flex-col {
flex-direction: column;
}
@ -2113,21 +2109,109 @@ video {
visibility: visible;
}
@media (prefers-color-scheme: dark) {
.dark\:text-gray-400 {
--tw-text-opacity: 1;
color: rgb(156 163 175 / var(--tw-text-opacity));
}
.dark .dark\:border-stone-900 {
--tw-border-opacity: 1;
border-color: rgb(28 25 23 / var(--tw-border-opacity));
}
@media (min-width: 640px) {
.sm\:table-row {
display: table-row;
}
.dark .dark\:border-stone-200 {
--tw-border-opacity: 1;
border-color: rgb(231 229 228 / var(--tw-border-opacity));
}
.sm\:flex-none {
flex: none;
}
.dark .dark\:border-stone-600 {
--tw-border-opacity: 1;
border-color: rgb(87 83 78 / var(--tw-border-opacity));
}
.dark .dark\:bg-stone-600 {
--tw-bg-opacity: 1;
background-color: rgb(87 83 78 / var(--tw-bg-opacity));
}
.dark .dark\:bg-stone-700 {
--tw-bg-opacity: 1;
background-color: rgb(68 64 60 / var(--tw-bg-opacity));
}
.dark .dark\:bg-stone-900 {
--tw-bg-opacity: 1;
background-color: rgb(28 25 23 / var(--tw-bg-opacity));
}
.dark .dark\:bg-stone-200 {
--tw-bg-opacity: 1;
background-color: rgb(231 229 228 / var(--tw-bg-opacity));
}
.dark .dark\:text-gray-400 {
--tw-text-opacity: 1;
color: rgb(156 163 175 / var(--tw-text-opacity));
}
.dark .dark\:text-stone-50 {
--tw-text-opacity: 1;
color: rgb(250 250 249 / var(--tw-text-opacity));
}
.dark .dark\:text-stone-200 {
--tw-text-opacity: 1;
color: rgb(231 229 228 / var(--tw-text-opacity));
}
.dark .dark\:text-stone-900 {
--tw-text-opacity: 1;
color: rgb(28 25 23 / var(--tw-text-opacity));
}
.dark .hover\:dark\:border-stone-600:hover {
--tw-border-opacity: 1;
border-color: rgb(87 83 78 / var(--tw-border-opacity));
}
.dark .hover\:dark\:border-teal-500:hover {
--tw-border-opacity: 1;
border-color: rgb(20 184 166 / var(--tw-border-opacity));
}
.dark .hover\:dark\:border-stone-200:hover {
--tw-border-opacity: 1;
border-color: rgb(231 229 228 / var(--tw-border-opacity));
}
.dark .hover\:dark\:bg-stone-900:hover {
--tw-bg-opacity: 1;
background-color: rgb(28 25 23 / var(--tw-bg-opacity));
}
.dark .hover\:dark\:bg-stone-200:hover {
--tw-bg-opacity: 1;
background-color: rgb(231 229 228 / var(--tw-bg-opacity));
}
.dark .hover\:dark\:text-stone-900:hover {
--tw-text-opacity: 1;
color: rgb(28 25 23 / var(--tw-text-opacity));
}
.dark .focus\:dark\:border-stone-600:focus {
--tw-border-opacity: 1;
border-color: rgb(87 83 78 / var(--tw-border-opacity));
}
.dark .focus\:dark\:text-stone-900:focus {
--tw-text-opacity: 1;
color: rgb(28 25 23 / var(--tw-text-opacity));
}
.dark .active\:dark\:border-stone-600:active {
--tw-border-opacity: 1;
border-color: rgb(87 83 78 / var(--tw-border-opacity));
}
.dark .active\:dark\:text-stone-900:active {
--tw-text-opacity: 1;
color: rgb(28 25 23 / var(--tw-text-opacity));
}
@media (min-width: 768px) {
@ -2141,10 +2225,6 @@ video {
margin-right: 0.5rem;
}
.lg\:mt-4 {
margin-top: 1rem;
}
.lg\:mt-0 {
margin-top: 0px;
}
@ -2165,24 +2245,20 @@ video {
display: none;
}
.lg\:h-12 {
height: 3rem;
}
.lg\:w-1\/2 {
width: 50%;
}
.lg\:w-1\/4 {
width: 25%;
.lg\:w-half {
width: 48%;
}
.lg\:w-3\/4 {
width: 75%;
}
.lg\:w-half {
width: 48%;
.lg\:w-1\/4 {
width: 25%;
}
.lg\:flex-row {

View File

@ -2,7 +2,7 @@ app.component('component-text', {
props: ['id', 'description', 'maxlength', 'hidden', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'css', 'errors'],
template: `<div :class="css ? css : ''" class="w-full mt-5 mb-5">
<label :for="name" class="block mb-1 font-medium">{{ $filters.translate(label) }}</label>
<input type="text" class="h-12 w-full border px-2 py-3" :class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
<input type="text" class="text-stone-900 h-12 w-full border px-2 py-3" :class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
:id="id"
:maxlength="maxlength"
:readonly="readonly"
@ -28,7 +28,7 @@ app.component('component-textarea', {
props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'],
template: `<div :class="css ? css : ''" class="w-full mt-5 mb-5">
<label :for="name" class="block mb-1 font-medium">{{ $filters.translate(label) }}</label>
<textarea rows="8" class="w-full border border-stone-300 bg-stone-200 px-2 py-3"
<textarea rows="8" class="w-full border border-stone-300 text-stone-900 bg-stone-200 px-2 py-3"
:id="id"
:class="css"
:readonly="readonly"
@ -128,7 +128,7 @@ app.component('component-select', {
props: ['id', 'description', 'readonly', 'required', 'disabled', 'label', 'name', 'type', 'css', 'options', 'value', 'errors', 'dataset', 'userroles'],
template: `<div :class="css ? css : ''" class="w-full mt-5 mb-5">
<label :for="name" class="block mb-1 font-medium">{{ $filters.translate(label) }}</label>
<select class="form-select block w-full border border-stone-300 bg-stone-200 px-2 py-3 h-12 transition ease-in-out"
<select class="form-select block w-full border border-stone-300 text-stone-900 bg-stone-200 px-2 py-3 h-12 transition ease-in-out"
:id="id"
:name="name"
:required="required"
@ -258,7 +258,7 @@ app.component('component-number', {
props: ['id', 'description', 'min', 'max', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'],
template: `<div :class="css ? css : ''" class="w-full mt-5 mb-5">
<label :for="name" class="block mb-1 font-medium">{{ $filters.translate(label) }}</label>
<input type="number" class="h-12 w-full border border-stone-300 bg-stone-200 px-2 py-3"
<input type="number" class="h-12 w-full border border-stone-300 text-stone-900 bg-stone-200 px-2 py-3"
:id="id"
:min="min"
:max="max"
@ -291,7 +291,7 @@ app.component('component-date', {
<path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd"></path>
</svg>
</div>
<input type="date" class="h-12 w-full border pl-10 pr-2 py-3" :class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
<input type="date" class="h-12 w-full border pl-10 pr-2 py-3 text-stone-900" :class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
:id="id"
:readonly="readonly"
:required="required"
@ -323,7 +323,7 @@ app.component('component-email', {
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"></path>
</svg>
</div>
<input type="email" class="h-12 w-full border pl-10 pr-2 py-3" :class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
<input type="email" class="h-12 w-full border pl-10 pr-2 py-3 text-stone-900" :class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
:id="id"
:maxlength="maxlength"
:readonly="readonly"
@ -355,7 +355,7 @@ app.component('component-tel', {
<path d="M22 20c-2 2-2 4-4 4s-4-2-6-4-4-4-4-6 2-2 4-4-4-8-6-8-6 6-6 6c0 4 4.109 12.109 8 16s12 8 16 8c0 0 6-4 6-6s-6-8-8-6z"></path>
</svg>
</div>
<input type="tel" class="h-12 w-full border pl-10 pr-2 py-3" :class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
<input type="tel" class="h-12 w-full border pl-10 pr-2 py-3 text-stone-900" :class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
:id="id"
:maxlength="maxlength"
:readonly="readonly"
@ -388,7 +388,7 @@ app.component('component-url', {
<path 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>
</div>
<input type="url" class="h-12 w-full border pl-10 pr-2 py-3" :class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
<input type="url" class="h-12 w-full border pl-10 pr-2 py-3 text-stone-900" :class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
:id="id"
:maxlength="maxlength"
:readonly="readonly"
@ -457,7 +457,7 @@ app.component('component-password', {
<path d="M18.5 14h-0.5v-6c0-3.308-2.692-6-6-6h-4c-3.308 0-6 2.692-6 6v6h-0.5c-0.825 0-1.5 0.675-1.5 1.5v15c0 0.825 0.675 1.5 1.5 1.5h17c0.825 0 1.5-0.675 1.5-1.5v-15c0-0.825-0.675-1.5-1.5-1.5zM6 8c0-1.103 0.897-2 2-2h4c1.103 0 2 0.897 2 2v6h-8v-6z"></path>
</svg>
</div>
<input :type="fieldType" class="h-12 w-full border pl-10 pr-10 py-1" :class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
<input :type="fieldType" class="h-12 w-full border pl-10 pr-10 py-1 text-stone-900" :class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
:id="id"
:maxlength="maxlength"
:readonly="readonly"
@ -582,7 +582,7 @@ app.component('component-customfields', {
<input
type="text"
placeholder="key"
class="h-12 w-full border px-2 py-3 border-stone-300 bg-stone-200"
class="h-12 w-full border px-2 py-3 border-stone-300 bg-stone-200 text-stone-900"
:class="pairobject.keyerror"
:value="pairobject.key"
@input="updatePairKey(pairindex,$event)">
@ -594,7 +594,7 @@ app.component('component-customfields', {
</svg>
<textarea
placeholder="value"
class="w-full border px-2 py-3 border-stone-300 bg-stone-200"
class="w-full border px-2 py-3 border-stone-300 bg-stone-200 text-stone-900"
:class="pairobject.valueerror"
v-html="pairobject.value"
@input="updatePairValue(pairindex,$event)"></textarea>
@ -751,7 +751,7 @@ app.component('component-image', {
<label class="block mb-1">{{ $filters.translate('Image URL (read only)') }}</label>
<div class="flex">
<button @click.prevent="deleteImage()" class="w-1/6 bg-stone-200 hover:bg-rose-500 hover:text-white">x</button>
<input type="text" class="h-12 w-5/6 border px-1 py-1" :class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
<input type="text" class="h-12 w-5/6 border px-1 py-1 text-stone-900" :class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
:id="id"
:maxlength="maxlength"
readonly="readonly"

View File

@ -34,12 +34,12 @@ const app = Vue.createApp({
<div class="w-half border-2 border-stone-200 p-4 my-8 text-center">
<h2 class="text-3 font-bold mb-4">Maker License</h2>
<p class="mb-4">Use all maker-prodcuts (plugins and themes) for one year. The subscription will automatically refresh after a year until you cancel it.</p>
<a href="https://typemill.net/buy">Buy on Typemill</a>
<a class="p-2 block dark:bg-stone-600 hover:dark:bg-stone-900 dark:text-stone-200" href="https://typemill.net/buy">Buy on Typemill</a>
</div>
<div class="w-half border-2 border-stone-200 p-4 my-8 text-center">
<h2 class="text-3 font-bold mb-4">Business License</h2>
<p class="mb-4">Use all business- and maker-products (plugins, themes, services) for one year. The subscription will automatically refresh after a year until you cancel it.</p>
<a href="https://typemill.net/buy">Buy on Typemill</a>
<a class="p-2 block dark:bg-stone-600 hover:dark:bg-stone-900 dark:text-stone-200" href="https://typemill.net/buy">Buy on Typemill</a>
</div>
</div>
</div>

View File

@ -2,7 +2,7 @@ const app = Vue.createApp({
template: `<Transition name="initial" appear>
<div class="w-full">
<ul>
<li v-for="(plugin,pluginname) in formDefinitions" class="w-full my-4 bg-stone-100">
<li v-for="(plugin,pluginname) in formDefinitions" class="w-full my-8 bg-stone-100 border border-stone-200">
<p v-if="versions[pluginname] !== undefined"><a href="https://plugins.typemill.net" class="block p-2 text-center bg-rose-500 text-white">Please update to version {{ versions[pluginname].version }}</a></p>
<div class="flex justify-between w-full px-8 py-3 border-b border-white" :class="getActiveClass(pluginname)">
<p class="py-2">License: {{ plugin.license }}</p>
@ -21,7 +21,7 @@ const app = Vue.createApp({
<p>{{plugin.description}}</p>
</div>
<div class="w-full mt-6 flex justify-between">
<button @click="setCurrent(pluginname)" class="flex-1 flex items-center justify-center space-x-4 p-3 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">
<button v-if="hasSettings(pluginname)" @click="setCurrent(pluginname)" class="flex-1 flex items-center justify-center space-x-4 p-3 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">
<span>{{ $filters.translate('Configure') }}</span>
<span :class="(current == pluginname) ? 'border-b-8 border-b-white' : 'border-t-8 border-t-white'" class="h-0 w-0 border-x-8 border-x-transparent"></span>
</button>
@ -133,18 +133,18 @@ const app = Vue.createApp({
},
methods: {
getActiveClass: function(pluginname)
getActiveClass(pluginname)
{
if(this.formData[pluginname]['active'])
{
return 'bg-stone-200';
}
},
getLinkToLicense: function()
getLinkToLicense()
{
return tmaxios.defaults.baseURL + "/tm/license";
},
checkLicense: function(haystack, needle)
checkLicense(haystack, needle)
{
if(needle == 'MAKER' || needle == 'BUSINESS')
{
@ -155,7 +155,7 @@ const app = Vue.createApp({
}
return true;
},
activate: function(pluginname)
activate(pluginname)
{
var self = this;
@ -178,7 +178,15 @@ const app = Vue.createApp({
}
});
},
setCurrent: function(name)
hasSettings(pluginname)
{
if(this.formDefinitions[pluginname].forms !== undefined)
{
return true;
}
return false;
},
setCurrent(name)
{
if(this.current == name)
{
@ -189,11 +197,11 @@ const app = Vue.createApp({
this.current = name;
}
},
selectComponent: function(type)
selectComponent(type)
{
return 'component-'+type;
},
save: function()
save()
{
this.reset();
var self = this;
@ -220,7 +228,7 @@ const app = Vue.createApp({
}
});
},
reset: function()
reset()
{
this.errors = {};
this.message = '';

View File

@ -4,7 +4,7 @@ const app = Vue.createApp({
<p v-if="version.system !== undefined"><a href="https://typemill.net" class="block p-2 text-center bg-rose-500 text-white">Please update typemill to version {{ version.system }}</a></p>
<ul class="flex flex-wrap mt-4 mb-4">
<li v-for="tab in tabs">
<button class="px-2 py-2 border-b-2 border-stone-200 hover:border-stone-700 transition duration-100" :class="(tab == currentTab) ? 'border-stone-700' : ''" @click.prevent="activateTab(tab)">{{ $filters.translate(tab) }}</button>
<button class="px-2 py-2 border-b-2 border-stone-200 dark:border-stone-900 hover:border-stone-700 hover:dark:border-stone-200 transition duration-100" :class="(tab == currentTab) ? 'border-stone-700 dark:border-stone-200' : ''" @click.prevent="activateTab(tab)">{{ $filters.translate(tab) }}</button>
</li>
</ul>
<div v-for="(fieldDefinition, fieldname) in formDefinitions">
@ -22,7 +22,7 @@ const app = Vue.createApp({
</div>
<div class="my-5">
<div :class="messageClass" class="block w-full h-8 px-3 py-1 my-1 text-white transition duration-100">{{ $filters.translate(message) }}</div>
<input type="submit" @click.prevent="save()" :value="$filters.translate('save')" class="w-full p-3 my-1 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">
<input type="submit" @click.prevent="save()" :value="$filters.translate('save')" class="w-full p-3 my-1 dark:bg-stone-600 hover:dark:bg-stone-900 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">
</div>
</form>
</Transition>`,

View File

@ -1,13 +1,13 @@
const systemnavi = Vue.createApp({
template: `
<ul class="lg:mr-2 border-l-2 border-stone-200">
<ul class="lg:mr-2">
<button @click="toggle" class="lg:hidden w-full flex-1 flex items-center justify-center space-x-4 p-2 mb-2 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">
<span>{{ $filters.translate('Menu') }}</span>
<span :class="expanded ? 'border-b-8 border-b-white' : 'border-t-8 border-t-white'" class="h-0 w-0 border-x-8 border-x-transparent"></span>
</button>
<div class="lg:block" :class="expanded ? '' : 'hidden'">
<li v-for="(navitem, name) in systemnavi" :key="name" class="mb-1">
<a :href="navitem.url" class="block p-2 border-l-4 hover:bg-stone-50 hover:border-teal-500 transition duration-100" :class="navitem.active ? ' active bg-stone-50 border-cyan-500' : ' border-slate-200'">
<a :href="navitem.url" class="block p-2 border-l-4 hover:bg-stone-50 hover:border-teal-500 dark:border-stone-200 dark:bg-stone-700 hover:dark:bg-stone-200 dark:text-stone-50 hover:dark:text-stone-900 transition duration-100" :class="navitem.active ? ' active bg-stone-50 border-cyan-500 dark:bg-stone-200 dark:text-stone-900' : ' border-slate-200'">
<svg class="icon {{ navitem.icon }} mr-2"><use xlink:href="#{{ navitem.icon }}"></use></svg> {{ $filters.translate(navitem.title) }}
</a>
</li>

View File

@ -2,7 +2,7 @@ const app = Vue.createApp({
template: `<Transition name="initial" appear>
<div class="w-full">
<ul>
<li v-for="(theme,themename) in formDefinitions" class="w-full my-4 bg-stone-100">
<li v-for="(theme,themename) in formDefinitions" class="w-full my-8 bg-stone-100 dark:bg-stone-600 border border-stone-200">
<p v-if="versions[themename] !== undefined"><a href="https://themes.typemill.net" class="block p-2 text-center bg-rose-500 text-white">Please update to version {{ versions[themename].version }}</a></p>
<div class="flex justify-between w-full px-8 py-3 border-b border-white" :class="getActiveClass(themename)">
<p class="py-2">License: {{ theme.license }}</p>
@ -22,7 +22,7 @@ const app = Vue.createApp({
<p>{{theme.description}}</p>
</div>
<div class="lg:w-1/2 w-full h-48 overflow-hidden">
<img :src="theme.preview" class="w-full">
<img :src="getSrc(theme.preview)" class="w-full">
</div>
</div>
<div class="w-full mt-6 flex justify-between">
@ -140,24 +140,28 @@ const app = Vue.createApp({
});
},
methods: {
deactivateThemes: function()
deactivateThemes()
{
for (const theme in this.formData) {
delete this.formData[theme].active;
}
},
getActiveClass: function(themename)
getActiveClass(themename)
{
if(this.formData[themename]['active'])
{
return 'bg-stone-200';
return 'bg-stone-200 dark:bg-stone-900';
}
},
getLinkToLicense: function()
getSrc(preview)
{
return data.urlinfo.baseurl + preview;
},
getLinkToLicense()
{
return tmaxios.defaults.baseURL + "/tm/license";
},
checkLicense: function(haystack, needle)
checkLicense(haystack, needle)
{
if(needle == 'MAKER' || needle == 'BUSINESS')
{
@ -168,7 +172,7 @@ const app = Vue.createApp({
}
return true;
},
activate: function(themename)
activate(themename)
{
var self = this;
@ -193,7 +197,7 @@ const app = Vue.createApp({
}
});
},
setCurrent: function(name)
setCurrent(name)
{
if(this.current == name)
{
@ -204,13 +208,14 @@ const app = Vue.createApp({
this.current = name;
}
},
selectComponent: function(type)
selectComponent(type)
{
return 'component-'+type;
},
save: function()
save()
{
this.reset();
var self = this;
tmaxios.post('/api/v1/theme',{
@ -235,14 +240,35 @@ const app = Vue.createApp({
self.errors = error.response.data.errors;
}
}
});
});
},
updateCSS: function()
updateCSS()
{
/* check if css has been modified */
/* if so, send to api endpoint */
var selfcss = this;
tmaxios.post('/api/v1/themecss',{
'theme': this.current,
'css': this.formData[this.current].customcss
})
.then(function (response)
{
self.messageClass = 'bg-teal-500';
self.message = response.data.message;
})
.catch(function (error)
{
if(error.response)
{
self.message = handleErrorMessage(error);
self.messageClass = 'bg-rose-500';
if(error.response.data.errors !== undefined)
{
self.errors = error.response.data.errors;
}
}
});
},
reset: function()
reset()
{
this.errors = {};
this.message = '';

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="{{ settings.darkmode ? 'dark' :'darkmodeoff' }}" >
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="{{ settings.darkmode ? 'dark' :'darkmodeoff' }}" >
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="{{ settings.darkmode ? 'dark' :'darkmodeoff' }}" >
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
@ -24,11 +24,11 @@
{{ assets.renderCSS() }}
</head>
<body class="bg-stone-100">
<body class="bg-stone-100 dark:bg-stone-900">
{% include 'partials/symbols.twig' %}
<header class="border-b-2 border-stone-200">
<header class="border-b-2 border-stone-200 dark:border-stone-600">
{% include 'partials/mainNavi.twig' %}
</header>
@ -38,7 +38,7 @@
<aside class="lg:w-1/4">
{% include 'partials/systemNavi.twig' %}
</aside>
<article class="lg:w-3/4 bg-stone-50 shadow-md p-8">
<article class="lg:w-3/4 bg-stone-50 dark:bg-stone-700 dark:text-stone-200 shadow-md p-8">
{% block content %}{% endblock %}
</article>
</div>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="{{ settings.darkmode ? 'dark' :'darkmodeoff' }}" >
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>

View File

@ -1,13 +1,19 @@
<nav class="max-w-6xl m-auto flex justify-between">
<div class="lg:p-3 p-2 flex">
<a class="text-2xl font-bold" href="{{ base_url() }}/tm/content/{{ settings.editor }}">T<span class="lg:inline hidden">ypemill</span></a>
<a class="text-2xl font-bold dark:text-stone-200" href="{{ base_url() }}/tm/content/{{ settings.editor }}">T<span class="lg:inline hidden">ypemill</span></a>
<div id="kixote"></div>
</div>
<ul class="flex border-l-2 border-stone-200">
<ul class="flex border-l-2 border-stone-200 dark:border-stone-600">
{% for name,navitem in mainnavi %}
<li class="border-r-2 border-stone-200">
<a class="inline-block lg:px-4 lg:pt-4 lg:pb-3 lg:border-b-4 px-3 pt-3 pb-3 border-b-2 hover:border-stone-700 hover:bg-stone-50 focus:bg-stone-50 active:bg-stone-50 transition duration-100{{ navitem.active ? ' bg-stone-50 border-stone-700' : ' border-stone-100' }}" href="{{ url_for(navitem.routename) }}">{{ translate(navitem.title)|capitalize }}</a>
</li>
{% if settings.darkmode %}
<li class="border-r-2 border-stone-200 dark:border-stone-600">
<a class="inline-block lg:px-4 lg:pt-4 lg:pb-3 lg:border-b-4 px-3 pt-3 pb-3 border-b-2 dark:text-stone-200 hover:bg-stone-50 hover:dark:bg-stone-200 hover:dark:text-stone-900 focus:dark:text-stone-900 active:dark:text-stone-900 hover:dark:border-stone-600 focus:dark:border-stone-600 active:dark:border-stone-600 transition duration-100{{ navitem.active ? ' dark:bg-stone-200 dark:text-stone-900 dark:border-stone-600' : ' dark:border-stone-900' }}" href="{{ url_for(navitem.routename) }}">{{ translate(navitem.title)|capitalize }}</a>
</li>
{% else %}
<li class="border-r-2 border-stone-200 dark:border-stone-600">
<a class="inline-block lg:px-4 lg:pt-4 lg:pb-3 lg:border-b-4 px-3 pt-3 pb-3 border-b-2 hover:border-stone-700 hover:bg-stone-50 focus:bg-stone-50 active:bg-stone-50 transition duration-100{{ navitem.active ? ' bg-stone-50 border-stone-700' : ' border-stone-100' }}" href="{{ url_for(navitem.routename) }}">{{ translate(navitem.title)|capitalize }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</nav>

View File

@ -29,6 +29,7 @@ $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) {
$group->get('/settings', ControllerApiSystemSettings::class . ':getSettings')->setName('api.settings.get')->add(new ApiAuthorization($acl, 'system', 'view')); # admin
$group->post('/settings', ControllerApiSystemSettings::class . ':updateSettings')->setName('api.settings.set')->add(new ApiAuthorization($acl, 'system', 'update')); # admin
$group->post('/license', ControllerApiSystemLicense::class . ':createLicense')->setName('api.license.create')->add(new ApiAuthorization($acl, 'system', 'update')); # admin
$group->post('/themecss', ControllerApiSystemThemes::class . ':updateThemeCss')->setName('api.themecss.set')->add(new ApiAuthorization($acl, 'system', 'update')); # admin
$group->post('/theme', ControllerApiSystemThemes::class . ':updateTheme')->setName('api.theme.set')->add(new ApiAuthorization($acl, 'system', 'update')); # admin
$group->post('/plugin', ControllerApiSystemPlugins::class . ':updatePlugin')->setName('api.plugin.set')->add(new ApiAuthorization($acl, 'system', 'update')); # admin
$group->post('/extensions', ControllerApiSystemExtensions::class . ':activateExtension')->setName('api.extension.activate')->add(new ApiAuthorization($acl, 'system', 'update')); # admin

View File

@ -53,6 +53,11 @@ fieldsetsystem:
label: Google sitemap (readonly)
css: lg:w-half
disabled: true
# darkmode:
# type: checkbox
# label: Darkmode
# checkboxlabel: Switch to darkmode for the authoring area
# css: lg:w-half
fieldsetmedia:
type: fieldset
legend: Media
@ -200,18 +205,10 @@ fieldsetdeveloper:
type: checkbox
label: Twig cache
checkboxlabel: Activate the cache for twig templates
refreshcache:
type: checkbox
label: Refresh cache
checkboxlabel: Refresh the cache every 10 minutes. Use this option if you change content-files via FTP.
proxy:
type: checkbox
label: Proxy
checkboxlabel: Use x-forwarded-header.
trustedproxies:
type: text
label: Trusted IPs for proxies (comma separated)
headersoff:
type: checkbox
label: Disable headers
checkboxlabel: Disable the typemill headers and send your owwn
label: Trusted IPs for proxies (comma separated)

View File

@ -43,8 +43,8 @@ $timer['start'] = microtime(true);
* HIDE ERRORS BY DEFAULT *
****************************/
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
ini_set('display_errors', 0);
ini_set('display_startup_errors', 0);
error_reporting(E_ALL);
/****************************

View File

@ -1,6 +1,7 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./system/typemill/author/**/*.{html,js,twig}"],
darkMode: 'class',
theme: {
extend: {
screens: {