diff --git a/composer.json b/composer.json
index 2837688..2672ce8 100644
--- a/composer.json
+++ b/composer.json
@@ -20,8 +20,8 @@
"vlucas/valitron": "^1.4",
"symfony/yaml": "^6.0",
"symfony/event-dispatcher": "^6.0",
- "erusev/parsedown": "^2.0",
- "erusev/parsedown-extra": "^2.0",
+ "erusev/parsedown": "^1.8.0-beta-4",
+ "erusev/parsedown-extra": "dev-master",
"jbroadway/urlify": "1.1.3",
"laminas/laminas-permissions-acl": "^2.10",
"akrabat/proxy-detection-middleware": "^0.4.0",
diff --git a/composer.lock b/composer.lock
index 90a135c..996351c 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "cec6169bbe69cc122c0c577476c42f23",
+ "content-hash": "78ca4826588a21b7b6d14444c454e6bd",
"packages": [
{
"name": "akrabat/proxy-detection-middleware",
@@ -61,33 +61,29 @@
},
{
"name": "erusev/parsedown",
- "version": "v2.0.0-beta-1",
+ "version": "1.8.0-beta-7",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
- "reference": "32278afaf36c758de026b66661d3afd6dfbea743"
+ "reference": "fe7a50eceb4a3c867cc9fa9c0aa906b1067d1955"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/erusev/parsedown/zipball/32278afaf36c758de026b66661d3afd6dfbea743",
- "reference": "32278afaf36c758de026b66661d3afd6dfbea743",
+ "url": "https://api.github.com/repos/erusev/parsedown/zipball/fe7a50eceb4a3c867cc9fa9c0aa906b1067d1955",
+ "reference": "fe7a50eceb4a3c867cc9fa9c0aa906b1067d1955",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
- "php": "^7.1||^8.0"
+ "php": ">=5.3.0"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "^3.0.0",
- "infection/infection": "^0.25.0",
- "phpunit/phpunit": "^9.3.11||^8.5.21||^7.5.20",
- "roave/infection-static-analysis-plugin": "^1.10.0",
- "vimeo/psalm": "^4.10.0"
+ "phpunit/phpunit": "^4.8.35"
},
"type": "library",
"autoload": {
- "psr-4": {
- "Erusev\\Parsedown\\": "src/"
+ "psr-0": {
+ "Parsedown": ""
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -109,40 +105,38 @@
],
"support": {
"issues": "https://github.com/erusev/parsedown/issues",
- "source": "https://github.com/erusev/parsedown/tree/v2.0.0-beta-1"
+ "source": "https://github.com/erusev/parsedown/tree/1.8.0-beta-7"
},
- "time": "2022-05-21T21:40:24+00:00"
+ "time": "2019-03-17T18:47:21+00:00"
},
{
"name": "erusev/parsedown-extra",
- "version": "v2.0.0-beta-1",
+ "version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown-extra.git",
- "reference": "aaed6a888230a9ffb5ef35e9f403ba2b5e4e21ef"
+ "reference": "fd33d68349630d18d56367712a64445a6e0bc83e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/erusev/parsedown-extra/zipball/aaed6a888230a9ffb5ef35e9f403ba2b5e4e21ef",
- "reference": "aaed6a888230a9ffb5ef35e9f403ba2b5e4e21ef",
+ "url": "https://api.github.com/repos/erusev/parsedown-extra/zipball/fd33d68349630d18d56367712a64445a6e0bc83e",
+ "reference": "fd33d68349630d18d56367712a64445a6e0bc83e",
"shasum": ""
},
"require": {
- "erusev/parsedown": "^2.0.0|^2.0.0-beta-1",
+ "erusev/parsedown": "^1.8.0|^1.8.0-beta-4",
+ "ext-dom": "*",
"ext-mbstring": "*",
- "php": "^7.1||^8.0"
+ "php": ">=5.3.6"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "^3.0.0",
- "infection/infection": "^0.25.0",
- "phpunit/phpunit": "^9.3.11||^8.5.21||^7.5.20",
- "roave/infection-static-analysis-plugin": "^1.10.0",
- "vimeo/psalm": "^4.10.0"
+ "phpunit/phpunit": "^4.8.35"
},
+ "default-branch": true,
"type": "library",
"autoload": {
- "psr-4": {
- "Erusev\\ParsedownExtra\\": "src/"
+ "psr-0": {
+ "ParsedownExtra": ""
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -166,9 +160,9 @@
],
"support": {
"issues": "https://github.com/erusev/parsedown-extra/issues",
- "source": "https://github.com/erusev/parsedown-extra/tree/v2.0.0-beta-1"
+ "source": "https://github.com/erusev/parsedown-extra/tree/master"
},
- "time": "2022-05-21T21:46:56+00:00"
+ "time": "2021-10-25T08:46:29+00:00"
},
{
"name": "fig/http-message-util",
@@ -832,21 +826,21 @@
},
{
"name": "psr/http-factory",
- "version": "1.0.1",
+ "version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
- "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
+ "reference": "e616d01114759c4c489f93b099585439f795fe35"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
- "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
+ "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35",
+ "reference": "e616d01114759c4c489f93b099585439f795fe35",
"shasum": ""
},
"require": {
"php": ">=7.0.0",
- "psr/http-message": "^1.0"
+ "psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
@@ -866,7 +860,7 @@
"authors": [
{
"name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
+ "homepage": "https://www.php-fig.org/"
}
],
"description": "Common interfaces for PSR-7 HTTP message factories",
@@ -881,31 +875,31 @@
"response"
],
"support": {
- "source": "https://github.com/php-fig/http-factory/tree/master"
+ "source": "https://github.com/php-fig/http-factory/tree/1.0.2"
},
- "time": "2019-04-30T12:38:16+00:00"
+ "time": "2023-04-10T20:10:41+00:00"
},
{
"name": "psr/http-message",
- "version": "1.0.1",
+ "version": "1.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
- "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+ "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
- "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+ "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
"shasum": ""
},
"require": {
- "php": ">=5.3.0"
+ "php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.1.x-dev"
}
},
"autoload": {
@@ -934,27 +928,27 @@
"response"
],
"support": {
- "source": "https://github.com/php-fig/http-message/tree/master"
+ "source": "https://github.com/php-fig/http-message/tree/1.1"
},
- "time": "2016-08-06T14:39:51+00:00"
+ "time": "2023-04-04T09:50:52+00:00"
},
{
"name": "psr/http-server-handler",
- "version": "1.0.1",
+ "version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-server-handler.git",
- "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7"
+ "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7",
- "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7",
+ "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4",
+ "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4",
"shasum": ""
},
"require": {
"php": ">=7.0",
- "psr/http-message": "^1.0"
+ "psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
@@ -974,7 +968,7 @@
"authors": [
{
"name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
+ "homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP server-side request handler",
@@ -990,28 +984,27 @@
"server"
],
"support": {
- "issues": "https://github.com/php-fig/http-server-handler/issues",
- "source": "https://github.com/php-fig/http-server-handler/tree/master"
+ "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2"
},
- "time": "2018-10-30T16:46:14+00:00"
+ "time": "2023-04-10T20:06:20+00:00"
},
{
"name": "psr/http-server-middleware",
- "version": "1.0.1",
+ "version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-server-middleware.git",
- "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5"
+ "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5",
- "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5",
+ "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829",
+ "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829",
"shasum": ""
},
"require": {
"php": ">=7.0",
- "psr/http-message": "^1.0",
+ "psr/http-message": "^1.0 || ^2.0",
"psr/http-server-handler": "^1.0"
},
"type": "library",
@@ -1032,7 +1025,7 @@
"authors": [
{
"name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
+ "homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP server-side middleware",
@@ -1048,9 +1041,9 @@
],
"support": {
"issues": "https://github.com/php-fig/http-server-middleware/issues",
- "source": "https://github.com/php-fig/http-server-middleware/tree/master"
+ "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2"
},
- "time": "2018-10-30T17:12:04+00:00"
+ "time": "2023-04-11T06:14:47+00:00"
},
{
"name": "psr/log",
@@ -1258,16 +1251,16 @@
},
{
"name": "slim/psr7",
- "version": "1.6",
+ "version": "1.6.1",
"source": {
"type": "git",
"url": "https://github.com/slimphp/Slim-Psr7.git",
- "reference": "3471c22c1a0d26c51c78f6aeb06489d38cf46a4d"
+ "reference": "72d2b2bac94ab4575d369f605dbfafbe168d3163"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/slimphp/Slim-Psr7/zipball/3471c22c1a0d26c51c78f6aeb06489d38cf46a4d",
- "reference": "3471c22c1a0d26c51c78f6aeb06489d38cf46a4d",
+ "url": "https://api.github.com/repos/slimphp/Slim-Psr7/zipball/72d2b2bac94ab4575d369f605dbfafbe168d3163",
+ "reference": "72d2b2bac94ab4575d369f605dbfafbe168d3163",
"shasum": ""
},
"require": {
@@ -1286,7 +1279,7 @@
"adriansuter/php-autoload-override": "^1.3",
"ext-json": "*",
"http-interop/http-factory-tests": "^0.9.0",
- "php-http/psr7-integration-tests": "dev-master",
+ "php-http/psr7-integration-tests": "1.1",
"phpspec/prophecy": "^1.15",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/phpstan": "^1.8",
@@ -1334,9 +1327,9 @@
],
"support": {
"issues": "https://github.com/slimphp/Slim-Psr7/issues",
- "source": "https://github.com/slimphp/Slim-Psr7/tree/1.6"
+ "source": "https://github.com/slimphp/Slim-Psr7/tree/1.6.1"
},
- "time": "2022-11-05T18:50:24+00:00"
+ "time": "2023-04-17T16:02:20+00:00"
},
{
"name": "slim/slim",
@@ -2281,7 +2274,9 @@
"packages-dev": [],
"aliases": [],
"minimum-stability": "dev",
- "stability-flags": [],
+ "stability-flags": {
+ "erusev/parsedown-extra": 20
+ },
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
diff --git a/content/00-welcome/00-markdown-test.md b/content/00-welcome/00-markdown-test.md
index 093fd9a..62ad7f0 100644
--- a/content/00-welcome/00-markdown-test.md
+++ b/content/00-welcome/00-markdown-test.md
@@ -24,6 +24,10 @@ Headlines are simply done with hash chars like this:
###### Sixth Level Headline
```
+
+
+
+
### Third Level Headline
A third headline is more decent and lower prioritized than a second level headline.
@@ -36,7 +40,7 @@ A fourth level headline is more decent and lower prioritized than a third level
A fifth level headline is more decent and lower prioritized than a fourth level headline.
-##### Sixth Level Headline
+###### Sixth Level Headline
A sixth level headline is more decent and lower prioritized than a fifths level headline.
@@ -46,6 +50,11 @@ A sixth level headline is more decent and lower prioritized than a fifths level
A paragraph is a simple text-block separated with a new line above and below.
````
+
+
+
+
+
A paragraph is a simple text-block separated with a new line above and below.
## Soft Linebreak
@@ -56,6 +65,11 @@ She said: "Hello"
He said: "again"
````
+
+
+
+
+
For a soft linebreak (eg. for dialoges in literature), add two spaces at the end of a line and use a simple return.
She said: "Hello"
@@ -68,6 +82,11 @@ For italic text use one *asterix* or one _underscore_.
For bold text use two **asterix** or two __underscores__.
````
+
+
+
+
+
For italic text use one *asterix* or one _underscore_.
For bold text use two **asterix** or two __underscores__.
@@ -86,10 +105,15 @@ For an ordered list use whatever number you want and add a dot:
1. this
````
+
+
+
+
+
For an unordered list use a dash
-- like
-- this
+* like
+* this
Or use one asterix
@@ -108,12 +132,15 @@ Easily created for example with three dashes like this:
---
```
+
+
+
+
+
Easily created for example with three dashes like this:
---
-##Links
-
````
This is an ordinary [Link](http://typemill.net).
Links can also be [relative](/info).
@@ -124,6 +151,11 @@ Or you can use a shortcut like http://typemill.net.
You can even use a download-link like []()
````
+
+
+
+
+
This is an ordinary [Link](http://typemill.net).
Links can also be [relative](/info).
@@ -136,7 +168,7 @@ You can even add [ids or classes](http://typemill.net){#myid .myclass}.
Or you can use a shortcut like http://typemill.net.
-[markdown (PNG, 1.05 KB)](media/files/markdown.png){.tm-download file-png}
+[highlight (ZIP, 2.71 MB)](media/files/highlight.zip){.tm-download file-zip}
##Images
@@ -149,13 +181,17 @@ The same rules as with links, but with a !
{#myid .otherclass width=150px}
````
+
+
+
+
+
The same rules as with links, but with a !
-{.center}
-*With Caption*
+
-{.center}
-*With a caption that spans over several lines.*
+{.center loading="lazy" width="820" height="464"}
+*With a caption that spans over two lines.*
{#myid .otherclass width=150px}
@@ -166,6 +202,11 @@ You can link an image with a nested syntax like this:
[](https://typemill.net)
````
+
+
+
+
+
You can link an image with a nested syntax like this:
[{.imgClass}](https://typemill.net)
@@ -182,8 +223,12 @@ You can controll the image position with the classes .left, .right and .middle l
*With caption that spans over several lines*
````
-{.left}
-*With caption that spans over several lines*
+
+
+
+
+
+
The first image should float on the left side of this paragraph. This might not work with all themes. If you are a theme developer, please ensure that you support the image classes "left", "right" and "center". You can add these classes manually in the raw mode or you can assign them in the visual mode when you edit a picture (double click on it to open the dialog). Images in a separate line are rendered with the html5 elements `figure` and `figcapture`.
@@ -201,9 +246,14 @@ The thirds image should be placed above this paragraph and centered to the middl
```
There are always some women and men with wise words
+
> But I usually don't read them, to be honest.
```
+
+
+
+
There always some women and men with wise words
> But I usually don't read them, to be honest.
@@ -218,6 +268,11 @@ Add the footnote text at the bottom of the page like this:
[^2]: This is the end of the page.
````
+
+
+
+
+
You can write footnotes[^1] with markdown.
Scroll down to the end of the page[^2] and look for the footnotes.
@@ -231,6 +286,11 @@ Footnotes won't work with the visual editor right now, so please use the raw mod
*[W3C]: World Wide Web Consortium
````
+
+
+
+
+
You won't see the abbreviation directly, but if you write HTML or W3C somewhere, then you can see the tooltip with the explanation.
*[HTML]: Hyper Text Markup Language
@@ -242,35 +302,38 @@ You won't see the abbreviation directly, but if you write HTML or W3C somewhere,
````
Apple
: Pomaceous fruit of plants of the genus Malus in the family Rosaceae.
+
Orange
: The fruit of an evergreen tree of the genus Citrus.
````
+
+
+
+
Apple
-: Pomaceous fruit of plants of the genus Malus in
+: Pomaceous fruit of plants of the genus Malus in
the family Rosaceae.
Orange
-: The fruit of an evergreen tree of the genus Citrus.
-
-
+: The fruit of an everggreen tree of the genus Citrus.
## Notices
You can create different notices if you add a '!', '!!', '!!!', '!!!!' before a line. This will wrap the content into a div-class with the classes `notice1`, `notice2`, `notice3` and `notice4`. You can also span notices over several lines. This logic follows some other CMS like Grav, Lektor or Yellow and it is not compatible with other markdown processors or editors.
-! Notice 1
+> ! Notice 1
+> !
+> ! Please note that you can use **markdown** inside of the notice so you can *format* your text here.
+
+Notice 2
+
+Please note that you can use **markdown** inside of the notice so you can *format* your text here.
+
+! **Notice 3**
!
! Please note that you can use **markdown** inside of the notice so you can *format* your text here.
-!! Notice 2
-!!
-!! Please note that you can use **markdown** inside of the notice so you can *format* your text here.
-
-!!! Notice 3
-!!!
-!!! Please note that you can use **markdown** inside of the notice so you can *format* your text here.
-
## Tables
````
@@ -280,9 +343,14 @@ You can create different notices if you add a '!', '!!', '!!!', '!!!!' before a
| Your Name | For You |
````
-| Name | Usage |
-| --------- | ------- |
-| My Name | For Me |
+
+
+
+
+
+| Name | Usage |
+|---|---|
+| My Name | For Me |
| Your Name | For You |
## Code
@@ -291,18 +359,28 @@ You can create different notices if you add a '!', '!!', '!!!', '!!!!' before a
Let us create some `` like this
````
+
+
+
+
+
Let us create some `` and now let us check, if a codeblock works:
````
Use four apostroph like this:
\````
\````
````
+
+
+
+
+
## Math
Please activate the math-plugin to use mathematical expressions with LaTeX syntax. You can choose between MathJax or the newer KaTeX library. MathJax is included from a CDN, KaTeX is included in the plugin. So if you don't want to fetch code from a CDN, use KaTeX instead. The markdown syntax in TYPEMILL is the same for both libraries.
@@ -313,6 +391,11 @@ inline $x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)$ math
inline \(x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)\) math
````
+
+
+
+
+
inline $x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)$ math
inline \(x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)\) math
@@ -327,10 +410,16 @@ x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)
\]
````
+
+
+
+
+
$$
x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)
$$
[^1]: Thank you for scrolling.
+
[^2]: This is the end of the page.
diff --git a/content/00-welcome/04-write-content.md b/content/00-welcome/04-write-content.md
index 1cc44c7..e72f7d0 100644
--- a/content/00-welcome/04-write-content.md
+++ b/content/00-welcome/04-write-content.md
@@ -1,5 +1,5 @@
-# Write Content
-
+# Write Content
+
Typemill provides easy and intuitive authoring tools and we work hard to create a good author experience. With the interactive navigation you can create pages and structure your websites. The visual markdown editor will help you to create content in a wysiwyg mode. The publish bar gives you full control over the status of each page. Watch the following video tutorial to learn all the details.
{#6I2-uV88GkE .youtube}
@@ -39,4 +39,5 @@ Ever tried to revise a published article in WordPress? Yes, it works, but if you
* In **visual mode**: Typemill stores your changes in a new draft automatically as soon as you save any content-block.
* In **raw mode**: To store changes in a new draft, simply click on the "save draft"-button in the publish controller.
* You can work on a draft as long as you want without changing the live version. Your changes go live if you click the button "publish".
-* In visual mode, you can also use the discard-button and go back to the published version.
\ No newline at end of file
+* In visual mode, you can also use the discard-button and go back to the published version.
+
diff --git a/content/00-welcome/05-edit-test.txt b/content/00-welcome/05-edit-test.txt
new file mode 100644
index 0000000..b83d57c
--- /dev/null
+++ b/content/00-welcome/05-edit-test.txt
@@ -0,0 +1 @@
+["# Write Content","Typemill provides easy and intuitive authoring tools and we work hard to create a good author experience. With the interactive navigation you can create pages and structure your websites. The visual markdown editor will help you to create content in a wysiwyg mode. The publish bar gives you full control over the status of each page. Watch the following video tutorial to learn all the details.","* You can change the default mode in the system settings. \n* You can also switch each format button on and off in the system settings.","The publish bar of Typemill is pretty intuitiv and sticks at the bottom of the screen so that you have always full control of the status of each page. Simply play around with it and you will quickly understand how it works. In short:","* The green button \"online\" indicates, that your page is published and visible for your readers.\n* You can depublish a page by clicking the green \"online\" button. The button will turn gray with the label \"offline\" and the page is not visible for your readers anymore.\n* With the green button \"Publish\" you can either publish a page that is offline or you can publish some unpublished changes on your page.\n* The publish-button is gray and disabled, if the page is online and if there are no unpublished changes.\n* All buttons will change in real time, so you can always exactly see what is going on.\n* To provide an easy status-overview of the whole website, Typemill marks all pages in the navigation on the left side as published (green), changed (orange) and unpublished (red).","## Working with Drafts","Ever tried to revise a published article in WordPress? Yes, it works, but if you click on \"save\", then all your changes are directly live. Typemill is much more flexible here and allows you to keep your original version live while you work on a **drafted version** in the background. This is how Typemill handles it: ","* In **visual mode**: Typemill stores your changes in a new draft automatically as soon as you save any content-block.\n* In **raw mode**: To store changes in a new draft, simply click on the \"save draft\"-button in the publish controller.\n* You can work on a draft as long as you want without changing the live version. Your changes go live if you click the button \"publish\".\n* In visual mode, you can also use the discard-button and go back to the published version."]
\ No newline at end of file
diff --git a/content/00-welcome/05-edit-test.yaml b/content/00-welcome/05-edit-test.yaml
new file mode 100644
index 0000000..b6b5024
--- /dev/null
+++ b/content/00-welcome/05-edit-test.yaml
@@ -0,0 +1,2 @@
+meta:
+ navtitle: 'edit test'
diff --git a/content/00-welcome/06-new.txt b/content/00-welcome/06-new.txt
new file mode 100644
index 0000000..1c1d1b4
--- /dev/null
+++ b/content/00-welcome/06-new.txt
@@ -0,0 +1 @@
+["# ToDo","[TOC]","## Visual Editor","Das ist ein Intro. Es gibt noch viel zu tun, aber das ist am Ende des Tages nicht so schlimm denn wir machen einfach weiter. Done","* 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* SHIT!!! fiinish youtube component","## Navigation","* DONE: fix status in navigation\n* fix error messages\n* DONE: refresh navigation after changes","## Publish Controller","* DONE: Create \n* DONE: publish\n* DONE: unpublish\n* DONE: discard\n* DONE: delete\n* save draft\n* switch to raw","## Raw Editor","* Setup","## Meta Tabs","* Setup","## Medialib","* Setup"]
\ No newline at end of file
diff --git a/content/00-welcome/06-new.yaml b/content/00-welcome/06-new.yaml
new file mode 100644
index 0000000..194e33a
--- /dev/null
+++ b/content/00-welcome/06-new.yaml
@@ -0,0 +1,2 @@
+meta:
+ navtitle: new
diff --git a/content/00-welcome/index.md b/content/00-welcome/index.md
index dcca197..eca2d6f 100644
--- a/content/00-welcome/index.md
+++ b/content/00-welcome/index.md
@@ -1,4 +1,4 @@
# Welcome
-Great that you give Typemill a try!! Typemill is a simple Flat File Content Management System (CMS). We (the community) work hard to provide the best author experience with easy and intuitive authoring tools. But Typemill is still in early development and it is likely that not everything will work perfectly out of the box. If you miss something or if you have ideas for improvements, then post a new issue on [GitHub](https://github.com/typemill/typemill/issues).
+Great that you give Typemill a try!! Typemill is a simple Flat File Content Management System (CMS). We (the community) work hard to provide the best author experience with easy and intuitive authoring tools. But Typemill is still in early development and it is likely that not everything will work perfectly out of the box. If you miss something or if you have ideas for improvements, then post a new issue on [GitHub](https://github.com/typemill/typemill/issues).mb
diff --git a/content/01-cyanine-theme/01-landingpage.md b/content/01-cyanine-theme/00-landingpage.md
similarity index 100%
rename from content/01-cyanine-theme/01-landingpage.md
rename to content/01-cyanine-theme/00-landingpage.md
diff --git a/content/01-cyanine-theme/02-footer.md b/content/01-cyanine-theme/01-footer.md
similarity index 100%
rename from content/01-cyanine-theme/02-footer.md
rename to content/01-cyanine-theme/01-footer.md
diff --git a/content/01-cyanine-theme/01-footer.yaml b/content/01-cyanine-theme/01-footer.yaml
new file mode 100644
index 0000000..ceae2f8
--- /dev/null
+++ b/content/01-cyanine-theme/01-footer.yaml
@@ -0,0 +1,15 @@
+meta:
+ title: '3-Column Footer'
+ description: 'Cyanine provides a three column footer at the bottom of each page. You can use markdown for each column. Make sure that you use the correct headline-level (we suggest a headline level 3 or level 4 to keep the logical headline hierarchy in the document). You can, of course, also add link-lists or'
+ heroimage: ''
+ heroimagealt: null
+ owner: null
+ author: trendschau
+ manualdate: '2021-07-01'
+ modified: '2021-05-18'
+ created: '2020-06-11'
+ time: 11-48-00
+ navtitle: ''
+ hide: false
+ allowedrole: null
+ alloweduser: null
diff --git a/content/01-cyanine-theme/01-landingpage.yaml b/content/01-cyanine-theme/01-landingpage.yaml
deleted file mode 100644
index 201a9c8..0000000
--- a/content/01-cyanine-theme/01-landingpage.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-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
diff --git a/content/01-cyanine-theme/03-content-elements.md b/content/01-cyanine-theme/02-content-elements.md
similarity index 100%
rename from content/01-cyanine-theme/03-content-elements.md
rename to content/01-cyanine-theme/02-content-elements.md
diff --git a/content/01-cyanine-theme/02-content-elements.yaml b/content/01-cyanine-theme/02-content-elements.yaml
new file mode 100644
index 0000000..68e0c1a
--- /dev/null
+++ b/content/01-cyanine-theme/02-content-elements.yaml
@@ -0,0 +1,16 @@
+meta:
+ navtitle: 'content elements'
+ title: 'Content Elements'
+ description: "Cyanine provides a lot of other settings for your content area. For example: \nAdd an edit-button for github, gitlab or other plattforms.\nShow the author.\nShow the publish date.\nShow the chapter numbers in the navigation.\n"
+ heroimage: ''
+ heroimagealt: null
+ hide: false
+ noindex: false
+ owner: trendschau
+ author: 'Sebastian Schürmanns'
+ allowedrole: null
+ alloweduser: null
+ manualdate: null
+ modified: '2021-11-24'
+ created: '2021-06-17'
+ time: 16-23-24
diff --git a/content/01-cyanine-theme/00-colors-and-fonts.md b/content/01-cyanine-theme/03-colors-and-fonts.md
similarity index 100%
rename from content/01-cyanine-theme/00-colors-and-fonts.md
rename to content/01-cyanine-theme/03-colors-and-fonts.md
diff --git a/content/01-cyanine-theme/00-colors-and-fonts.yaml b/content/01-cyanine-theme/03-colors-and-fonts.yaml
similarity index 100%
rename from content/01-cyanine-theme/00-colors-and-fonts.yaml
rename to content/01-cyanine-theme/03-colors-and-fonts.yaml
diff --git a/data/navigation/navi-draft.txt b/data/navigation/navi-draft.txt
index f46481d..b154045 100644
--- a/data/navigation/navi-draft.txt
+++ b/data/navigation/navi-draft.txt
@@ -1 +1 @@
-a:2:{i:0;O:8:"stdClass":22:{s:12:"originalName";s:10:"00-welcome";s:11:"elementType";s:6:"folder";s:8:"contains";s:5:"pages";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"00";s:4:"name";s:7:"welcome";s:4:"slug";s:7:"welcome";s:4:"path";s:11:"/00-welcome";s:15:"pathWithoutType";s:17:"/00-welcome/index";s:9:"urlRelWoF";s:8:"/welcome";s:6:"urlRel";s:17:"/typemill/welcome";s:6:"urlAbs";s:33:"http://localhost/typemill/welcome";s:3:"key";i:0;s:7:"keyPath";i:0;s:12:"keyPathArray";a:1:{i:0;s:1:"0";}s:7:"chapter";i:1;s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:13:"folderContent";a:5:{i:0;O:8:"stdClass":20:{s:12:"originalName";s:19:"00-markdown-test.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"00";s:4:"name";s:13:"markdown test";s:4:"slug";s:13:"markdown-test";s:4:"path";s:31:"/00-welcome/00-markdown-test.md";s:15:"pathWithoutType";s:28:"/00-welcome/00-markdown-test";s:3:"key";i:0;s:7:"keyPath";s:3:"0.0";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"0";}s:7:"chapter";s:3:"1.1";s:9:"urlRelWoF";s:22:"/welcome/markdown-test";s:6:"urlRel";s:31:"/typemill/welcome/markdown-test";s:6:"urlAbs";s:47:"http://localhost/typemill/welcome/markdown-test";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:1;O:8:"stdClass":20:{s:12:"originalName";s:19:"01-manage-access.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"01";s:4:"name";s:13:"manage access";s:4:"slug";s:13:"manage-access";s:4:"path";s:31:"/00-welcome/01-manage-access.md";s:15:"pathWithoutType";s:28:"/00-welcome/01-manage-access";s:3:"key";i:1;s:7:"keyPath";s:3:"0.1";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"1";}s:7:"chapter";s:3:"1.2";s:9:"urlRelWoF";s:22:"/welcome/manage-access";s:6:"urlRel";s:31:"/typemill/welcome/manage-access";s:6:"urlAbs";s:47:"http://localhost/typemill/welcome/manage-access";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:2;O:8:"stdClass":20:{s:12:"originalName";s:14:"02-get-help.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"02";s:4:"name";s:8:"get help";s:4:"slug";s:8:"get-help";s:4:"path";s:26:"/00-welcome/02-get-help.md";s:15:"pathWithoutType";s:23:"/00-welcome/02-get-help";s:3:"key";i:2;s:7:"keyPath";s:3:"0.2";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"2";}s:7:"chapter";s:3:"1.3";s:9:"urlRelWoF";s:17:"/welcome/get-help";s:6:"urlRel";s:26:"/typemill/welcome/get-help";s:6:"urlAbs";s:42:"http://localhost/typemill/welcome/get-help";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:3;O:8:"stdClass":20:{s:12:"originalName";s:24:"03-setup-your-website.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"03";s:4:"name";s:18:"setup your website";s:4:"slug";s:18:"setup-your-website";s:4:"path";s:36:"/00-welcome/03-setup-your-website.md";s:15:"pathWithoutType";s:33:"/00-welcome/03-setup-your-website";s:3:"key";i:3;s:7:"keyPath";s:3:"0.3";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"3";}s:7:"chapter";s:3:"1.4";s:9:"urlRelWoF";s:27:"/welcome/setup-your-website";s:6:"urlRel";s:36:"/typemill/welcome/setup-your-website";s:6:"urlAbs";s:52:"http://localhost/typemill/welcome/setup-your-website";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:4;O:8:"stdClass":20:{s:12:"originalName";s:19:"04-write-content.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"04";s:4:"name";s:13:"write content";s:4:"slug";s:13:"write-content";s:4:"path";s:31:"/00-welcome/04-write-content.md";s:15:"pathWithoutType";s:28:"/00-welcome/04-write-content";s:3:"key";i:4;s:7:"keyPath";s:3:"0.4";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"4";}s:7:"chapter";s:3:"1.5";s:9:"urlRelWoF";s:22:"/welcome/write-content";s:6:"urlRel";s:31:"/typemill/welcome/write-content";s:6:"urlAbs";s:47:"http://localhost/typemill/welcome/write-content";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}}s:7:"noindex";b:0;}i:1;O:8:"stdClass":22:{s:12:"originalName";s:16:"01-cyanine-theme";s:11:"elementType";s:6:"folder";s:8:"contains";s:5:"pages";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"01";s:4:"name";s:13:"cyanine theme";s:4:"slug";s:13:"cyanine-theme";s:4:"path";s:17:"/01-cyanine-theme";s:15:"pathWithoutType";s:23:"/01-cyanine-theme/index";s:9:"urlRelWoF";s:14:"/cyanine-theme";s:6:"urlRel";s:23:"/typemill/cyanine-theme";s:6:"urlAbs";s:39:"http://localhost/typemill/cyanine-theme";s:3:"key";i:1;s:7:"keyPath";i:1;s:12:"keyPathArray";a:1:{i:0;s:1:"1";}s:7:"chapter";i:2;s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:13:"folderContent";a:4:{i:0;O:8:"stdClass":20:{s:12:"originalName";s:22:"00-colors-and-fonts.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"00";s:4:"name";s:16:"colors and fonts";s:4:"slug";s:16:"colors-and-fonts";s:4:"path";s:40:"/01-cyanine-theme/00-colors-and-fonts.md";s:15:"pathWithoutType";s:37:"/01-cyanine-theme/00-colors-and-fonts";s:3:"key";i:0;s:7:"keyPath";s:3:"1.0";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"0";}s:7:"chapter";s:3:"2.1";s:9:"urlRelWoF";s:31:"/cyanine-theme/colors-and-fonts";s:6:"urlRel";s:40:"/typemill/cyanine-theme/colors-and-fonts";s:6:"urlAbs";s:56:"http://localhost/typemill/cyanine-theme/colors-and-fonts";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:1;O:8:"stdClass":20:{s:12:"originalName";s:17:"01-landingpage.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"01";s:4:"name";s:11:"landingpage";s:4:"slug";s:11:"landingpage";s:4:"path";s:35:"/01-cyanine-theme/01-landingpage.md";s:15:"pathWithoutType";s:32:"/01-cyanine-theme/01-landingpage";s:3:"key";i:1;s:7:"keyPath";s:3:"1.1";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"1";}s:7:"chapter";s:3:"2.2";s:9:"urlRelWoF";s:26:"/cyanine-theme/landingpage";s:6:"urlRel";s:35:"/typemill/cyanine-theme/landingpage";s:6:"urlAbs";s:51:"http://localhost/typemill/cyanine-theme/landingpage";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:2;O:8:"stdClass":20:{s:12:"originalName";s:12:"02-footer.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"02";s:4:"name";s:6:"footer";s:4:"slug";s:6:"footer";s:4:"path";s:30:"/01-cyanine-theme/02-footer.md";s:15:"pathWithoutType";s:27:"/01-cyanine-theme/02-footer";s:3:"key";i:2;s:7:"keyPath";s:3:"1.2";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"2";}s:7:"chapter";s:3:"2.3";s:9:"urlRelWoF";s:21:"/cyanine-theme/footer";s:6:"urlRel";s:30:"/typemill/cyanine-theme/footer";s:6:"urlAbs";s:46:"http://localhost/typemill/cyanine-theme/footer";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:3;O:8:"stdClass":20:{s:12:"originalName";s:22:"03-content-elements.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"03";s:4:"name";s:16:"content elements";s:4:"slug";s:16:"content-elements";s:4:"path";s:40:"/01-cyanine-theme/03-content-elements.md";s:15:"pathWithoutType";s:37:"/01-cyanine-theme/03-content-elements";s:3:"key";i:3;s:7:"keyPath";s:3:"1.3";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"3";}s:7:"chapter";s:3:"2.4";s:9:"urlRelWoF";s:31:"/cyanine-theme/content-elements";s:6:"urlRel";s:40:"/typemill/cyanine-theme/content-elements";s:6:"urlAbs";s:56:"http://localhost/typemill/cyanine-theme/content-elements";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}}s:7:"noindex";b:0;}}
\ No newline at end of file
+a:2:{i:0;O:8:"stdClass":22:{s:12:"originalName";s:10:"00-welcome";s:11:"elementType";s:6:"folder";s:8:"contains";s:5:"pages";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"00";s:4:"name";s:7:"welcome";s:4:"slug";s:7:"welcome";s:4:"path";s:11:"/00-welcome";s:15:"pathWithoutType";s:17:"/00-welcome/index";s:9:"urlRelWoF";s:8:"/welcome";s:6:"urlRel";s:17:"/typemill/welcome";s:6:"urlAbs";s:33:"http://localhost/typemill/welcome";s:3:"key";i:0;s:7:"keyPath";i:0;s:12:"keyPathArray";a:1:{i:0;s:1:"0";}s:7:"chapter";i:1;s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:13:"folderContent";a:7:{i:0;O:8:"stdClass":20:{s:12:"originalName";s:19:"00-markdown-test.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"00";s:4:"name";s:13:"markdown test";s:4:"slug";s:13:"markdown-test";s:4:"path";s:31:"/00-welcome/00-markdown-test.md";s:15:"pathWithoutType";s:28:"/00-welcome/00-markdown-test";s:3:"key";i:0;s:7:"keyPath";s:3:"0.0";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"0";}s:7:"chapter";s:3:"1.1";s:9:"urlRelWoF";s:22:"/welcome/markdown-test";s:6:"urlRel";s:31:"/typemill/welcome/markdown-test";s:6:"urlAbs";s:47:"http://localhost/typemill/welcome/markdown-test";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:1;O:8:"stdClass":20:{s:12:"originalName";s:19:"01-manage-access.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"01";s:4:"name";s:13:"manage access";s:4:"slug";s:13:"manage-access";s:4:"path";s:31:"/00-welcome/01-manage-access.md";s:15:"pathWithoutType";s:28:"/00-welcome/01-manage-access";s:3:"key";i:1;s:7:"keyPath";s:3:"0.1";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"1";}s:7:"chapter";s:3:"1.2";s:9:"urlRelWoF";s:22:"/welcome/manage-access";s:6:"urlRel";s:31:"/typemill/welcome/manage-access";s:6:"urlAbs";s:47:"http://localhost/typemill/welcome/manage-access";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:2;O:8:"stdClass":20:{s:12:"originalName";s:14:"02-get-help.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"02";s:4:"name";s:8:"get help";s:4:"slug";s:8:"get-help";s:4:"path";s:26:"/00-welcome/02-get-help.md";s:15:"pathWithoutType";s:23:"/00-welcome/02-get-help";s:3:"key";i:2;s:7:"keyPath";s:3:"0.2";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"2";}s:7:"chapter";s:3:"1.3";s:9:"urlRelWoF";s:17:"/welcome/get-help";s:6:"urlRel";s:26:"/typemill/welcome/get-help";s:6:"urlAbs";s:42:"http://localhost/typemill/welcome/get-help";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:3;O:8:"stdClass":20:{s:12:"originalName";s:24:"03-setup-your-website.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"03";s:4:"name";s:18:"setup your website";s:4:"slug";s:18:"setup-your-website";s:4:"path";s:36:"/00-welcome/03-setup-your-website.md";s:15:"pathWithoutType";s:33:"/00-welcome/03-setup-your-website";s:3:"key";i:3;s:7:"keyPath";s:3:"0.3";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"3";}s:7:"chapter";s:3:"1.4";s:9:"urlRelWoF";s:27:"/welcome/setup-your-website";s:6:"urlRel";s:36:"/typemill/welcome/setup-your-website";s:6:"urlAbs";s:52:"http://localhost/typemill/welcome/setup-your-website";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:4;O:8:"stdClass":20:{s:12:"originalName";s:19:"04-write-content.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"04";s:4:"name";s:13:"write content";s:4:"slug";s:13:"write-content";s:4:"path";s:31:"/00-welcome/04-write-content.md";s:15:"pathWithoutType";s:28:"/00-welcome/04-write-content";s:3:"key";i:4;s:7:"keyPath";s:3:"0.4";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"4";}s:7:"chapter";s:3:"1.5";s:9:"urlRelWoF";s:22:"/welcome/write-content";s:6:"urlRel";s:31:"/typemill/welcome/write-content";s:6:"urlAbs";s:47:"http://localhost/typemill/welcome/write-content";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:5;O:8:"stdClass":20:{s:12:"originalName";s:16:"05-edit-test.txt";s:11:"elementType";s:4:"file";s:6:"status";s:11:"unpublished";s:8:"fileType";s:3:"txt";s:5:"order";s:2:"05";s:4:"name";s:9:"edit test";s:4:"slug";s:9:"edit-test";s:4:"path";s:28:"/00-welcome/05-edit-test.txt";s:15:"pathWithoutType";s:24:"/00-welcome/05-edit-test";s:3:"key";i:5;s:7:"keyPath";s:3:"0.5";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"5";}s:7:"chapter";s:3:"1.6";s:9:"urlRelWoF";s:18:"/welcome/edit-test";s:6:"urlRel";s:27:"/typemill/welcome/edit-test";s:6:"urlAbs";s:43:"http://localhost/typemill/welcome/edit-test";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:6;O:8:"stdClass":20:{s:12:"originalName";s:10:"06-new.txt";s:11:"elementType";s:4:"file";s:6:"status";s:11:"unpublished";s:8:"fileType";s:3:"txt";s:5:"order";s:2:"06";s:4:"name";s:3:"new";s:4:"slug";s:3:"new";s:4:"path";s:22:"/00-welcome/06-new.txt";s:15:"pathWithoutType";s:18:"/00-welcome/06-new";s:3:"key";i:6;s:7:"keyPath";s:3:"0.6";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"6";}s:7:"chapter";s:3:"1.7";s:9:"urlRelWoF";s:12:"/welcome/new";s:6:"urlRel";s:21:"/typemill/welcome/new";s:6:"urlAbs";s:37:"http://localhost/typemill/welcome/new";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}}s:7:"noindex";b:0;}i:1;O:8:"stdClass":22:{s:12:"originalName";s:16:"01-cyanine-theme";s:11:"elementType";s:6:"folder";s:8:"contains";s:5:"pages";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"01";s:4:"name";s:13:"cyanine theme";s:4:"slug";s:13:"cyanine-theme";s:4:"path";s:17:"/01-cyanine-theme";s:15:"pathWithoutType";s:23:"/01-cyanine-theme/index";s:9:"urlRelWoF";s:14:"/cyanine-theme";s:6:"urlRel";s:23:"/typemill/cyanine-theme";s:6:"urlAbs";s:39:"http://localhost/typemill/cyanine-theme";s:3:"key";i:1;s:7:"keyPath";i:1;s:12:"keyPathArray";a:1:{i:0;s:1:"1";}s:7:"chapter";i:2;s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:13:"folderContent";a:4:{i:0;O:8:"stdClass":20:{s:12:"originalName";s:17:"00-landingpage.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"00";s:4:"name";s:11:"landingpage";s:4:"slug";s:11:"landingpage";s:4:"path";s:35:"/01-cyanine-theme/00-landingpage.md";s:15:"pathWithoutType";s:32:"/01-cyanine-theme/00-landingpage";s:3:"key";i:0;s:7:"keyPath";s:3:"1.0";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"0";}s:7:"chapter";s:3:"2.1";s:9:"urlRelWoF";s:26:"/cyanine-theme/landingpage";s:6:"urlRel";s:35:"/typemill/cyanine-theme/landingpage";s:6:"urlAbs";s:51:"http://localhost/typemill/cyanine-theme/landingpage";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:1;O:8:"stdClass":20:{s:12:"originalName";s:12:"01-footer.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"01";s:4:"name";s:6:"footer";s:4:"slug";s:6:"footer";s:4:"path";s:30:"/01-cyanine-theme/01-footer.md";s:15:"pathWithoutType";s:27:"/01-cyanine-theme/01-footer";s:3:"key";i:1;s:7:"keyPath";s:3:"1.1";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"1";}s:7:"chapter";s:3:"2.2";s:9:"urlRelWoF";s:21:"/cyanine-theme/footer";s:6:"urlRel";s:30:"/typemill/cyanine-theme/footer";s:6:"urlAbs";s:46:"http://localhost/typemill/cyanine-theme/footer";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:2;O:8:"stdClass":20:{s:12:"originalName";s:22:"02-content-elements.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"02";s:4:"name";s:16:"content elements";s:4:"slug";s:16:"content-elements";s:4:"path";s:40:"/01-cyanine-theme/02-content-elements.md";s:15:"pathWithoutType";s:37:"/01-cyanine-theme/02-content-elements";s:3:"key";i:2;s:7:"keyPath";s:3:"1.2";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"2";}s:7:"chapter";s:3:"2.3";s:9:"urlRelWoF";s:31:"/cyanine-theme/content-elements";s:6:"urlRel";s:40:"/typemill/cyanine-theme/content-elements";s:6:"urlAbs";s:56:"http://localhost/typemill/cyanine-theme/content-elements";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:3;O:8:"stdClass":20:{s:12:"originalName";s:22:"03-colors-and-fonts.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"03";s:4:"name";s:16:"colors and fonts";s:4:"slug";s:16:"colors-and-fonts";s:4:"path";s:40:"/01-cyanine-theme/03-colors-and-fonts.md";s:15:"pathWithoutType";s:37:"/01-cyanine-theme/03-colors-and-fonts";s:3:"key";i:3;s:7:"keyPath";s:3:"1.3";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"3";}s:7:"chapter";s:3:"2.4";s:9:"urlRelWoF";s:31:"/cyanine-theme/colors-and-fonts";s:6:"urlRel";s:40:"/typemill/cyanine-theme/colors-and-fonts";s:6:"urlAbs";s:56:"http://localhost/typemill/cyanine-theme/colors-and-fonts";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}}s:7:"noindex";b:0;}}
\ No newline at end of file
diff --git a/data/navigation/navi-extended.txt b/data/navigation/navi-extended.txt
index c378e37..01b3ec7 100644
--- a/data/navigation/navi-extended.txt
+++ b/data/navigation/navi-extended.txt
@@ -34,33 +34,45 @@
noindex: false
path: /00-welcome/04-write-content.md
keyPath: '0.4'
+/welcome/edit-test:
+ navtitle: ''
+ hide: false
+ noindex: false
+ path: /00-welcome/05-edit-test.txt
+ keyPath: '0.5'
+/welcome/new:
+ navtitle: ''
+ hide: false
+ noindex: false
+ path: /00-welcome/06-new.txt
+ keyPath: '0.6'
/cyanine-theme:
navtitle: ''
hide: false
noindex: false
path: /01-cyanine-theme
keyPath: 1
-/cyanine-theme/colors-and-fonts:
- navtitle: ''
- hide: false
- noindex: false
- 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'
+ path: /01-cyanine-theme/00-landingpage.md
+ keyPath: '1.0'
/cyanine-theme/footer:
navtitle: ''
hide: false
noindex: false
- path: /01-cyanine-theme/02-footer.md
- keyPath: '1.2'
+ path: /01-cyanine-theme/01-footer.md
+ keyPath: '1.1'
/cyanine-theme/content-elements:
navtitle: ''
hide: false
noindex: false
- path: /01-cyanine-theme/03-content-elements.md
+ path: /01-cyanine-theme/02-content-elements.md
+ keyPath: '1.2'
+/cyanine-theme/colors-and-fonts:
+ navtitle: ''
+ hide: false
+ noindex: false
+ path: /01-cyanine-theme/03-colors-and-fonts.md
keyPath: '1.3'
diff --git a/media/live/don-quijote-1.webp b/media/live/don-quijote-1.webp
new file mode 100644
index 0000000..7581eca
Binary files /dev/null and b/media/live/don-quijote-1.webp differ
diff --git a/media/live/external-content-duckduckgo-com-th-10.webp b/media/live/external-content-duckduckgo-com-th-10.webp
new file mode 100644
index 0000000..6126640
Binary files /dev/null and b/media/live/external-content-duckduckgo-com-th-10.webp differ
diff --git a/media/live/external-content-duckduckgo-com-th-11.webp b/media/live/external-content-duckduckgo-com-th-11.webp
new file mode 100644
index 0000000..6126640
Binary files /dev/null and b/media/live/external-content-duckduckgo-com-th-11.webp differ
diff --git a/media/live/external-content-duckduckgo-com-th-12.webp b/media/live/external-content-duckduckgo-com-th-12.webp
new file mode 100644
index 0000000..6126640
Binary files /dev/null and b/media/live/external-content-duckduckgo-com-th-12.webp differ
diff --git a/media/live/external-content-duckduckgo-com-th-13.webp b/media/live/external-content-duckduckgo-com-th-13.webp
new file mode 100644
index 0000000..6126640
Binary files /dev/null and b/media/live/external-content-duckduckgo-com-th-13.webp differ
diff --git a/media/live/external-content-duckduckgo-com-th-4.webp b/media/live/external-content-duckduckgo-com-th-4.webp
new file mode 100644
index 0000000..6126640
Binary files /dev/null and b/media/live/external-content-duckduckgo-com-th-4.webp differ
diff --git a/media/live/external-content-duckduckgo-com-th-5.webp b/media/live/external-content-duckduckgo-com-th-5.webp
new file mode 100644
index 0000000..6126640
Binary files /dev/null and b/media/live/external-content-duckduckgo-com-th-5.webp differ
diff --git a/media/live/external-content-duckduckgo-com-th-6.webp b/media/live/external-content-duckduckgo-com-th-6.webp
new file mode 100644
index 0000000..6126640
Binary files /dev/null and b/media/live/external-content-duckduckgo-com-th-6.webp differ
diff --git a/media/live/external-content-duckduckgo-com-th-7.webp b/media/live/external-content-duckduckgo-com-th-7.webp
new file mode 100644
index 0000000..6126640
Binary files /dev/null and b/media/live/external-content-duckduckgo-com-th-7.webp differ
diff --git a/media/live/external-content-duckduckgo-com-th-8.webp b/media/live/external-content-duckduckgo-com-th-8.webp
new file mode 100644
index 0000000..6126640
Binary files /dev/null and b/media/live/external-content-duckduckgo-com-th-8.webp differ
diff --git a/media/live/external-content-duckduckgo-com-th-9.webp b/media/live/external-content-duckduckgo-com-th-9.webp
new file mode 100644
index 0000000..6126640
Binary files /dev/null and b/media/live/external-content-duckduckgo-com-th-9.webp differ
diff --git a/media/live/kai-dahms-v0nbnxlwjzk-3unsp-1-1.webp b/media/live/kai-dahms-v0nbnxlwjzk-3unsp-1-1.webp
new file mode 100644
index 0000000..4b1013b
Binary files /dev/null and b/media/live/kai-dahms-v0nbnxlwjzk-3unsp-1-1.webp differ
diff --git a/media/live/kai-dahms-v0nbnxlwjzk-3unsp-1-2.webp b/media/live/kai-dahms-v0nbnxlwjzk-3unsp-1-2.webp
new file mode 100644
index 0000000..4b1013b
Binary files /dev/null and b/media/live/kai-dahms-v0nbnxlwjzk-3unsp-1-2.webp differ
diff --git a/media/live/kai-dahms-v0nbnxlwjzk-3unsp-1-3.webp b/media/live/kai-dahms-v0nbnxlwjzk-3unsp-1-3.webp
new file mode 100644
index 0000000..4b1013b
Binary files /dev/null and b/media/live/kai-dahms-v0nbnxlwjzk-3unsp-1-3.webp differ
diff --git a/media/live/kai-dahms-v0nbnxlwjzk-3unsp-1.webp b/media/live/kai-dahms-v0nbnxlwjzk-3unsp-1.webp
new file mode 100644
index 0000000..4b1013b
Binary files /dev/null and b/media/live/kai-dahms-v0nbnxlwjzk-3unsp-1.webp differ
diff --git a/media/live/nvhmq-pb-cs.webp b/media/live/nvhmq-pb-cs.webp
new file mode 100644
index 0000000..0ad4d71
Binary files /dev/null and b/media/live/nvhmq-pb-cs.webp differ
diff --git a/media/live/pxart-1.webp b/media/live/pxart-1.webp
new file mode 100644
index 0000000..e74f0ad
Binary files /dev/null and b/media/live/pxart-1.webp differ
diff --git a/media/live/pxart-2.webp b/media/live/pxart-2.webp
new file mode 100644
index 0000000..e74f0ad
Binary files /dev/null and b/media/live/pxart-2.webp differ
diff --git a/media/live/yaml-editor.webp b/media/live/yaml-editor.webp
new file mode 100644
index 0000000..97a1e53
Binary files /dev/null and b/media/live/yaml-editor.webp differ
diff --git a/media/live/youtube-nvhmq-pb-cs-1.webp b/media/live/youtube-nvhmq-pb-cs-1.webp
new file mode 100644
index 0000000..0ad4d71
Binary files /dev/null and b/media/live/youtube-nvhmq-pb-cs-1.webp differ
diff --git a/media/live/youtube-nvhmq-pb-cs-2.webp b/media/live/youtube-nvhmq-pb-cs-2.webp
new file mode 100644
index 0000000..0ad4d71
Binary files /dev/null and b/media/live/youtube-nvhmq-pb-cs-2.webp differ
diff --git a/media/live/youtube-nvhmq-pb-cs.webp b/media/live/youtube-nvhmq-pb-cs.webp
new file mode 100644
index 0000000..0ad4d71
Binary files /dev/null and b/media/live/youtube-nvhmq-pb-cs.webp differ
diff --git a/media/original/don-quijote-1.png b/media/original/don-quijote-1.png
new file mode 100644
index 0000000..0bdca53
Binary files /dev/null and b/media/original/don-quijote-1.png differ
diff --git a/media/original/external-content-duckduckgo-com-th-10.png b/media/original/external-content-duckduckgo-com-th-10.png
new file mode 100644
index 0000000..219ecae
Binary files /dev/null and b/media/original/external-content-duckduckgo-com-th-10.png differ
diff --git a/media/original/external-content-duckduckgo-com-th-11.png b/media/original/external-content-duckduckgo-com-th-11.png
new file mode 100644
index 0000000..219ecae
Binary files /dev/null and b/media/original/external-content-duckduckgo-com-th-11.png differ
diff --git a/media/original/external-content-duckduckgo-com-th-12.png b/media/original/external-content-duckduckgo-com-th-12.png
new file mode 100644
index 0000000..219ecae
Binary files /dev/null and b/media/original/external-content-duckduckgo-com-th-12.png differ
diff --git a/media/original/external-content-duckduckgo-com-th-13.png b/media/original/external-content-duckduckgo-com-th-13.png
new file mode 100644
index 0000000..219ecae
Binary files /dev/null and b/media/original/external-content-duckduckgo-com-th-13.png differ
diff --git a/media/original/external-content-duckduckgo-com-th-4.png b/media/original/external-content-duckduckgo-com-th-4.png
new file mode 100644
index 0000000..219ecae
Binary files /dev/null and b/media/original/external-content-duckduckgo-com-th-4.png differ
diff --git a/media/original/external-content-duckduckgo-com-th-5.png b/media/original/external-content-duckduckgo-com-th-5.png
new file mode 100644
index 0000000..219ecae
Binary files /dev/null and b/media/original/external-content-duckduckgo-com-th-5.png differ
diff --git a/media/original/external-content-duckduckgo-com-th-6.png b/media/original/external-content-duckduckgo-com-th-6.png
new file mode 100644
index 0000000..219ecae
Binary files /dev/null and b/media/original/external-content-duckduckgo-com-th-6.png differ
diff --git a/media/original/external-content-duckduckgo-com-th-7.png b/media/original/external-content-duckduckgo-com-th-7.png
new file mode 100644
index 0000000..219ecae
Binary files /dev/null and b/media/original/external-content-duckduckgo-com-th-7.png differ
diff --git a/media/original/external-content-duckduckgo-com-th-8.png b/media/original/external-content-duckduckgo-com-th-8.png
new file mode 100644
index 0000000..219ecae
Binary files /dev/null and b/media/original/external-content-duckduckgo-com-th-8.png differ
diff --git a/media/original/external-content-duckduckgo-com-th-9.png b/media/original/external-content-duckduckgo-com-th-9.png
new file mode 100644
index 0000000..219ecae
Binary files /dev/null and b/media/original/external-content-duckduckgo-com-th-9.png differ
diff --git a/media/original/kai-dahms-v0nbnxlwjzk-3unsp-1-1.webp b/media/original/kai-dahms-v0nbnxlwjzk-3unsp-1-1.webp
new file mode 100644
index 0000000..6f152ce
Binary files /dev/null and b/media/original/kai-dahms-v0nbnxlwjzk-3unsp-1-1.webp differ
diff --git a/media/original/kai-dahms-v0nbnxlwjzk-3unsp-1-2.webp b/media/original/kai-dahms-v0nbnxlwjzk-3unsp-1-2.webp
new file mode 100644
index 0000000..6f152ce
Binary files /dev/null and b/media/original/kai-dahms-v0nbnxlwjzk-3unsp-1-2.webp differ
diff --git a/media/original/kai-dahms-v0nbnxlwjzk-3unsp-1-3.webp b/media/original/kai-dahms-v0nbnxlwjzk-3unsp-1-3.webp
new file mode 100644
index 0000000..6f152ce
Binary files /dev/null and b/media/original/kai-dahms-v0nbnxlwjzk-3unsp-1-3.webp differ
diff --git a/media/original/kai-dahms-v0nbnxlwjzk-3unsp-1.png b/media/original/kai-dahms-v0nbnxlwjzk-3unsp-1.png
new file mode 100644
index 0000000..dd18361
Binary files /dev/null and b/media/original/kai-dahms-v0nbnxlwjzk-3unsp-1.png differ
diff --git a/media/original/kai-dahms-v0nbnxlwjzk-3unsp-1.webp b/media/original/kai-dahms-v0nbnxlwjzk-3unsp-1.webp
new file mode 100644
index 0000000..6f152ce
Binary files /dev/null and b/media/original/kai-dahms-v0nbnxlwjzk-3unsp-1.webp differ
diff --git a/media/original/nvhmq-pb-cs.jpg b/media/original/nvhmq-pb-cs.jpg
new file mode 100644
index 0000000..3917c9e
Binary files /dev/null and b/media/original/nvhmq-pb-cs.jpg differ
diff --git a/media/original/pxart-1.png b/media/original/pxart-1.png
new file mode 100644
index 0000000..f981285
Binary files /dev/null and b/media/original/pxart-1.png differ
diff --git a/media/original/pxart-2.png b/media/original/pxart-2.png
new file mode 100644
index 0000000..f981285
Binary files /dev/null and b/media/original/pxart-2.png differ
diff --git a/media/original/yaml-editor.png b/media/original/yaml-editor.png
new file mode 100644
index 0000000..f2ed70c
Binary files /dev/null and b/media/original/yaml-editor.png differ
diff --git a/media/original/youtube-nvhmq-pb-cs-1.jpg b/media/original/youtube-nvhmq-pb-cs-1.jpg
new file mode 100644
index 0000000..3917c9e
Binary files /dev/null and b/media/original/youtube-nvhmq-pb-cs-1.jpg differ
diff --git a/media/original/youtube-nvhmq-pb-cs-2.jpg b/media/original/youtube-nvhmq-pb-cs-2.jpg
new file mode 100644
index 0000000..3917c9e
Binary files /dev/null and b/media/original/youtube-nvhmq-pb-cs-2.jpg differ
diff --git a/media/original/youtube-nvhmq-pb-cs.jpg b/media/original/youtube-nvhmq-pb-cs.jpg
new file mode 100644
index 0000000..3917c9e
Binary files /dev/null and b/media/original/youtube-nvhmq-pb-cs.jpg differ
diff --git a/media/thumbs/don-quijote-1.webp b/media/thumbs/don-quijote-1.webp
new file mode 100644
index 0000000..7581eca
Binary files /dev/null and b/media/thumbs/don-quijote-1.webp differ
diff --git a/media/thumbs/external-content-duckduckgo-com-th-10.webp b/media/thumbs/external-content-duckduckgo-com-th-10.webp
new file mode 100644
index 0000000..aba76b1
Binary files /dev/null and b/media/thumbs/external-content-duckduckgo-com-th-10.webp differ
diff --git a/media/thumbs/external-content-duckduckgo-com-th-11.webp b/media/thumbs/external-content-duckduckgo-com-th-11.webp
new file mode 100644
index 0000000..aba76b1
Binary files /dev/null and b/media/thumbs/external-content-duckduckgo-com-th-11.webp differ
diff --git a/media/thumbs/external-content-duckduckgo-com-th-12.webp b/media/thumbs/external-content-duckduckgo-com-th-12.webp
new file mode 100644
index 0000000..aba76b1
Binary files /dev/null and b/media/thumbs/external-content-duckduckgo-com-th-12.webp differ
diff --git a/media/thumbs/external-content-duckduckgo-com-th-13.webp b/media/thumbs/external-content-duckduckgo-com-th-13.webp
new file mode 100644
index 0000000..aba76b1
Binary files /dev/null and b/media/thumbs/external-content-duckduckgo-com-th-13.webp differ
diff --git a/media/thumbs/external-content-duckduckgo-com-th-4.webp b/media/thumbs/external-content-duckduckgo-com-th-4.webp
new file mode 100644
index 0000000..aba76b1
Binary files /dev/null and b/media/thumbs/external-content-duckduckgo-com-th-4.webp differ
diff --git a/media/thumbs/external-content-duckduckgo-com-th-5.webp b/media/thumbs/external-content-duckduckgo-com-th-5.webp
new file mode 100644
index 0000000..aba76b1
Binary files /dev/null and b/media/thumbs/external-content-duckduckgo-com-th-5.webp differ
diff --git a/media/thumbs/external-content-duckduckgo-com-th-6.webp b/media/thumbs/external-content-duckduckgo-com-th-6.webp
new file mode 100644
index 0000000..aba76b1
Binary files /dev/null and b/media/thumbs/external-content-duckduckgo-com-th-6.webp differ
diff --git a/media/thumbs/external-content-duckduckgo-com-th-7.webp b/media/thumbs/external-content-duckduckgo-com-th-7.webp
new file mode 100644
index 0000000..aba76b1
Binary files /dev/null and b/media/thumbs/external-content-duckduckgo-com-th-7.webp differ
diff --git a/media/thumbs/external-content-duckduckgo-com-th-8.webp b/media/thumbs/external-content-duckduckgo-com-th-8.webp
new file mode 100644
index 0000000..aba76b1
Binary files /dev/null and b/media/thumbs/external-content-duckduckgo-com-th-8.webp differ
diff --git a/media/thumbs/external-content-duckduckgo-com-th-9.webp b/media/thumbs/external-content-duckduckgo-com-th-9.webp
new file mode 100644
index 0000000..aba76b1
Binary files /dev/null and b/media/thumbs/external-content-duckduckgo-com-th-9.webp differ
diff --git a/media/thumbs/kai-dahms-v0nbnxlwjzk-3unsp-1-1.webp b/media/thumbs/kai-dahms-v0nbnxlwjzk-3unsp-1-1.webp
new file mode 100644
index 0000000..06c9bee
Binary files /dev/null and b/media/thumbs/kai-dahms-v0nbnxlwjzk-3unsp-1-1.webp differ
diff --git a/media/thumbs/kai-dahms-v0nbnxlwjzk-3unsp-1-2.webp b/media/thumbs/kai-dahms-v0nbnxlwjzk-3unsp-1-2.webp
new file mode 100644
index 0000000..06c9bee
Binary files /dev/null and b/media/thumbs/kai-dahms-v0nbnxlwjzk-3unsp-1-2.webp differ
diff --git a/media/thumbs/kai-dahms-v0nbnxlwjzk-3unsp-1-3.webp b/media/thumbs/kai-dahms-v0nbnxlwjzk-3unsp-1-3.webp
new file mode 100644
index 0000000..06c9bee
Binary files /dev/null and b/media/thumbs/kai-dahms-v0nbnxlwjzk-3unsp-1-3.webp differ
diff --git a/media/thumbs/kai-dahms-v0nbnxlwjzk-3unsp-1.webp b/media/thumbs/kai-dahms-v0nbnxlwjzk-3unsp-1.webp
new file mode 100644
index 0000000..06c9bee
Binary files /dev/null and b/media/thumbs/kai-dahms-v0nbnxlwjzk-3unsp-1.webp differ
diff --git a/media/thumbs/nvhmq-pb-cs.webp b/media/thumbs/nvhmq-pb-cs.webp
new file mode 100644
index 0000000..c68a879
Binary files /dev/null and b/media/thumbs/nvhmq-pb-cs.webp differ
diff --git a/media/thumbs/pxart-1.webp b/media/thumbs/pxart-1.webp
new file mode 100644
index 0000000..1b144c8
Binary files /dev/null and b/media/thumbs/pxart-1.webp differ
diff --git a/media/thumbs/pxart-2.webp b/media/thumbs/pxart-2.webp
new file mode 100644
index 0000000..1b144c8
Binary files /dev/null and b/media/thumbs/pxart-2.webp differ
diff --git a/media/thumbs/yaml-editor.webp b/media/thumbs/yaml-editor.webp
new file mode 100644
index 0000000..712aec3
Binary files /dev/null and b/media/thumbs/yaml-editor.webp differ
diff --git a/media/thumbs/youtube-nvhmq-pb-cs-1.webp b/media/thumbs/youtube-nvhmq-pb-cs-1.webp
new file mode 100644
index 0000000..c68a879
Binary files /dev/null and b/media/thumbs/youtube-nvhmq-pb-cs-1.webp differ
diff --git a/media/thumbs/youtube-nvhmq-pb-cs-2.webp b/media/thumbs/youtube-nvhmq-pb-cs-2.webp
new file mode 100644
index 0000000..c68a879
Binary files /dev/null and b/media/thumbs/youtube-nvhmq-pb-cs-2.webp differ
diff --git a/media/thumbs/youtube-nvhmq-pb-cs.webp b/media/thumbs/youtube-nvhmq-pb-cs.webp
new file mode 100644
index 0000000..c68a879
Binary files /dev/null and b/media/thumbs/youtube-nvhmq-pb-cs.webp differ
diff --git a/system/Extensions/ParsedownExtension.php b/system/Extensions/ParsedownExtension.php
index 2361d18..11c38b0 100644
--- a/system/Extensions/ParsedownExtension.php
+++ b/system/Extensions/ParsedownExtension.php
@@ -2,10 +2,9 @@
namespace Typemill\Extensions;
-use Typemill\Models\Folder;
+use Typemill\Static\Slug;
use Typemill\Events\OnShortcodeFound;
-
class ParsedownExtension extends \ParsedownExtra
{
function __construct($baseUrl = '', $settings = NULL, $dispatcher = NULL)
@@ -360,7 +359,7 @@ class ParsedownExtension extends \ParsedownExtra
}
$text = trim($Line['text'], '#');
- $headline = Folder::createSlug($Line['text'], $this->settings);
+ $headline = Slug::createSlug($Line['text'], $this->settings);
if ($this->strictMode and isset($text[0]) and $text[0] !== ' ')
{
diff --git a/system/typemill/Controllers/Controller.php b/system/typemill/Controllers/Controller.php
index 91abf15..9c10441 100644
--- a/system/typemill/Controllers/Controller.php
+++ b/system/typemill/Controllers/Controller.php
@@ -41,6 +41,83 @@ abstract class Controller
return false;
}
+ protected function getItem($navigation, $url, $urlinfo)
+ {
+ $url = $this->removeEditorFromUrl($url);
+ $langattr = $this->settings['langattr'];
+
+ if($url == '/')
+ {
+ $keyPathArray = [''];
+ }
+
+ else
+ {
+ $extendedNavigation = $navigation->getExtendedNavigation($urlinfo, $langattr);
+
+ $pageinfo = $extendedNavigation[$url] ?? false;
+ if(!$pageinfo)
+ {
+ # page not found
+ return false;
+ }
+
+ $keyPathArray = explode(".", $pageinfo['keyPath']);
+
+ }
+
+ $draftNavigation = $navigation->getDraftNavigation($urlinfo, $langattr);
+
+ $item = $navigation->getItemWithKeyPath($draftNavigation, $keyPathArray, $urlinfo['basepath']);
+
+ return $item;
+ }
+
+ protected function removeEditorFromUrl($url)
+ {
+ $url = trim($url, '/');
+
+ $url = str_replace('tm/content/visual', '', $url);
+ $url = str_replace('tm/content/raw', '', $url);
+
+ $url = trim($url, '/');
+
+ return '/' . $url;
+ }
+
+ protected function validateRights($userrole, $resource, $action)
+ {
+ $acl = $this->c->get('acl');
+
+ if($acl->isAllowed($userrole, $resource, $action))
+ {
+ return true;
+ }
+
+ # check ownership.
+
+ $writeMeta = new writeMeta();
+ $pagemeta = $writeMeta->getPageMeta($this->settings, $this->item);
+
+ if(
+ isset($pagemeta['meta']['owner']) &&
+ $pagemeta['meta']['owner'] &&
+ $pagemeta['meta']['owner'] !== ''
+ )
+ {
+ $allowedusers = array_map('trim', explode(",", $pagemeta['meta']['owner']));
+ if(
+ isset($_SESSION['user']) &&
+ in_array($_SESSION['user'], $allowedusers)
+ )
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
# move to another place??
protected function recursiveValidation($validator, array $formdefinitions, $input, $output = [])
{
@@ -104,495 +181,4 @@ abstract class Controller
return $output;
}
-
-
-
-
-/*
- protected function setUrlCollection($uri)
- {
- $scheme = $uri->getScheme();
- $authority = $uri->getAuthority();
- $protocol = ($scheme ? $scheme . ':' : '') . ($authority ? '//' . $authority : '');
-
- $this->basePath = $this->c->get('basePath');
- $this->currentPath = $uri->getPath();
- $this->fullBaseUrl = $protocol . $this->basePath;
- $this->fullCurrentUrl = $protocol . $this->currentPath;
-
- $this->urlCollection = [
- 'basePath' => $this->basePath,
- 'currentPath' => $this->currentPath,
- 'fullBaseUrl' => $this->fullBaseUrl,
- 'fullCurrentUrl' => $this->fullCurrentUrl
- ];
- }
-
-
-/*
- # holds the pimple container
- protected $c;
-
- # holds the settings
- protected $settings;
-
- # holds the write cache object
- protected $writeCache;
-
- # holds the structure of content folder as a serialized array of objects
- protected $structureDraft = false;
-
- # holds the structure of content folder as a serialized array of objects
- protected $structureLive = false;
-
- # holds the name of the structure-file with drafts for author environment
- protected $structureDraftName = 'structure-draft.txt';
-
- # holds the name of the structure-file without drafts for live site
- protected $structureLiveName = 'structure.txt';
-
- # holds the frontend navigation without hidden pages
- protected $navigation = false;
-
- # holds the list of pages with navigation titles and hidden pages. It extends the structures and navigations
- protected $extended = false;
-
- public function __construct(ContainerInterface $c)
- {
- $this->c = $c;
- $this->settings = $this->c->get('settings');
-
- # used everywhere so instantiate it
- $this->writeCache = new writeCache();
-
- $this->c->dispatcher->dispatch('onTwigLoaded');
- }
-
- # render page for frontend
- protected function render($response, $route, $data)
- {
- # why commented this out??
- $data = $this->c->dispatcher->dispatch('onPageReady', new OnPageReady($data))->getData();
-
- if(isset($_SESSION['old']))
- {
- unset($_SESSION['old']);
- }
-
- $response = $response->withoutHeader('Server');
- $response = $response->withAddedHeader('X-Powered-By', 'Typemill');
-
- if(!isset($this->settings['headersoff']) or !$this->settings['headersoff'])
- {
- $response = $response->withAddedHeader('X-Content-Type-Options', 'nosniff');
- $response = $response->withAddedHeader('X-Frame-Options', 'SAMEORIGIN');
- $response = $response->withAddedHeader('X-XSS-Protection', '1;mode=block');
- $response = $response->withAddedHeader('Referrer-Policy', 'no-referrer-when-downgrade');
- if($this->c->request->getUri()->getScheme() == 'https')
- {
- $response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000');
- }
- }
-
- return $this->c->view->render($response, $route, $data);
- }
-
- # render 404 for frontend
- protected function render404($response, $data = NULL)
- {
- return $this->c->view->render($response->withStatus(404), '/404.twig', $data);
- }
-
- # render page for authors (admin-area)
- protected function renderIntern($response, $route, $data)
- {
- if(isset($_SESSION['old']))
- {
- unset($_SESSION['old']);
- }
-
- $response = $response->withoutHeader('Server');
- $response = $response->withAddedHeader('X-Powered-By', 'Typemill');
-
- if(!isset($this->settings['headersoff']) or !$this->settings['headersoff'])
- {
- $response = $response->withAddedHeader('X-Content-Type-Options', 'nosniff');
- $response = $response->withAddedHeader('X-Frame-Options', 'SAMEORIGIN');
- $response = $response->withAddedHeader('X-XSS-Protection', '1;mode=block');
- $response = $response->withAddedHeader('Referrer-Policy', 'no-referrer-when-downgrade');
- if($this->c->request->getUri()->getScheme() == 'https')
- {
- $response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000');
- }
- }
-
- return $this->c->view->render($response, $route, $data);
- }
-
- # render 404 for authors
- protected function renderIntern404($response, $data = NULL)
- {
- return $this->c->view->render($response->withStatus(404), '/intern404.twig', $data);
- }
-
- # reads the cached structure with published and non-published pages for the author
- protected function setStructureDraft()
- {
- # get the cached structure
- $this->structureDraft = $this->writeCache->getCache('cache', $this->structureDraftName);
-
- # if there is no cached structure
- if(!$this->structureDraft)
- {
- return $this->setFreshStructureDraft();
- }
-
- return true;
- }
-
- # creates a fresh structure with published and non-published pages for the author
- protected function setFreshStructureDraft()
- {
- # scan the content of the folder
- $pagetreeDraft = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft = true );
-
- # if there is content, then get the content details
- if(count($pagetreeDraft) > 0)
- {
- # get the extended structure files with changes like navigation title or hidden pages
- $yaml = new writeYaml();
- $extended = $this->getExtended();
-
- # create an array of object with the whole content of the folder and changes from extended file
- $this->structureDraft = Folder::getFolderContentDetails($pagetreeDraft, $extended, $this->settings, $this->uri->getBaseUrl(), $this->uri->getBasePath());
-
- # cache structure draft
- $this->writeCache->updateCache('cache', $this->structureDraftName, 'lastCache.txt', $this->structureDraft);
-
- return true;
- }
-
- return false;
- }
-
- # reads the cached structure of published pages
- protected function setStructureLive()
- {
- # get the cached structure
- $this->structureLive = $this->writeCache->getCache('cache', $this->structureLiveName);
-
- # if there is no cached structure
- if(!$this->structureLive)
- {
- return $this->setFreshStructureLive();
- }
-
- return true;
- }
-
- # creates a fresh structure with published pages
- protected function setFreshStructureLive()
- {
- # scan the content of the folder
- $pagetreeLive = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft = false );
-
- # if there is content, then get the content details
- if($pagetreeLive && count($pagetreeLive) > 0)
- {
- # get the extended structure files with changes like navigation title or hidden pages
- $yaml = new writeYaml();
- $extended = $this->getExtended();
-
- # create an array of object with the whole content of the folder and changes from extended file
- $this->structureLive = Folder::getFolderContentDetails($pagetreeLive, $extended, $this->settings, $this->uri->getBaseUrl(), $this->uri->getBasePath());
-
- # cache structure live
- $this->writeCache->updateCache('cache', $this->structureLiveName, 'lastCache.txt', $this->structureLive);
-
- return true;
- }
-
- return false;
- }
-
- # reads the live navigation from cache (live structure without hidden pages)
- protected function setNavigation()
- {
- # get the cached structure
- $this->navigation = $this->writeCache->getCache('cache', 'navigation.txt');
-
- # if there is no cached structure
- if(!$this->navigation)
- {
- return $this->setFreshNavigation();
- }
-
- return true;
- }
-
- # creates a fresh live navigation (live structure without hidden pages)
- protected function setFreshNavigation()
- {
-
- if(!$this->extended)
- {
- $extended = $this->getExtended();
- }
-
- if($this->containsHiddenPages($this->extended))
- {
- if(!$this->structureLive)
- {
- $this->setStructureLive();
- }
-
- $structureLive = $this->c->dispatcher->dispatch('onPagetreeLoaded', new OnPagetreeLoaded($this->structureLive))->getData();
- $this->navigation = $this->createNavigation($structureLive);
-
- # cache navigation
- $this->writeCache->updateCache('cache', 'navigation.txt', false, $this->navigation);
-
- return true;
- }
-
- # make sure no old navigation file is left
- $this->writeCache->deleteFileWithPath('cache' . DIRECTORY_SEPARATOR . 'navigation.txt');
-
- return false;
- }
-
- # create navigation from structure
- protected function createNavigation($structureLive)
- {
- foreach ($structureLive as $key => $element)
- {
- if($element->hide === true)
- {
- unset($structureLive[$key]);
- }
- elseif(isset($element->folderContent))
- {
- $structureLive[$key]->folderContent = $this->createNavigation($element->folderContent);
- }
- }
-
- return $structureLive;
- }
-
- # controllerFrontendWebsite, but not in use, makes no sense to check on each page load
- public function checkSitemap()
- {
- if(!$this->writeCache->getCache('cache', 'sitemap.xml'))
- {
- if(!$this->structureLive)
- {
- $this->setStructureLive();
- }
-
- $this->updateSitemap();
- }
-
- return true;
- }
-
-
- public function updateSitemap($ping = false)
- {
- $sitemap = '' . "\n";
- $sitemap .= '
'; + print_r($draftHtml); + die(); +*/ return $this->c->get('view')->render($response, 'content/blox-editor.twig', [ 'settings' => $this->settings, 'mainnavi' => $mainNavigation, + 'content' => $draftMarkdownHtml, 'jsdata' => [ 'settings' => $this->settings, + 'urlinfo' => $urlinfo, 'navigation' => $draftNavigation, 'item' => $item, - 'urlinfo' => $urlinfo + 'home' => $home, + 'content' => $draftMarkdownHtml ] ]); - # set structure - if(!$this->setStructureDraft()){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); } # set information for homepage $this->setHomepage($args); - - # set item - if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structureDraft, 'settings' => $this->settings, 'content' => $this->errors )); } - # we have to check ownership here to use it for permission-check in templates $this->checkContentOwnership(); - # set the status for published and drafted - $this->setPublishStatus(); - + $this->setPublishStatus(); # set path $this->setItemPath($this->item->fileType); - # add the modified date for the file - $this->item->modified = ($this->item->published OR $this->item->drafted) ? filemtime($this->settings['contentFolder'] . $this->path) : false; - # read content from file if(!$this->setContent()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); } diff --git a/system/typemill/Controllers/ControllerWebSystem.php b/system/typemill/Controllers/ControllerWebSystem.php index 611382a..4bb9532 100644 --- a/system/typemill/Controllers/ControllerWebSystem.php +++ b/system/typemill/Controllers/ControllerWebSystem.php @@ -2,48 +2,69 @@ namespace Typemill\Controllers; -use Typemill\Models\StorageWrapper; +use Typemill\Models\Navigation; +use Typemill\Models\Extension; use Typemill\Models\User; use Typemill\Models\License; +use Typemill\Static\Settings; -class ControllerWebSystem extends ControllerData +class ControllerWebSystem extends Controller { public function showSettings($request, $response, $args) { - $storage = new StorageWrapper('\Typemill\Models\Storage'); - $systemfields = $storage->getYaml('systemSettings', '', 'system.yaml'); - $translations = $this->c->get('translations'); + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo') + ); + + $systemfields = Settings::getSettingsDefinitions(); # add full url for sitemap to settings $this->settings['sitemap'] = $this->c->get('urlinfo')['baseurl'] . '/cache/sitemap.xml'; return $this->c->get('view')->render($response, 'system/system.twig', [ +# 'captcha' => $this->checkIfAddCaptcha(), # 'basicauth' => $user->getBasicAuth(), 'settings' => $this->settings, - 'mainnavi' => $this->getMainNavigation($request->getAttribute('c_userrole')), - 'systemnavi' => $this->getSystemNavigation($request->getAttribute('c_userrole')), + 'mainnavi' => $mainNavigation, + 'systemnavi' => $systemNavigation, 'jsdata' => [ 'settings' => $this->settings, 'system' => $systemfields, - 'labels' => $translations, + 'labels' => $this->c->get('translations'), 'urlinfo' => $this->c->get('urlinfo') ] -# 'captcha' => $this->checkIfAddCaptcha(), ]); } public function showThemes($request, $response, $args) { - $storage = new StorageWrapper('\Typemill\Models\Storage'); - $translations = $this->c->get('translations'); - $themeDefinitions = $this->getThemeDetails(); + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); - $themeSettings = []; - foreach($this->settings['themes'] as $themename => $themeinputs) - { - $themeSettings[$themename] = $themeinputs; - $themeSettings[$themename]['customcss'] = $storage->getFile('cacheFolder', '', $themename . '-custom.css'); - } + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo') + ); + + $extension = new Extension(); + $themeDefinitions = $extension->getThemeDetails(); + $themeSettings = $extension->getThemeSettings($this->settings['themes']); $license = []; if(is_array($this->settings['license'])) @@ -53,14 +74,14 @@ class ControllerWebSystem extends ControllerData return $this->c->get('view')->render($response, 'system/themes.twig', [ 'settings' => $this->settings, - 'mainnavi' => $this->getMainNavigation($request->getAttribute('c_userrole')), - 'systemnavi' => $this->getSystemNavigation($request->getAttribute('c_userrole')), + 'mainnavi' => $mainNavigation, + 'systemnavi' => $systemNavigation, 'jsdata' => [ 'settings' => $themeSettings, 'definitions' => $themeDefinitions, 'theme' => $this->settings['theme'], 'license' => $license, - 'labels' => $translations, + 'labels' => $this->c->get('translations'), 'urlinfo' => $this->c->get('urlinfo') ] ]); @@ -68,15 +89,23 @@ class ControllerWebSystem extends ControllerData public function showPlugins($request, $response, $args) { - $translations = $this->c->get('translations'); - $pluginDefinitions = $this->getPluginDetails(); - - $pluginSettings = []; + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); - foreach($this->settings['plugins'] as $pluginname => $plugininputs) - { - $pluginSettings[$pluginname] = $plugininputs; - } + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo') + ); + + $extension = new Extension(); + $pluginDefinitions = $extension->getPluginDetails(); + $pluginSettings = $extension->getPluginSettings($this->settings['plugins']); $license = []; if(is_array($this->settings['license'])) @@ -86,13 +115,13 @@ class ControllerWebSystem extends ControllerData return $this->c->get('view')->render($response, 'system/plugins.twig', [ 'settings' => $this->settings, - 'mainnavi' => $this->getMainNavigation($request->getAttribute('c_userrole')), - 'systemnavi' => $this->getSystemNavigation($request->getAttribute('c_userrole')), + 'mainnavi' => $mainNavigation, + 'systemnavi' => $systemNavigation, 'jsdata' => [ 'settings' => $pluginSettings, 'definitions' => $pluginDefinitions, 'license' => $license, - 'labels' => $translations, + 'labels' => $this->c->get('translations'), 'urlinfo' => $this->c->get('urlinfo') ] ]); @@ -100,11 +129,22 @@ class ControllerWebSystem extends ControllerData public function showLicense($request, $response, $args) { - $storage = new StorageWrapper('\Typemill\Models\Storage'); - $license = new License(); - $licensefields = $storage->getYaml('systemSettings', '', 'license.yaml'); - $translations = $this->c->get('translations'); + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo') + ); + + $license = new License(); + $licensefields = $license->getLicenseFields(); $licensedata = $license->getLicenseData($this->c->get('urlinfo')); if($licensedata) { @@ -116,35 +156,48 @@ class ControllerWebSystem extends ControllerData return $this->c->get('view')->render($response, 'system/license.twig', [ 'settings' => $this->settings, - 'mainnavi' => $this->getMainNavigation($request->getAttribute('c_userrole')), - 'systemnavi' => $this->getSystemNavigation($request->getAttribute('c_userrole')), + 'mainnavi' => $mainNavigation, + 'systemnavi' => $systemNavigation, 'jsdata' => [ 'licensedata' => $licensedata, 'licensefields' => $licensefields, - 'labels' => $translations, - 'urlinfo' => $this->c->get('urlinfo') ] + 'labels' => $this->c->get('translations'), + 'urlinfo' => $this->c->get('urlinfo') ] ]); } public function showAccount($request, $response, $args) { - $translations = $this->c->get('translations'); + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo') + ); + $username = $request->getAttribute('c_username'); $user = new User(); - $user->setUser($username); + $userdata = $user->getUserData(); - $userfields = $this->getUserFields($userdata['userrole']); + $userfields = $user->getUserFields($this->c->get('acl'), $userdata['userrole']); return $this->c->get('view')->render($response, 'system/account.twig', [ 'settings' => $this->settings, - 'mainnavi' => $this->getMainNavigation($request->getAttribute('c_userrole')), - 'systemnavi' => $this->getSystemNavigation($request->getAttribute('c_userrole')), + 'mainnavi' => $mainNavigation, + 'systemnavi' => $systemNavigation, 'jsdata' => [ 'userdata' => $userdata, 'userfields' => $userfields, 'userroles' => $this->c->get('acl')->getRoles(), - 'labels' => $translations, + 'labels' => $this->c->get('translations'), 'urlinfo' => $this->c->get('urlinfo') ] ]); @@ -152,12 +205,24 @@ class ControllerWebSystem extends ControllerData public function showUsers($request, $response, $args) { - $translations = $this->c->get('translations'); + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo') + ); + $user = new User(); $usernames = $user->getAllUsers(); $userdata = []; - - $count = 0; + $count = 0; foreach($usernames as $username) { if($count == 10) break; @@ -168,14 +233,14 @@ class ControllerWebSystem extends ControllerData return $this->c->get('view')->render($response, 'system/users.twig', [ 'settings' => $this->settings, - 'mainnavi' => $this->getMainNavigation($request->getAttribute('c_userrole')), - 'systemnavi' => $this->getSystemNavigation($request->getAttribute('c_userrole')), + 'mainnavi' => $mainNavigation, + 'systemnavi' => $systemNavigation, 'jsdata' => [ 'totalusers' => count($usernames), 'usernames' => $usernames, 'userdata' => $userdata, 'userroles' => $this->c->get('acl')->getRoles(), - 'labels' => $translations, + 'labels' => $this->c->get('translations'), 'urlinfo' => $this->c->get('urlinfo') ] ]); @@ -183,28 +248,40 @@ class ControllerWebSystem extends ControllerData public function showUser($request, $response, $args) { - $translations = $this->c->get('translations'); - $username = $args['username'] ?? false; - $inspector = $request->getAttribute('c_userrole'); - $user = new User(); + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo') + ); + + $user = new User(); + $username = $args['username'] ?? false; if(!$user->setUser($username)) { die("return a not found page"); } $userdata = $user->getUserData(); - $userfields = $this->getUserFields($userdata['userrole'], $inspector); + $inspector = $request->getAttribute('c_userrole'); + $userfields = $user->getUserFields($this->c->get('acl'), $userdata['userrole'], $inspector); return $this->c->get('view')->render($response, 'system/user.twig', [ 'settings' => $this->settings, - 'mainnavi' => $this->getMainNavigation($request->getAttribute('c_userrole')), - 'systemnavi' => $this->getSystemNavigation($request->getAttribute('c_userrole')), + 'mainnavi' => $mainNavigation, + 'systemnavi' => $systemNavigation, 'jsdata' => [ 'userdata' => $userdata, 'userfields' => $userfields, 'userroles' => $this->c->get('acl')->getRoles(), - 'labels' => $translations, + 'labels' => $this->c->get('translations'), 'urlinfo' => $this->c->get('urlinfo') ] ]); @@ -212,26 +289,33 @@ class ControllerWebSystem extends ControllerData public function newUser($request, $response, $args) { - $translations = $this->c->get('translations'); + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo') + ); return $this->c->get('view')->render($response, 'system/usernew.twig', [ 'settings' => $this->settings, - 'mainnavi' => $this->getMainNavigation($request->getAttribute('c_userrole')), - 'systemnavi' => $this->getSystemNavigation($request->getAttribute('c_userrole')), + 'mainnavi' => $mainNavigation, + 'systemnavi' => $systemNavigation, 'jsdata' => [ 'userroles' => $this->c->get('acl')->getRoles(), - 'labels' => $translations, + 'labels' => $this->c->get('translations'), 'urlinfo' => $this->c->get('urlinfo') ] ]); } - - - - - /* public function showBlank($request, $response, $args) { @@ -249,71 +333,6 @@ class ControllerWebSystem extends ControllerData 'content' => $content, 'route' => $route->getName() )); - } - - - public function clearCache($request, $response, $args) - { - $this->uri = $request->getUri()->withUserInfo(''); - $dir = $this->settings['basePath'] . 'cache'; - - $error = $this->writeCache->deleteCacheFiles($dir); - if($error) - { - return $response->withJson(['errors' => $error], 500); - } - - # create a new draft structure - $this->setFreshStructureDraft(); - - # create a new draft structure - $this->setFreshStructureLive(); - - # create a new draft structure - $this->setFreshNavigation(); - - # update the sitemap - $this->updateSitemap(); - - return $response->withJson(array('errors' => false)); - } - - - protected function saveImages($imageFields, $userInput, $userSettings, $files) - { - # initiate image processor with standard image sizes - $processImages = new ProcessImage($userSettings['images']); - - if(!$processImages->checkFolders('images')) - { - $this->c->flash->addMessage('error', 'Please make sure that your media folder exists and is writable.'); - return false; - } - - foreach($imageFields as $fieldName => $imageField) - { - if(isset($userInput[$fieldName])) - { - # handle single input with single file upload - $image = $files[$fieldName]; - - if($image->getError() === UPLOAD_ERR_OK) - { - # not the most elegant, but createImage expects a base64-encoded string. - $imageContent = $image->getStream()->getContents(); - $imageData = base64_encode($imageContent); - $imageSrc = 'data: ' . $image->getClientMediaType() . ';base64,' . $imageData; - - if($processImages->createImage($imageSrc, $image->getClientFilename(), $userSettings['images'], $overwrite = NULL)) - { - # returns image path to media library - $userInput[$fieldName] = $processImages->publishImage(); - } - } - } - } - return $userInput; - } - */ - + } +*/ } \ No newline at end of file diff --git a/system/typemill/Controllers/oldclasses.php b/system/typemill/Controllers/oldclasses.php new file mode 100644 index 0000000..58df960 --- /dev/null +++ b/system/typemill/Controllers/oldclasses.php @@ -0,0 +1,593 @@ +getScheme(); + $authority = $uri->getAuthority(); + $protocol = ($scheme ? $scheme . ':' : '') . ($authority ? '//' . $authority : ''); + + $this->basePath = $this->c->get('basePath'); + $this->currentPath = $uri->getPath(); + $this->fullBaseUrl = $protocol . $this->basePath; + $this->fullCurrentUrl = $protocol . $this->currentPath; + + $this->urlCollection = [ + 'basePath' => $this->basePath, + 'currentPath' => $this->currentPath, + 'fullBaseUrl' => $this->fullBaseUrl, + 'fullCurrentUrl' => $this->fullCurrentUrl + ]; + } + + # render page for frontend + protected function render($response, $route, $data) + { + # why commented this out?? + $data = $this->c->dispatcher->dispatch('onPageReady', new OnPageReady($data))->getData(); + + if(isset($_SESSION['old'])) + { + unset($_SESSION['old']); + } + + $response = $response->withoutHeader('Server'); + $response = $response->withAddedHeader('X-Powered-By', 'Typemill'); + + if(!isset($this->settings['headersoff']) or !$this->settings['headersoff']) + { + $response = $response->withAddedHeader('X-Content-Type-Options', 'nosniff'); + $response = $response->withAddedHeader('X-Frame-Options', 'SAMEORIGIN'); + $response = $response->withAddedHeader('X-XSS-Protection', '1;mode=block'); + $response = $response->withAddedHeader('Referrer-Policy', 'no-referrer-when-downgrade'); + if($this->c->request->getUri()->getScheme() == 'https') + { + $response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000'); + } + } + + return $this->c->view->render($response, $route, $data); + } + + # render 404 for frontend + protected function render404($response, $data = NULL) + { + return $this->c->view->render($response->withStatus(404), '/404.twig', $data); + } + + # render page for authors (admin-area) + protected function renderIntern($response, $route, $data) + { + if(isset($_SESSION['old'])) + { + unset($_SESSION['old']); + } + + $response = $response->withoutHeader('Server'); + $response = $response->withAddedHeader('X-Powered-By', 'Typemill'); + + if(!isset($this->settings['headersoff']) or !$this->settings['headersoff']) + { + $response = $response->withAddedHeader('X-Content-Type-Options', 'nosniff'); + $response = $response->withAddedHeader('X-Frame-Options', 'SAMEORIGIN'); + $response = $response->withAddedHeader('X-XSS-Protection', '1;mode=block'); + $response = $response->withAddedHeader('Referrer-Policy', 'no-referrer-when-downgrade'); + if($this->c->request->getUri()->getScheme() == 'https') + { + $response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000'); + } + } + + return $this->c->view->render($response, $route, $data); + } + + # render 404 for authors + protected function renderIntern404($response, $data = NULL) + { + return $this->c->view->render($response->withStatus(404), '/intern404.twig', $data); + } + + + + +########### MOVE TO GLOBAL API ?? + + public function clearCache($request, $response, $args) + { + $this->uri = $request->getUri()->withUserInfo(''); + $dir = $this->settings['basePath'] . 'cache'; + + $error = $this->writeCache->deleteCacheFiles($dir); + if($error) + { + return $response->withJson(['errors' => $error], 500); + } + + # create a new draft structure + $this->setFreshStructureDraft(); + + # create a new draft structure + $this->setFreshStructureLive(); + + # create a new draft structure + $this->setFreshNavigation(); + + # update the sitemap + $this->updateSitemap(); + + return $response->withJson(array('errors' => false)); + } + +############ CHECK IF NEEDED FOR API IMAGE ??? + + protected function saveImages($imageFields, $userInput, $userSettings, $files) + { + # initiate image processor with standard image sizes + $processImages = new ProcessImage($userSettings['images']); + + if(!$processImages->checkFolders('images')) + { + $this->c->flash->addMessage('error', 'Please make sure that your media folder exists and is writable.'); + return false; + } + + foreach($imageFields as $fieldName => $imageField) + { + if(isset($userInput[$fieldName])) + { + # handle single input with single file upload + $image = $files[$fieldName]; + + if($image->getError() === UPLOAD_ERR_OK) + { + # not the most elegant, but createImage expects a base64-encoded string. + $imageContent = $image->getStream()->getContents(); + $imageData = base64_encode($imageContent); + $imageSrc = 'data: ' . $image->getClientMediaType() . ';base64,' . $imageData; + + if($processImages->createImage($imageSrc, $image->getClientFilename(), $userSettings['images'], $overwrite = NULL)) + { + # returns image path to media library + $userInput[$fieldName] = $processImages->publishImage(); + } + } + } + } + return $userInput; + } + + +############ NAVIGATION ??? + + # reads the cached structure with published and non-published pages for the author + # setStructureDraft + protected function getStructureForAuthors($userrole, $username) + { + # get the cached structure + $this->structureDraft = $this->writeCache->getCache('cache', $this->structureDraftName); + + # if there is no cached structure + if(!$this->structureDraft) + { + return $this->setFreshStructureDraft(); + } + + return true; + } + + # creates a fresh structure with published and non-published pages for the author + # setFreshStrutureDraft + protected function createNewStructureForAuthors() + { + # scan the content of the folder + $pagetreeDraft = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft = true ); + + # if there is content, then get the content details + if(count($pagetreeDraft) > 0) + { + # get the extended structure files with changes like navigation title or hidden pages + $yaml = new writeYaml(); + $extended = $this->getExtended(); + + # create an array of object with the whole content of the folder and changes from extended file + $this->structureDraft = Folder::getFolderContentDetails($pagetreeDraft, $extended, $this->settings, $this->uri->getBaseUrl(), $this->uri->getBasePath()); + + # cache structure draft + $this->writeCache->updateCache('cache', $this->structureDraftName, 'lastCache.txt', $this->structureDraft); + + return true; + } + + return false; + } + + # reads the cached structure of published pages + # setStrutureLive + protected function getStructureForReaders() + { + # get the cached structure + $this->structureLive = $this->writeCache->getCache('cache', $this->structureLiveName); + + # if there is no cached structure + if(!$this->structureLive) + { + return $this->setFreshStructureLive(); + } + + return true; + } + + # creates a fresh structure with published pages + protected function setFreshStructureLive() + { + # scan the content of the folder + $pagetreeLive = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft = false ); + + # if there is content, then get the content details + if($pagetreeLive && count($pagetreeLive) > 0) + { + # get the extended structure files with changes like navigation title or hidden pages + $yaml = new writeYaml(); + $extended = $this->getExtended(); + + # create an array of object with the whole content of the folder and changes from extended file + $this->structureLive = Folder::getFolderContentDetails($pagetreeLive, $extended, $this->settings, $this->uri->getBaseUrl(), $this->uri->getBasePath()); + + # cache structure live + $this->writeCache->updateCache('cache', $this->structureLiveName, 'lastCache.txt', $this->structureLive); + + return true; + } + + return false; + } + + # reads the live navigation from cache (live structure without hidden pages) + protected function setNavigation() + { + # get the cached structure + $this->navigation = $this->writeCache->getCache('cache', 'navigation.txt'); + + # if there is no cached structure + if(!$this->navigation) + { + return $this->setFreshNavigation(); + } + + return true; + } + + # creates a fresh live navigation (live structure without hidden pages) + protected function setFreshNavigation() + { + + if(!$this->extended) + { + $extended = $this->getExtended(); + } + + if($this->containsHiddenPages($this->extended)) + { + if(!$this->structureLive) + { + $this->setStructureLive(); + } + + $structureLive = $this->c->dispatcher->dispatch('onPagetreeLoaded', new OnPagetreeLoaded($this->structureLive))->getData(); + $this->navigation = $this->createNavigation($structureLive); + + # cache navigation + $this->writeCache->updateCache('cache', 'navigation.txt', false, $this->navigation); + + return true; + } + + # make sure no old navigation file is left + $this->writeCache->deleteFileWithPath('cache' . DIRECTORY_SEPARATOR . 'navigation.txt'); + + return false; + } + + # create navigation from structure + protected function createNavigation($structureLive) + { + foreach ($structureLive as $key => $element) + { + if($element->hide === true) + { + unset($structureLive[$key]); + } + elseif(isset($element->folderContent)) + { + $structureLive[$key]->folderContent = $this->createNavigation($element->folderContent); + } + } + + return $structureLive; + } + + + # reads the cached structure with published and non-published pages for the author + protected function setStructureDraft() + { + # get the cached structure + $this->structureDraft = $this->writeCache->getCache('cache', $this->structureDraftName); + + # if there is no cached structure + if(!$this->structureDraft) + { + return $this->setFreshStructureDraft(); + } + + return true; + } + + # creates a fresh structure with published and non-published pages for the author + protected function setFreshStructureDraft() + { + # scan the content of the folder + $pagetreeDraft = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft = true ); + + # if there is content, then get the content details + if(count($pagetreeDraft) > 0) + { + # get the extended structure files with changes like navigation title or hidden pages + $yaml = new writeYaml(); + $extended = $this->getExtended(); + + # create an array of object with the whole content of the folder and changes from extended file + $this->structureDraft = Folder::getFolderContentDetails($pagetreeDraft, $extended, $this->settings, $this->uri->getBaseUrl(), $this->uri->getBasePath()); + + # cache structure draft + $this->writeCache->updateCache('cache', $this->structureDraftName, 'lastCache.txt', $this->structureDraft); + + return true; + } + + return false; + } + + # reads the cached structure of published pages + protected function setStructureLive() + { + # get the cached structure + $this->structureLive = $this->writeCache->getCache('cache', $this->structureLiveName); + + # if there is no cached structure + if(!$this->structureLive) + { + return $this->setFreshStructureLive(); + } + + return true; + } + + # creates a fresh structure with published pages + protected function setFreshStructureLive() + { + # scan the content of the folder + $pagetreeLive = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft = false ); + + # if there is content, then get the content details + if($pagetreeLive && count($pagetreeLive) > 0) + { + # get the extended structure files with changes like navigation title or hidden pages + $yaml = new writeYaml(); + $extended = $this->getExtended(); + + # create an array of object with the whole content of the folder and changes from extended file + $this->structureLive = Folder::getFolderContentDetails($pagetreeLive, $extended, $this->settings, $this->uri->getBaseUrl(), $this->uri->getBasePath()); + + # cache structure live + $this->writeCache->updateCache('cache', $this->structureLiveName, 'lastCache.txt', $this->structureLive); + + return true; + } + + return false; + } + + # reads the live navigation from cache (live structure without hidden pages) + protected function setNavigation() + { + # get the cached structure + $this->navigation = $this->writeCache->getCache('cache', 'navigation.txt'); + + # if there is no cached structure + if(!$this->navigation) + { + return $this->setFreshNavigation(); + } + + return true; + } + + # creates a fresh live navigation (live structure without hidden pages) + protected function setFreshNavigation() + { + + if(!$this->extended) + { + $extended = $this->getExtended(); + } + + if($this->containsHiddenPages($this->extended)) + { + if(!$this->structureLive) + { + $this->setStructureLive(); + } + + $structureLive = $this->c->dispatcher->dispatch('onPagetreeLoaded', new OnPagetreeLoaded($this->structureLive))->getData(); + $this->navigation = $this->createNavigation($structureLive); + + # cache navigation + $this->writeCache->updateCache('cache', 'navigation.txt', false, $this->navigation); + + return true; + } + + # make sure no old navigation file is left + $this->writeCache->deleteFileWithPath('cache' . DIRECTORY_SEPARATOR . 'navigation.txt'); + + return false; + } + + # create navigation from structure + protected function createNavigation($structureLive) + { + foreach ($structureLive as $key => $element) + { + if($element->hide === true) + { + unset($structureLive[$key]); + } + elseif(isset($element->folderContent)) + { + $structureLive[$key]->folderContent = $this->createNavigation($element->folderContent); + } + } + + return $structureLive; + } + + protected function getExtended() + { + $yaml = new writeYaml(); + + if(!$this->extended) + { + $this->extended = $yaml->getYaml('cache', 'structure-extended.yaml'); + } + + if(!$this->extended) + { + # scan the content of the folder + $pagetreeDraft = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft = true ); + + # if there is content, then get the content details + if(count($pagetreeDraft) == 0) + { + return false; + } + + # create an array of object with the whole content of the folder and changes from extended file + $structureDraft = Folder::getFolderContentDetails($pagetreeDraft, $extended = false, $this->settings, $this->uri->getBaseUrl(), $this->uri->getBasePath()); + + $this->extended = $this->createExtended($this->settings['rootPath'] . $this->settings['contentFolder'], $yaml, $structureDraft); + + $yaml->updateYaml('cache', 'structure-extended.yaml', $this->extended); + } + + return $this->extended; + } + + # creates a file that holds all hide flags and navigation titles + # reads all meta-files and creates an array with url => ['hide' => bool, 'navtitle' => 'bla'] + public function createExtended($contentPath, $yaml, $structureLive, $extended = NULL) + { + if(!$extended) + { + $extended = []; + } + + foreach ($structureLive as $key => $item) + { + # $filename = ($item->elementType == 'folder') ? DIRECTORY_SEPARATOR . 'index.yaml' : $item->pathWithoutType . '.yaml'; + $filename = $item->pathWithoutType . '.yaml'; + + if(file_exists($contentPath . $filename)) + { + # read file + $meta = $yaml->getYaml('content', $filename); + + $extended[$item->urlRelWoF]['hide'] = isset($meta['meta']['hide']) ? $meta['meta']['hide'] : false; + $extended[$item->urlRelWoF]['navtitle'] = isset($meta['meta']['navtitle']) ? $meta['meta']['navtitle'] : ''; + } + + if ($item->elementType == 'folder') + { + $extended = $this->createExtended($contentPath, $yaml, $item->folderContent, $extended); + } + } + return $extended; + } + + # only backoffice + protected function renameExtended($item, $newFolder) + { + # get the extended structure files with changes like navigation title or hidden pages + $yaml = new writeYaml(); + $extended = $yaml->getYaml('cache', 'structure-extended.yaml'); + + if(isset($extended[$item->urlRelWoF])) + { + $newUrl = $newFolder->urlRelWoF . '/' . $item->slug; + + $entry = $extended[$item->urlRelWoF]; + + unset($extended[$item->urlRelWoF]); + + $extended[$newUrl] = $entry; + $yaml->updateYaml('cache', 'structure-extended.yaml', $extended); + } + + return true; + } + + # only backoffice + protected function deleteFromExtended() + { + # get the extended structure files with changes like navigation title or hidden pages + $yaml = new writeYaml(); + $extended = $yaml->getYaml('cache', 'structure-extended.yaml'); + + if($this->item->elementType == "file" && isset($extended[$this->item->urlRelWoF])) + { + unset($extended[$this->item->urlRelWoF]); + $yaml->updateYaml('cache', 'structure-extended.yaml', $extended); + } + + if($this->item->elementType == "folder") + { + $changed = false; + + # delete all entries with that folder url + foreach($extended as $url => $entries) + { + if( strpos($url, $this->item->urlRelWoF) !== false ) + { + $changed = true; + unset($extended[$url]); + } + } + + if($changed) + { + $yaml->updateYaml('cache', 'structure-extended.yaml', $extended); + } + } + } + + + # checks if there is a hidden page, returns true on first find + protected function containsHiddenPages($extended) + { + foreach($extended as $element) + { + if(isset($element['hide']) && $element['hide'] === true) + { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/system/typemill/Extensions/ParsedownExtension.php b/system/typemill/Extensions/ParsedownExtension.php new file mode 100644 index 0000000..eb28b91 --- /dev/null +++ b/system/typemill/Extensions/ParsedownExtension.php @@ -0,0 +1,1267 @@ +settings = $settings; + + $this->dispatcher = $dispatcher; + + # show anchor next to headline? + $this->showAnchor = isset($settings['headlineanchors']) ? $settings['headlineanchors'] : false; + + # extend link schemes + $urlschemes = ( isset($settings['urlschemes']) && !empty($settings['urlschemes']) ) ? explode(",", $settings['urlschemes']) : false; + if($urlschemes) + { + foreach($urlschemes as $urlschema) + { + $this->safeLinksWhitelist[] = $urlschema; + } + } + + # base url is needed for media/images and relative links (e.g. if www.mydomain.com/mywebsite) + $this->baseUrl = $baseUrl; + + # math support + $this->BlockTypes['\\'][] = 'Math'; + $this->BlockTypes['$'][] = 'Math'; + + $this->InlineTypes['\\'][] = 'Math'; + $this->InlineTypes['$'][] = 'Math'; + $this->InlineTypes['['][] = 'Shortcode'; + $this->inlineMarkerList .= '\\'; + $this->inlineMarkerList .= '$'; + + $this->BlockTypes['!'][] = 'Image'; + $this->BlockTypes['!'][] = "Notice"; + + $this->visualMode = false; + + # identify Shortcodes after footnotes and links + array_unshift($this->BlockTypes['['], 'Shortcode'); + + # identify Table Of contents after footnotes and links and shortcodes + array_unshift($this->BlockTypes['['], 'TableOfContents'); + } + + public function extendLinksWhitelist($linktypes) + { + /* + if($linktypes) + { + $this->safeLinksWhitelist[] = ; + } + */ + } + + public function setVisualMode() + { + $this->visualMode = true; + } + + public function text($text, $relurl = null) + { + $Elements = $this->textElements($text); + + return $Elements; + } + + public function markup($Elements) + { + # convert to markup + $markup = $this->elements($Elements); + + # trim line breaks + $markup = trim($markup, "\n"); + + # merge consecutive dl elements + $markup = preg_replace('/<\/dl>\s+
]*>\[TOC\]
)%i', $TOC, $markup); + } + + # add footnotes + if (isset($this->DefinitionData['Footnote'])) + { + $Element = $this->buildFootnoteElement(); + + $markup .= "\n" . $this->element($Element); + } + return $markup; + } + + protected $imageAttributes = true; + + public function withoutImageAttributes() + { + $this->imageAttributes = false; + } + + # BlockImages with html5 figure and figcaption + # No, this is not the most elegant code on planet earth!! + protected function blockImage($line, $block) + { + if (preg_match('/^\!\[/', $line['text'], $matches)) + { + + $Block = array( + 'element' => array( + 'name' => 'figure', + 'elements' => array( + ) + ), + ); + + $Elements = array( + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $line['text'], + 'destination' => 'elements', + ) + ); + + if (preg_match('/[ ]*{('.$this->regexAttribute.'+)}/', $line['text'], $matches, PREG_OFFSET_CAPTURE)) + { + $attributeString = $matches[1][0]; + $dataAttributes = $this->parseAttributeData($attributeString); + + # move classes and ids from img to the figure element + $figureAttributes = array(); + if(isset($dataAttributes['class'])) + { + $figureAttributes['class'] = $dataAttributes['class']; + $classes = explode(' ', $dataAttributes['class']); + foreach($classes as $class) + { + $attributeString = str_replace('.'.$class, '', $attributeString); + } + } + if(isset($dataAttributes['id'])) + { + $figureAttributes['id'] = $dataAttributes['id']; + $attributeString = str_replace('#'.$dataAttributes['id'], '', $attributeString); + } + + $attributeString = trim(str_replace(' ', ' ', $attributeString)); + $line['text'] = substr($line['text'], 0, $matches[0][1]); + if(str_replace(' ', '', $attributeString) != '' && $this->imageAttributes) + { + $line['text'] .= '{' . $attributeString . '}'; + } + + $Block['element']['attributes'] = $figureAttributes; + + $Elements['handler']['argument'] = $line['text']; + } + + $Block['element']['elements'][] = $Elements; + + return $Block; + } + } + + protected function blockImageContinue($line, $block) + { + if (isset($block['complete'])) + { + return; + } + + # A blank newline has occurred, so it is a new content-block and not a caption + if (isset($block['interrupted'])) + { + return; + } + + $block['element']['elements'][] = array( + 'name' => 'figcaption', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $line['text'], + 'destination' => 'elements', + ) + ); + + $block['complete'] = true; + + return $block; + } + + protected function blockImageComplete($block) + { + return $block; + } + + protected function inlineImage($excerpt) + { + $image = parent::inlineImage($excerpt); + + if ( ! isset($image)) + { + return null; + } + + $image['element']['attributes']['loading'] = "lazy"; + + return $image; + } + + protected function blockTable($Line, array $Block = null) + { + + $Block = parent::blockTable($Line, $Block); + + if($Block) + { + $table = $Block['element']; + + $Block['element'] = [ + 'name' => 'div', + 'element' => $table, + 'attributes' => [ + 'class' => "tm-table", + ], + ]; + } + + return $Block; + } + + protected function blockTableContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|')) + { + $Elements = array(); + + $row = $Line['text']; + + $row = trim($row); + $row = trim($row, '|'); + + preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches); + + $cells = array_slice($matches[0], 0, count($Block['alignments'])); + + foreach ($cells as $index => $cell) + { + $cell = trim($cell); + + $Element = array( + 'name' => 'td', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $cell, + 'destination' => 'elements', + ) + ); + + if (isset($Block['alignments'][$index])) + { + $Element['attributes'] = array( + 'style' => 'text-align: ' . $Block['alignments'][$index] . ';', + ); + } + + $Elements []= $Element; + } + + $Element = array( + 'name' => 'tr', + 'elements' => $Elements, + ); + + $Block['element']['element']['elements'][1]['elements'] []= $Element; + + return $Block; + } + } + + # Handle notice blocks + # adopted from grav: https://github.com/getgrav/grav-plugin-markdown-notices/blob/develop/markdown-notices.php + # and yellow / datenstrom: https://raw.githubusercontent.com/datenstrom/yellow-extensions/master/features/markdown/markdownx.php + protected function blockNotice($Line, $Block) + { + if (preg_match("/^!(?!\[)[ ]?+(.*+)/", $Line["text"], $matches)) + { + $level = strspn(str_replace(array("![", " "), "", $Line["text"]), "!"); + $text = substr($matches[0], $level); + + $Block = [ + 'element' => [ + 'name' => 'div', + 'handler' => array( + 'function' => 'linesElements', + 'argument' => (array) $text, + 'destination' => 'elements', + ), + 'attributes' => [ + 'class' => "notice$level", + ], + ], + ]; + + return $Block; + } + } + + # Handle notice blocks over multiple lines + # adopted from grav: https://github.com/getgrav/grav-plugin-markdown-notices/blob/develop/markdown-notices.php + # and yellow / datenstrom: https://raw.githubusercontent.com/datenstrom/yellow-extensions/master/features/markdown/markdownx.php + protected function blockNoticeContinue($Line, $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if (preg_match("/^!(?!\[)[ ]?+(.*+)/", $Line["text"], $matches) ) + { + $level = strspn(str_replace(array("![", " "), "", $Line["text"]), "!"); + $text = substr($matches[0], $level); + + $Block['element']['handler']['argument'][] = $text; + return $Block; + } + } + + + # Headlines + public $headlines = array(); + + protected function blockHeader($Line) + { + if (isset($Line['text'][1])) + { + $level = strspn($Line['text'], '#'); + + if ($level > 6) + { + return; + } + + $text = trim($Line['text'], '#'); + $headline = Slug::createSlug($Line['text'], $this->settings); + + if ($this->strictMode and isset($text[0]) and $text[0] !== ' ') + { + return; + } + + $text = trim($text, ' '); + + $tocText = $text; + + if($this->showAnchor && $level > 1) + { + $text = "[#](#h-$headline){.tm-heading-anchor}" . $text; + } + + $Block = array( + 'element' => array( + 'name' => 'h' . min(6, $level), + 'attributes' => array( + 'id' => "h-$headline" + ), + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $text, + 'destination' => 'elements', + ), + ) + ); + + $this->headlines[] = array('level' => $level, 'name' => $Block['element']['name'], 'attribute' => $Block['element']['attributes']['id'], 'text' => $tocText); + + return $Block; + } + } + + + # TableOfContents + protected function blockTableOfContents($line, $block) + { + if ($line['text'] == '[TOC]') + { + $this->DefinitionData['TableOfContents'] = true; + } + } + + + # build the markup for table of contents + public function buildTOC($headlines) + { + $markup = 'No shortcode found.
'; + } + else + { + $html = 'No shortcode found.'; + } + } + + return array( + 'element' => array( + 'rawHtml' => $html, + 'allowRawHtmlInSafeMode' => true, + ), + 'extent' => strlen($matches[0]), + ); + } + + protected function inlineLink($Excerpt) + { + $Element = array( + 'name' => 'a', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => null, + 'destination' => 'elements', + ), + 'nonNestables' => array('Url', 'Link'), + 'attributes' => array( + 'href' => null, + 'title' => null, + ), + ); + + $extent = 0; + + $remainder = $Excerpt['text']; + + if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) + { + $Element['handler']['argument'] = $matches[1]; + + $extent += strlen($matches[0]); + + $remainder = substr($remainder, $extent); + } + else + { + return; + } + + if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches)) + { + # start typemill: if relative link or media-link + $href = $matches[1]; + if($href[0] == '/') + { + $href = $this->baseUrl . $href; + } + elseif(substr( $href, 0, 6 ) === "media/") + { + $href = $this->baseUrl . '/' . $href; + } + # end typemill + + $Element['attributes']['href'] = $href; + + if (isset($matches[2])) + { + $Element['attributes']['title'] = substr($matches[2], 1, - 1); + } + + $extent += strlen($matches[0]); + } + else + { + if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) + { + $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument']; + $definition = strtolower($definition); + + $extent += strlen($matches[0]); + } + else + { + $definition = strtolower($Element['handler']['argument']); + } + + if ( ! isset($this->DefinitionData['Reference'][$definition])) + { + return; + } + + $Definition = $this->DefinitionData['Reference'][$definition]; + + $Element['attributes']['href'] = $Definition['url']; + $Element['attributes']['title'] = $Definition['title']; + } + + $Link = array( + 'extent' => $extent, + 'element' => $Element, + ); + + # Parsedown Extra + $remainder = $Link !== null ? substr($Excerpt['text'], $Link['extent']) : ''; + + if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches)) + { + $Link['element']['attributes'] += $this->parseAttributeData($matches[1]); + + $Link['extent'] += strlen($matches[0]); + } + + return $Link; + + } + + # advanced attribute data, check parsedown extra plugin: https://github.com/tovic/parsedown-extra-plugin + protected function parseAttributeData($text) { + + // Allow compact attributes ... + $text = str_replace(array('#', '.'), array(' #', ' .'), $text); + if (strpos($text, '="') !== false || strpos($text, '=\'') !== false) { + $text = preg_replace_callback('#([-\w]+=)(["\'])([^\n]*?)\2#', function($m) { + $s = str_replace(array( + ' #', + ' .', + ' ' + ), array( + '#', + '.', + "\x1A" + ), $m[3]); + return $m[1] . $m[2] . $s . $m[2]; + }, $text); + } + $attrs = array(); + foreach (explode(' ', $text) as $v) { + if (!$v) continue; + // `{#foo}` + if ($v[0] === '#' && isset($v[1])) { + $attrs['id'] = substr($v, 1); + // `{.foo}` + } else if ($v[0] === '.' && isset($v[1])) { + $attrs['class'][] = substr($v, 1); + // ~ + } else if (strpos($v, '=') !== false) { + $vv = explode('=', $v, 2); + // `{foo=}` + if ($vv[1] === "") { + $attrs[$vv[0]] = ""; + // `{foo="bar baz"}` + // `{foo='bar baz'}` + } else if ($vv[1][0] === '"' && substr($vv[1], -1) === '"' || $vv[1][0] === "'" && substr($vv[1], -1) === "'") { + $attrs[$vv[0]] = str_replace("\x1A", ' ', substr(substr($vv[1], 1), 0, -1)); + // `{foo=bar}` + } else { + $attrs[$vv[0]] = $vv[1]; + } + // `{foo}` + } else { + $attrs[$v] = $v; + } + } + if (isset($attrs['class'])) { + $attrs['class'] = implode(' ', $attrs['class']); + } + return $attrs; + } + + protected $regexAttribute = '(?:[#.][-\w:\\\]+[ ]*|[-\w:\\\]+(?:=(?:["\'][^\n]*?["\']|[^\s]+)?)?[ ]*)'; + + # ++ + # blocks that belong to a "magneticType" would "merge" if they are next to each other + protected $magneticTypes = array('DefinitionList', 'Footnote'); + + public function markdownToArrayBlocks($markdown) + { + # make sure no definitions are set + $this->DefinitionData = array(); + + # standardize line breaks + $text = str_replace(array("\r\n", "\r"), "\n", $markdown); + + # remove surrounding line breaks + $text = trim($text, "\n"); + + # split text into lines + $lines = explode("\n", $text); + + # manipulated method linesElements + + # $Elements = array(); + + # the block that's being built + # when a block is complete we add it to $Elements + $CurrentBlock = null; + + # ++ + $done = array(); + $current = null; + + foreach ($lines as $line) { + + # is it a blank line + if (chop($line) === '') { + # mark current block as interrupted + if (isset($CurrentBlock)) { + # set or increment interrupted + $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted']) + ? $CurrentBlock['interrupted'] + 1 : 1 + ); + } + + # keep empty lines in pre-tags + if($CurrentBlock['type'] == 'FencedCode' && isset($current['text'])) + { + $current['text'] .= "\n"; + } + continue; + } + + # ~ + + # figure out line indent and text + + while (($beforeTab = strstr($line, "\t", true)) !== false) { + $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4; + + $line = $beforeTab + . str_repeat(' ', $shortage) + . substr($line, strlen($beforeTab) + 1); + } + + $indent = strspn($line, ' '); + + $text = $indent > 0 ? substr($line, $indent) : $line; + + # ~ + + $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); + + # ~ + + if (isset($CurrentBlock['continuable'])) { + # current block is continuable + # let's attempt to continue it + $methodName = 'block' . $CurrentBlock['type'] . 'Continue'; + $Block = $this->$methodName($Line, $CurrentBlock); + + if (isset($Block)) { + # attempt to continue was successful + # let's update it + $CurrentBlock = $Block; + + # ++ + $current['text'] .= "\n$line"; + + # move to next line + continue; + } else { + # attempt to continue failed + # this means current block is complete + # let's call its "complete" method if it has one + if ($this->isBlockCompletable($CurrentBlock['type'])) { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); + } + } + } + + # ~ + + # current block failed to "eat" current line + # let's see if we can start a new block + $marker = $text[0]; + + # ~ + + # make a list of the block types that current line can start + $blockTypes = $this->unmarkedBlockTypes; + if (isset($this->BlockTypes[$marker])) { + foreach ($this->BlockTypes[$marker] as $blockType) { + $blockTypes [] = $blockType; + } + } + + # + # ~ + + foreach ($blockTypes as $blockType) { + # let's see if current line can start a block of type $blockType + $Block = $this->{"block$blockType"}($Line, $CurrentBlock); + + if (isset($Block)) { + # echo "[$blockType]"; + # current line managed to start a block of type $blockType + # let's set its type + $Block['type'] = $blockType; + + # on start block, we "ship" current block and flag started block as identified + # except when the started block has already flagged itself as identified + # this is the case of table + # blocks flag themselves as identified to "absorb" current block + # setext function doesn't set "identified" but it inherits it from the $Block param + if (!isset($Block['identified'])) { + # if (isset($CurrentBlock)) { + # $Elements[] = $this->extractElement($CurrentBlock); + # } + + # ++ + # $current would be null if this is the first block + if ($current !== null) { + $done[] = $current; + } + + # ++ + # line doesn't belong to $current + $current = ['text' => $line, 'type' => $blockType]; + + $Block['identified'] = true; + } else { + # ++ + $current['text'] .= "\n$line"; + $current['type'] = $blockType; + } + + # does block have a "continue" method + if ($this->isBlockContinuable($blockType)) { + $Block['continuable'] = true; + } + + $CurrentBlock = $Block; + + # we're done with this line + # move on to next line + continue 2; + } + } + + # ~ + + if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph') { + # we continue paragraphs here because they are "lazy" + # they "eat" the line only if no other block type has "eaten" it + $Block = $this->paragraphContinue($Line, $CurrentBlock); + } + + if (isset($Block)) { + $CurrentBlock = $Block; + + # ++ + $current['text'] .= "\n$line"; + } else { + # is this "isset" might be here to handle $lines[0] (first line) + # version 1.7.x doesn't have it but it does unset($Blocks[0]) + if (isset($CurrentBlock)) { + # $Elements[] = $this->extractElement($CurrentBlock); + + # ++ + $done[] = $current; + } + + $CurrentBlock = $this->paragraph($Line); + + # ++ + $current = ['text' => $line, 'type' => 'Paragraph']; + + $CurrentBlock['identified'] = true; + } + } + + # ~ + + # at this point, we're out of the $lines loop + + # handles the case where the last block is continuable + # since there are no more lines, it won't get completed in the loop + # we need to complete it here + if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); + } + + # ~ + + if (isset($CurrentBlock)) { + # $Elements[] = $this->extractElement($CurrentBlock); + + # ++ + $done[] = $current; + } + + # ~ + + # ++ + # merge blocks that have magnetic types + $done = array_reduce($done, function (array $accumulator, array $current) { + if ($accumulator) { + $last = array_pop($accumulator); + + if ($current['type'] === $last['type'] and in_array($current['type'], $this->magneticTypes)) { + $last['text'] .= "\n\n" . $current['text']; + $accumulator[] = $last; + } else { + $accumulator[] = $last; + $accumulator[] = $current; + } + } else { + # first iteration + $accumulator[] = $current; + } + + return $accumulator; + }, []); + + # ~ + + # return $Elements; + + # ++ + # return just the text of each item + return array_map(function (array $item) { + return $item['text']; + }, $done); + } + + public function arrayBlocksToMarkdown(array $arrayBlocks) + { + $markdown = ''; + + foreach($arrayBlocks as $block) + { + $markdown .= $block . "\n\n"; + } + + return $markdown; + } + + protected function isComplete($codeblock) + { + $lines = explode("\n", $codeblock); + if(count($lines) > 1) + { + $lastLine = array_pop($lines); + if(substr($lastLine,0,2) == '``') + { + return true; + } + return false; + } + return false; + } +} \ No newline at end of file diff --git a/system/typemill/Models/Content.php b/system/typemill/Models/Content.php new file mode 100644 index 0000000..ee7f1de --- /dev/null +++ b/system/typemill/Models/Content.php @@ -0,0 +1,222 @@ +storage = new StorageWrapper('\Typemill\Models\Storage'); + $this->parsedown = new ParsedownExtension($baseurl); + } + + public function getDraftMarkdown($item) + { + # needed for ToC links + # $relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel; + + # to fix footnote-logic in parsedown, set visual mode to true + # $this->parsedown->setVisualMode(); + + # make sure you get the txt version unless page is published +# $filetype = ($item->status == 'published') ? '.md' : '.txt'; + + $filetype = '.txt'; + + $markdown = $this->storage->getFile('contentFolder', '', $item->pathWithoutType . $filetype); + + if(!$markdown) + { + $filetype = '.md'; + $markdown = $this->storage->getFile('contentFolder', '', $item->pathWithoutType . $filetype); + } + + # if !$mardkown ? + + if($markdown == '') + { + $markdownArray = []; + } + elseif($filetype == '.txt') + { + $markdownArray = json_decode($markdown); + } + else + { + $markdownArray = $this->parsedown->markdownToArrayBlocks($markdown); + } + + return $markdownArray; + } + + public function saveDraftMarkdown($item, $markdown) + { + $markdown = json_encode($markdown); + + if($this->storage->writeFile('contentFolder', '', $item->pathWithoutType . '.txt', $markdown)) + { + return true; + } + + return $this->storage->getError(); + } + + public function publishMarkdown($item, array $markdownArray) + { + $markdown = $this->parsedown->arrayBlocksToMarkdown($markdownArray); + + if($this->storage->writeFile('contentFolder', '', $item->pathWithoutType . '.md', $markdown)) + { + $this->storage->deleteFile('contentFolder', '', $item->pathWithoutType . '.txt'); + + return true; + } + + return $this->storage->getError(); + } + + public function unpublishMarkdown($item, array $markdownArray) + { + $markdown = json_encode($markdownArray); + + if($this->storage->writeFile('contentFolder', '', $item->pathWithoutType . '.txt', $markdown)) + { + $this->storage->deleteFile('contentFolder', '', $item->pathWithoutType . '.md'); + + return true; + } + + return $this->storage->getError(); + } + + public function deleteDraft($item) + { + if($this->storage->deleteFile('contentFolder', '', $item->pathWithoutType . '.txt')) + { + return true; + } + + return $this->storage->getError(); + } + + public function deletePage($item) + { + $extensions = ['.md', '.txt', '.yaml']; + + $result = true; + foreach($extensions as $extension) + { + $result = $this->storage->deleteFile('contentFolder', '', $item->pathWithoutType . $extension); + } + + if($result) + { + return true; + } + + return $this->storage->getError(); + } + + public function addDraftHtml($markdownArray) + { + $content = []; + + $toc_id = false; + + foreach($markdownArray as $key => $markdown) + { + if($markdown == "[TOC]") + { + $toc_id = $key; + } + + $contentArray = $this->parsedown->text($markdown); + $html = $this->parsedown->markup($contentArray); + + $content[$key] = [ + 'id' => $key, + 'markdown' => $markdown, + 'html' => $html + ]; + } + + if($toc_id) + { + # generate the toc markup + $tocMarkup = $this->parsedown->buildTOC($this->parsedown->headlines); + + # add to content html + $content[$toc_id]['html'] = $tocMarkup; + } + + return $content; + } + + public function getDraftHtml($markdownArray) + { + foreach($markdownArray as $key => $block) + { + # parse markdown-file to content-array + $contentArray = $this->parsedown->text($block); + + # parse markdown-content-array to content-string + $content[$key] = $this->parsedown->markup($contentArray); + } + + return $content; + } + + public function arrayBlocksToMarkdown($arrayBlocks) + { + $markdown = ''; + + foreach($arrayBlocks as $block) + { + $markdown .= $block . "\n\n"; + } + + return $markdown; + } + + public function generateToc($content, $relurl) + { + # we assume that page has no table of content + $toc = false; + + # needed for ToC links + $relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel; + + # loop through mardkown-array and create html-blocks + foreach($content as $key => $block) + { + # parse markdown-file to content-array + $contentArray = $parsedown->text($block); + + if($block == '[TOC]') + { + # toc is true and holds the key of the table of content now + $toc = $key; + } + + # parse markdown-content-array to content-string + $content[$key] = ['id' => $key, 'html' => $parsedown->markup($contentArray)]; + } + + # if page has a table of content + if($toc) + { + # generate the toc markup + $tocMarkup = $parsedown->buildTOC($parsedown->headlines); + + # toc holds the id of the table of content and the html-markup now + $toc = ['id' => $toc, 'html' => $tocMarkup]; + } + + return $toc; + } +} \ No newline at end of file diff --git a/system/typemill/Models/Extension.php b/system/typemill/Models/Extension.php new file mode 100644 index 0000000..a2a8aa4 --- /dev/null +++ b/system/typemill/Models/Extension.php @@ -0,0 +1,127 @@ +storage = new StorageWrapper('\Typemill\Models\Storage'); + } + + public function getThemeDetails() + { + $themes = $this->getThemes(); + + $themeDetails = []; + foreach($themes as $themeName) + { + $themeDetails[$themeName] = $this->getThemeDefinition($themeName); + } + + return $themeDetails; + } + + public function getThemeSettings($themes) + { + $themeSettings = []; + foreach($themes as $themename => $themeinputs) + { + $themeSettings[$themename] = $themeinputs; + $themeSettings[$themename]['customcss'] = $this->storage->getFile('cacheFolder', '', $themename . '-custom.css'); + } + + return $themeSettings; + } + + public function getThemes() + { + $themeFolder = $this->storage->getFolderPath('themeFolder'); + $themeFolderC = scandir($themeFolder); + $themes = []; + foreach ($themeFolderC as $key => $theme) + { + if (!in_array($theme, [".",".."])) + { + if (is_dir($themeFolder . DIRECTORY_SEPARATOR . $theme)) + { + $themes[] = $theme; + } + } + } + + return $themes; + } + + public function getThemeDefinition($themeName) + { + $themeSettings = $this->storage->getYaml('themeFolder', $themeName, $themeName . '.yaml'); + + # add standard-textarea for custom css + $themeSettings['forms']['fields']['customcss'] = [ + 'type' => 'codearea', + 'label' => 'Custom CSS', + 'class' => 'codearea', + 'description' => 'You can overwrite the theme-css with your own css here.' + ]; + + # add image preview file + $themeSettings['preview'] = 'http://localhost/typemill/themes/' . $themeName . '/' . $themeName . '.png'; + + return $themeSettings; + } + + public function getPluginDetails() + { + $plugins = $this->getPlugins(); + + $pluginDetails = []; + foreach($plugins as $pluginName) + { + $pluginDetails[$pluginName] = $this->getPluginDefinition($pluginName); + } + + return $pluginDetails; + } + + public function getPluginSettings($plugins) + { + $pluginSettings = []; + foreach($plugins as $pluginname => $plugininputs) + { + $pluginSettings[$pluginname] = $plugininputs; + } + + return $pluginSettings; + } + + public function getPlugins() + { + $pluginFolder = $this->storage->getFolderPath('pluginFolder'); + $pluginFolderC = scandir($pluginFolder); + $plugins = []; + foreach ($pluginFolderC as $key => $plugin) + { + if (!in_array($plugin, [".",".."])) + { + if (is_dir($pluginFolder . DIRECTORY_SEPARATOR . $plugin)) + { + $plugins[] = $plugin; + } + } + } + + return $plugins; + } + + public function getPluginDefinition($pluginName) + { + $pluginSettings = $this->storage->getYaml('pluginFolder', $pluginName, $pluginName . '.yaml'); + + return $pluginSettings; + } +} \ No newline at end of file diff --git a/system/typemill/Models/Folder.php b/system/typemill/Models/Folder.php index 158190f..307b49d 100644 --- a/system/typemill/Models/Folder.php +++ b/system/typemill/Models/Folder.php @@ -161,16 +161,13 @@ class Folder } elseif($name) { - # do not use files in base folder (only folders are allowed) - # if(!isset($keyPath)) continue; - # do not use index files if($name == 'index.md' || $name == 'index.txt' || $name == 'index.txtmd' ) continue; $nameParts = $this->getStringParts($name); $fileType = array_pop($nameParts); $nameWithoutType = $this->getNameWithoutType($name); - + if($fileType == 'md') { $status = 'published'; @@ -255,6 +252,7 @@ class Folder public function getHomepageItem($baseUrl) { + die('folder model: getHomepageItem moved to navigation model'); # return a standard item-object $item = new \stdClass; @@ -284,6 +282,8 @@ class Folder public function getItemForUrl($folderContentDetails, $url, $baseUrl, $result = NULL, $home = NULL ) { + die('folder model: getItemForUrl. Is it in use?'); + # if we are on the homepage if($home) { diff --git a/system/typemill/Models/License.php b/system/typemill/Models/License.php index 29c1dbb..f662716 100644 --- a/system/typemill/Models/License.php +++ b/system/typemill/Models/License.php @@ -69,6 +69,15 @@ class License } + + public function getLicenseFields() + { + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $licensefields = $storage->getYaml('systemSettings', '', 'license.yaml'); + + return $licensefields; + } + # check the local licence file (like pem or pub) private function checkLicense() { diff --git a/system/typemill/Models/Navigation.php b/system/typemill/Models/Navigation.php index 30aa554..d733729 100644 --- a/system/typemill/Models/Navigation.php +++ b/system/typemill/Models/Navigation.php @@ -42,16 +42,18 @@ class Navigation extends Folder $this->extendedNaviName = 'navi-extended.txt'; } + # use array ['extended' => true, 'draft' => true, 'live' => true] to clear files public function clearNavigation(array $deleteitems = NULL ) { - # clear cache - $this->extendedNavigation = false; - $this->draftNavigation = false; - $this->basicDraftNavigation = false; - $this->liveNavigation = false; - $this->basicLiveNavigation = false; + $result = false; + + # 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, @@ -107,13 +109,49 @@ class Navigation extends Folder return $allowedmainnavi; } + public function getSystemNavigation($userrole, $acl, $urlinfo) + { + $systemnavi = $this->storage->getYaml('systemSettings', '', 'systemnavi.yaml'); +# $systemnavi = $this->c->get('dispatcher')->dispatch(new OnSystemnaviLoaded($systemnavi), 'onSystemnaviLoaded')->getData(); + + $allowedsystemnavi = []; + + foreach($systemnavi as $name => $naviitem) + { + # check if the navi-item is active (e.g if segments like "content" or "system" is in current url) + # a bit fragile because url-segment and name/key in systemnavi.yaml and plugins have to be the same + if(strpos($urlinfo['route'], 'tm/' . $name)) + { + $naviitem['active'] = true; + } + + if($acl->isAllowed($userrole, $naviitem['aclresource'], $naviitem['aclprivilege'])) + { + $allowedsystemnavi[$name] = $naviitem; + } + } + + return $allowedsystemnavi; + } + # get the navigation with draft files for author environment public function getDraftNavigation($urlinfo, $language, $userrole = null, $username = null) { # todo: filter for userrole or username +# $this->clearNavigation(['extended' => true, 'draft' => true, 'live' => true]); + $this->draftNavigation = $this->storage->getFile('dataFolder', $this->naviFolder, $this->draftNaviName, 'unserialize'); + + +/* echo ''; + $draftContentTree = $this->scanFolder($this->storage->getFolderPath('contentFolder'), true); + $draftNavigation = $this->getFolderContentDetails($draftContentTree, $language, $urlinfo['baseurl'], $urlinfo['basepath']); + print_r($draftNavigation); + die(); +*/ + if($this->draftNavigation) { return $this->draftNavigation; @@ -201,18 +239,22 @@ class Navigation extends Folder { # read file $meta = $this->storage->getYaml($contentFolder, '', $filename); - - $extended[$item->urlRelWoF]['navtitle'] = isset($meta['meta']['navtitle']) ? $meta['meta']['navtitle'] : ''; - $extended[$item->urlRelWoF]['hide'] = isset($meta['meta']['hide']) ? $meta['meta']['hide'] : false; - $extended[$item->urlRelWoF]['noindex'] = isset($meta['meta']['noindex']) ? $meta['meta']['noindex'] : false; - $extended[$item->urlRelWoF]['path'] = $item->path; - $extended[$item->urlRelWoF]['keyPath'] = $item->keyPath; } else { - # should we create the yaml file here if it does not exist? + # create initial yaml + $meta = []; + $meta['meta']['navtitle'] = $item->name; + + $this->storage->updateYaml('contentFolder', '', $filename, $meta); } + $extended[$item->urlRelWoF]['navtitle'] = isset($meta['meta']['navtitle']) ? $meta['meta']['navtitle'] : ''; + $extended[$item->urlRelWoF]['hide'] = isset($meta['meta']['hide']) ? $meta['meta']['hide'] : false; + $extended[$item->urlRelWoF]['noindex'] = isset($meta['meta']['noindex']) ? $meta['meta']['noindex'] : false; + $extended[$item->urlRelWoF]['path'] = $item->path; + $extended[$item->urlRelWoF]['keyPath'] = $item->keyPath; + if ($item->elementType == 'folder') { $extended = $this->createExtendedNavigation($item->folderContent, $extended); @@ -247,10 +289,16 @@ class Navigation extends Folder return $mergedNavigation; } - public function getItemWithKeyPath($navigation, array $searchArray) + public function getItemWithKeyPath($navigation, array $searchArray, $baseUrl = null) { $item = false; + # if it is the homepage + if(isset($searchArray[0]) && $searchArray[0] == '') + { + return $this->getHomepageItem($baseUrl); + } + foreach($searchArray as $key => $itemKey) { $item = isset($navigation[$itemKey]) ? clone($navigation[$itemKey]) : false; @@ -265,25 +313,6 @@ class Navigation extends Folder return $item; } - public function getItemForUrlFrontend($folderContentDetails, $url, $result = NULL) - { - foreach($folderContentDetails as $key => $item) - { - # set item active, needed to move item in navigation - if($item->urlRelWoF === $url) - { - $item->active = true; - $result = $item; - } - elseif($item->elementType === "folder") - { - $result = $this->getItemForUrlFrontend($item->folderContent, $url, $result); - } - } - - return $result; - } - public function setActiveNaviItems($navigation, array $searchArray) { foreach($searchArray as $key => $itemKey) @@ -312,6 +341,41 @@ class Navigation extends Folder return $navigation; } + public function getHomepageItem($baseUrl) + { +# $live = $this->storage->getFile('contentFolder', '', 'index.md'); + $draft = $this->storage->getFile('contentFolder', '', 'index.txt'); + + # return a standard item-object + $item = new \stdClass; + + $item->status = $draft ? 'modified' : 'published'; + $item->originalName = 'home'; + $item->elementType = 'folder'; + $item->fileType = $draft ? 'mdtxt' : 'md'; + $item->order = false; + $item->name = 'home'; + $item->slug = ''; + $item->path = ''; + $item->pathWithoutType = DIRECTORY_SEPARATOR . 'index'; + $item->key = false; + $item->keyPath = ''; + $item->keyPathArray = ['']; + $item->chapter = false; + $item->urlRel = '/'; + $item->urlRelWoF = '/'; + $item->urlAbs = $baseUrl; + $item->active = true; + $item->activeParent = false; + $item->hide = false; + + return $item; + } + + + + + ############################## TODO # reads the cached structure with published pages diff --git a/system/typemill/Models/ProcessAssets.php b/system/typemill/Models/ProcessAssets.php index 448e7b3..e7c647a 100644 --- a/system/typemill/Models/ProcessAssets.php +++ b/system/typemill/Models/ProcessAssets.php @@ -3,15 +3,24 @@ namespace Typemill\Models; use Typemill\Models\Folder; +use Typemill\Static\Slug; + class ProcessAssets -{ - # holds the path to the temporary image folder - public $basepath = false; +{ + public $errors = []; - public $tmpFolder = false; + public $basepath = false; - public $errors = []; + public $tmpFolder = false; + + public $extension = false; + + public $filename = false; + + public $filetype = false; + + public $filedata = false; public function __construct() { @@ -50,6 +59,117 @@ class ProcessAssets return $result; } + # set the pathinfo (name and extension) and slugify a unique name if option to overwrite existing files is false + public function setPathInfo(string $name) + { + $pathinfo = pathinfo($name); + if(!$pathinfo) + { + $this->errors[] = 'Could not read pathinfo.'; + + return false; + } + + $this->extension = isset($pathinfo['extension']) ? strtolower($pathinfo['extension']) : false; + $this->filename = Slug::createSlug($pathinfo['filename']); + + if(!$this->extension OR !$this->filename) + { + $this->errors[] = 'Extension or filename are missing.'; + + return false; + } + + return true; + } + + public function decode(string $file) + { + $fileParts = explode(";base64,", $file); + $fileType = explode("/", $fileParts[0]); + $fileData = base64_decode($fileParts[1]); + + $fileParts = explode(";base64,", $file); + + if(!isset($fileParts[0]) OR !isset($fileParts[1])) + { + $this->errors[] = 'Could not decode image or file, probably not a base64 encoding.'; + + return false; + } + + $type = explode("/", $fileParts[0]); + $this->filetype = strtolower($fileType[0]); + $this->filedata = base64_decode($fileParts[1]); + + return true; + } + + public function getExtension() + { + return $this->extension; + } + + public function getFiletype() + { + return $this->filetype; + } + + public function getFilename() + { + return $this->filename; + } + + public function setFilename($filename) + { + $this->filename = $filename; + } + + public function getFullName() + { + return $this->filename . '.' . $this->extension; + } + + public function getFiledata() + { + return $this->filedata; + } + + public function getFullPath() + { + return $this->tmpFolder . $this->filename . '.' . $this->extension; + } + + public function formatSizeUnits($bytes) + { + if ($bytes >= 1073741824) + { + $bytes = number_format($bytes / 1073741824, 2) . ' GB'; + } + elseif ($bytes >= 1048576) + { + $bytes = number_format($bytes / 1048576, 2) . ' MB'; + } + elseif ($bytes >= 1024) + { + $bytes = number_format($bytes / 1024, 2) . ' KB'; + } + elseif ($bytes > 1) + { + $bytes = $bytes . ' bytes'; + } + elseif ($bytes == 1) + { + $bytes = $bytes . ' byte'; + } + else + { + $bytes = '0 bytes'; + } + + return $bytes; + } + /* @@ -211,33 +331,4 @@ class ProcessAssets return $result; } - public function formatSizeUnits($bytes) - { - if ($bytes >= 1073741824) - { - $bytes = number_format($bytes / 1073741824, 2) . ' GB'; - } - elseif ($bytes >= 1048576) - { - $bytes = number_format($bytes / 1048576, 2) . ' MB'; - } - elseif ($bytes >= 1024) - { - $bytes = number_format($bytes / 1024, 2) . ' KB'; - } - elseif ($bytes > 1) - { - $bytes = $bytes . ' bytes'; - } - elseif ($bytes == 1) - { - $bytes = $bytes . ' byte'; - } - else - { - $bytes = '0 bytes'; - } - - return $bytes; - } } \ No newline at end of file diff --git a/system/typemill/Models/ProcessFile.php b/system/typemill/Models/ProcessFile.php index 87ba885..f1b44f1 100644 --- a/system/typemill/Models/ProcessFile.php +++ b/system/typemill/Models/ProcessFile.php @@ -2,41 +2,163 @@ namespace Typemill\Models; -class Yaml extends StorageWrapper +class ProcessFile extends ProcessAssets { - /** - * Get the a yaml file. - * @param string $fileName is the name of the Yaml Folder. - * @param string $yamlFileName is the name of the Yaml File. - */ - public function getYaml($folderName, $yamlFileName) + public function storeFile($file, $name) { - die('you are useing old class here'); - $yaml = $this->getFile($folderName, $yamlFileName); - - if($yaml) + + $this->clearTempFolder(); + + $this->setPathInfo($name); + + $this->decode($file); + + $fullpath = $this->getFullPath(); + + if($this->filedata !== false && file_put_contents($fullpath, $this->filedata)) { - return \Symfony\Component\Yaml\Yaml::parse($yaml); + $size = filesize($this->getFullPath()); + $size = $this->formatSizeUnits($size); + + $title = str_replace('-', ' ', $this->filename); + $title = $title . ' (' . strtoupper($this->extension) . ', ' . $size .')'; + + return [ + 'title' => $title, + 'name' => $this->filename, + 'extension' => $this->extension, + 'size' => $size, + 'url' => 'media/files/' . $this->getFullName() + ]; } return false; } + + + + /** - * Writes a yaml file. - * @param string $fileName is the name of the Yaml Folder. - * @param string $yamlFileName is the name of the Yaml File. - * @param array $contentArray is the content as an array. - */ - public function updateYaml($folderName, $yamlFileName, $contentArray) + * Moves the uploaded file to the upload directory. Only used for settings / NON VUE.JS uploads + * + * @param string $directory directory to which the file is moved + * @param UploadedFile $uploadedFile file uploaded file to move + * @return string filename of moved file + */ + public function moveUploadedFile(UploadedFile $uploadedFile, $overwrite = false, $name = false, $folder = NULL) { - die('you are useing old class here'); - $yaml = \Symfony\Component\Yaml\Yaml::dump($contentArray,6); - if($this->writeFile($folderName, $yamlFileName, $yaml)) + $this->setFileName($uploadedFile->getClientFilename(), 'file'); + + if($name) + { + $this->setFileName($name . '.' . $this->extension, 'file', $overwrite); + } + + $uploadedFile->moveTo($this->fileFolder . $this->getFullName()); + + return $this->getFullName(); + } + + public function publishFile() + { + $files = scandir($this->tmpFolder); + $success = true; + + foreach($files as $file) + { + if (!in_array($file, array(".",".."))) + { + $success = rename($this->tmpFolder . $file, $this->fileFolder . $file); + } + } + + return $success; + } + + + + + + + public function deleteFile($name) + { + # validate name + $name = basename($name); + + if(file_exists($this->fileFolder . $name) && unlink($this->fileFolder . $name)) { return true; } return false; } + + + public function deleteFileWithName($name) + { + # e.g. delete $name = 'logo'; + + $name = basename($name); + + if($name != '' && !in_array($name, array(".",".."))) + { + foreach(glob($this->fileFolder . $name) as $file) + { + unlink($file); + } + } + } + + + /* + * scans content of a folder (without recursion) + * vars: folder path as string + * returns: one-dimensional array with names of folders and files + */ + public function scanFilesFlat() + { + $files = scandir($this->fileFolder); + $filelist = array(); + + foreach ($files as $key => $name) + { + if (!in_array($name, array(".","..","filerestrictions.yaml")) && file_exists($this->fileFolder . $name)) + { + $filelist[] = [ + 'name' => $name, + 'timestamp' => filemtime($this->fileFolder . $name), + 'info' => pathinfo($this->fileFolder . $name), + 'url' => 'media/files/' . $name, + ]; + } + } + + $filelist = Helpers::array_sort($filelist, 'timestamp', SORT_DESC); + + return $filelist; + } + + + public function getFileDetails($name, $structure) + { + $name = basename($name); + + if (!in_array($name, array(".","..")) && file_exists($this->fileFolder . $name)) + { + $filedetails = [ + 'name' => $name, + 'timestamp' => filemtime($this->fileFolder . $name), + 'bytes' => filesize($this->fileFolder . $name), + 'info' => pathinfo($this->fileFolder . $name), + 'url' => 'media/files/' . $name, + 'pages' => $this->findPagesWithUrl($structure, $name, $result = []) + ]; + + return $filedetails; + } + + return false; + } + } \ No newline at end of file diff --git a/system/typemill/Models/ProcessImage.php b/system/typemill/Models/ProcessImage.php index 769a58e..638d52a 100644 --- a/system/typemill/Models/ProcessImage.php +++ b/system/typemill/Models/ProcessImage.php @@ -7,13 +7,12 @@ use Typemill\Static\Slug; class ProcessImage extends ProcessAssets { - protected $imgstring = false; - protected $type = false; - protected $extension = false; protected $allowedExtensions = ['png' => true, 'jpg' => true, 'jpeg' => true, 'webp' => true]; - protected $filename = false; + protected $animated = false; + protected $resizable = true; + protected $sizes = []; public function prepareImage($image, $name) @@ -73,78 +72,13 @@ class ProcessImage extends ProcessAssets return false; } - - - # decode a base64 image string from js image components - public function decode(string $image) - { - $imageParts = explode(";base64,", $image); - - if(!isset($imageParts[0]) OR !isset($imageParts[1])) - { - $this->errors[] = 'Could not decode image, probably not a base64 encoding.'; - - return false; - } - - $type = explode("/", $imageParts[0]); - $this->type = strtolower($type[0]); - $this->imgstring = base64_decode($imageParts[1]); - - return true; - } - - # set the pathinfo (name and extension) and slugify a unique name if option to overwrite existing files is false - public function setPathInfo(string $name) - { - $pathinfo = pathinfo($name); - if(!$pathinfo) - { - $this->errors[] = 'Could not read pathinfo.'; - - return false; - } - - $this->extension = isset($pathinfo['extension']) ? strtolower($pathinfo['extension']) : false; - $this->filename = Slug::createSlug($pathinfo['filename']); - - if(!$this->extension OR !$this->filename) - { - $this->errors[] = 'Extension or filename are missing.'; - - return false; - } - - return true; - } - - public function getExtension() - { - return $this->extension; - } - - public function getFilename() - { - return $this->filename; - } - - public function setFilename($filename) - { - $this->filename = $filename; - } - - public function getFullName() - { - return $this->filename . '.' . $this->extension; - } - + # add an allowed image extension like svg public function addAllowedExtension(string $extension) { $this->allowedExtensions[$extension] = true; } - # force an image type like webp public function setExtension(string $extension) { @@ -166,12 +100,12 @@ class ProcessImage extends ProcessAssets # check if image should not be resized (animated gif and svg) public function isResizable() { - if($this->type == 'gif' && $this->detectAnimatedGif()) + if($this->filetype == 'gif' && $this->detectAnimatedGif()) { $this->resizable = false; } - if($this->type == 'svg+xml') + if($this->filetype == 'svg+xml') { $this->resizable = false; } @@ -181,7 +115,7 @@ class ProcessImage extends ProcessAssets public function detectAnimatedGif() { - $is_animated = preg_match('#(\x00\x21\xF9\x04.{4}\x00\x2C.*){2,}#s', $this->imgstring); + $is_animated = preg_match('#(\x00\x21\xF9\x04.{4}\x00\x2C.*){2,}#s', $this->filedata); if ($is_animated == 1) { $this->animated = true; @@ -195,7 +129,7 @@ class ProcessImage extends ProcessAssets { $path = $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.' . $this->extension; - if(!file_put_contents($path, $this->imgstring)) + if(!file_put_contents($path, $this->filedata)) { $this->errors[] = 'could not store the image in the temporary folder'; } @@ -210,7 +144,7 @@ class ProcessImage extends ProcessAssets public function createImage() { - return imagecreatefromstring($this->imgstring); + return imagecreatefromstring($this->filedata); } public function getImageSize($image) @@ -297,6 +231,26 @@ class ProcessImage extends ProcessAssets # publish image function is moved to storage model + + + + + + + + + + + + + + + + + + + + # MOVE TO STORAGE ?? public function deleteImage($name) diff --git a/system/typemill/Models/Sitemap.php b/system/typemill/Models/Sitemap.php new file mode 100644 index 0000000..22fff6e --- /dev/null +++ b/system/typemill/Models/Sitemap.php @@ -0,0 +1,102 @@ +storage = new StorageWrapper('\Typemill\Models\Storage'); + } + + # controllerFrontendWebsite, but not in use, makes no sense to check on each page load + public function checkSitemap() + { + if(!$this->writeCache->getCache('cache', 'sitemap.xml')) + { + if(!$this->structureLive) + { + $this->setStructureLive(); + } + + $this->updateSitemap(); + } + + return true; + } + + public function updateSitemap($ping = false) + { + $sitemap = '' . "\n"; + $sitemap .= '' . "\n"; + $sitemap = $this->addUrlSet($sitemap, $this->uri->getBaseUrl()); + $sitemap .= $this->generateUrlSets($this->structureLive); + $sitemap .= ' '; + + $this->writeCache->writeFile('cache', 'sitemap.xml', $sitemap); + + if($ping && isset($this->settings['pingsitemap']) && $this->settings['pingsitemap']) + { + $sitemapUrl = $this->uri->getBaseUrl() . '/cache/sitemap.xml'; + + $pingGoogleUrl = 'http://www.google.com/ping?sitemap=' . urlencode($sitemapUrl); + $pingBingUrl = 'http://www.bing.com/ping?sitemap=' . urlencode($sitemapUrl); + + $opts = array( + 'http'=>array( + 'method'=>"GET", + 'ignore_errors' => true, + 'timeout' => 5 + ) + ); + + $context = stream_context_create($opts); + + $responseBing = file_get_contents($pingBingUrl, false, $context); + $responseGoogle = file_get_contents($pingGoogleUrl, false, $context); + } + + } + + public function generateUrlSets($navigation) + { + $urlset = ''; + + foreach($navigation as $item) + { + if($item->elementType == 'folder' && isset($item->noindex) && $item->noindex === true) + { + $urlset .= $this->generateUrlSets($item->folderContent, $urlset); + } + elseif($item->elementType == 'folder') + { + $urlset = $this->addUrlSet($urlset, $item->urlAbs); + $urlset .= $this->generateUrlSets($item->folderContent, $urlset); + } + elseif(isset($item->noindex) && $item->noindex === true ) + { + continue; + } + else + { + $urlset = $this->addUrlSet($urlset, $item->urlAbs); + } + } + return $urlset; + } + + public function addUrlSet($urlset, $url) + { + $urlset .= '' . "\n"; + $urlset .= ' ' . "\n"; + return $urlset; + } +} \ No newline at end of file diff --git a/system/typemill/Models/Storage.php b/system/typemill/Models/Storage.php index ea65052..9ede8cf 100644 --- a/system/typemill/Models/Storage.php +++ b/system/typemill/Models/Storage.php @@ -308,14 +308,123 @@ class Storage return false; } + public function createUniqueImageName($filename, $extension) + { + $defaultfilename = $filename; + + $suffix = 1; + + while(file_exists($this->originalFolder . $filename . '.' . $extension)) + { + $filename = $defaultfilename . '-' . $suffix; + $suffix++; + } + + return $filename; + } + + public function publishImage($name) + { + $pathinfo = pathinfo($name); + if(!$pathinfo) + { + $this->error = 'Could not read pathinfo.'; + + return false; + } + + $extension = isset($pathinfo['extension']) ? strtolower($pathinfo['extension']) : false; + $imagename = isset($pathinfo['filename']) ? $pathinfo['filename'] : false; + + $imagesInTmp = glob($this->tmpFolder . "*$imagename.*"); + if(empty($imagesInTmp) OR !$imagesInTmp) + { + $this->error = "We did not find the image in the tmp-folder or could not read it."; + return false; + } + + # case: image is not published yet and in tmp + foreach( $imagesInTmp as $imagepath) + { + $tmpimagename = explode("+", basename($imagepath)); + $destinationfolder = strtolower($tmpimagename[0]); + $filename = $tmpimagename[1]; + + switch($destinationfolder) + { + case 'original': + if(!rename($imagepath, $this->originalFolder . $filename)) + { + $this->error = "We could not store the original image to the original folder"; + } + break; + case 'live': + if(!rename($imagepath, $this->liveFolder . $filename)) + { + $this->error = "We could not store the live image to the live folder"; + } + break; + case 'thumbs': + if(!rename($imagepath, $this->thumbsFolder . $filename)) + { + $this->error = "We could not store the thumb to the thumb folder"; + } + break; + } + } + + if(!$this->error) + { + # return true; + return 'media/live/' . $imagename . '.' . $extension; + } + + return false; + } + + # check if an image exists in the live folder or in the original folder independent from extension + public function checkImage($imagepath) + { + $original = stripos($imagepath, '/original/'); + $live = stripos($imagepath, '/live/'); + + $pathinfo = pathinfo($imagepath); + if(!$pathinfo) + { + $this->error = 'Could not read pathinfo.'; + + return false; + } + + $extension = isset($pathinfo['extension']) ? strtolower($pathinfo['extension']) : false; + $imagename = isset($pathinfo['filename']) ? $pathinfo['filename'] : false; + $newpath = false; + + if($original) + { + $image = glob($this->originalFolder . "$imagename.*"); + if(isset($image[0])) + { + $newpath = 'media/original/' . basename($image[0]); + } + } + elseif($live) + { + $image = glob($this->liveFolder . "$imagename.*"); + if(isset($image[0])) + { + $newpath = 'media/live/' . basename($image[0]); + } + } + + return $newpath; + + } - - - - +/* public function getStorageInfoBREAK($item) @@ -518,120 +627,12 @@ class Storage - public function createUniqueImageName($filename, $extension) - { - $defaultfilename = $filename; - - $suffix = 1; - while(file_exists($this->originalFolder . $filename . '.' . $extension)) - { - $filename = $defaultfilename . '-' . $suffix; - $suffix++; - } - return $filename; - } - public function publishImage($name) - { - $pathinfo = pathinfo($name); - if(!$pathinfo) - { - $this->errors[] = 'Could not read pathinfo.'; - return false; - } - $extension = isset($pathinfo['extension']) ? strtolower($pathinfo['extension']) : false; - $imagename = isset($pathinfo['filename']) ? $pathinfo['filename'] : false; - $imagesInTmp = glob($this->tmpFolder . "*$imagename.*"); - if(empty($imagesInTmp) OR !$imagesInTmp) - { - $this->errors[] = "We did not find the image in the tmp-folder or could not read it."; - return false; - } - - # case: image is not published yet and in tmp - foreach( $imagesInTmp as $imagepath) - { - $tmpimagename = explode("+", basename($imagepath)); - $destinationfolder = strtolower($tmpimagename[0]); - $filename = $tmpimagename[1]; - - switch($destinationfolder) - { - case 'original': - if(!rename($imagepath, $this->originalFolder . $filename)) - { - $this->errors[] = "We could not store the original image to the original folder"; - } - break; - case 'live': - if(!rename($imagepath, $this->liveFolder . $filename)) - { - $this->errors[] = "We could not store the live image to the live folder"; - } - break; - case 'thumbs': - if(!rename($imagepath, $this->thumbsFolder . $filename)) - { - $this->errors[] = "We could not store the thumb to the thumb folder"; - } - break; - } - } - - if(empty($this->errors)) - { - # return true; - return 'media/live/' . $imagename . '.' . $extension; - } - - return false; - } - - # check if an image exists in the live folder or in the original folder independent from extension - public function checkImage($imagepath) - { - $original = stripos($imagepath, '/original/'); - $live = stripos($imagepath, '/live/'); - - $pathinfo = pathinfo($imagepath); - if(!$pathinfo) - { - $this->errors[] = 'Could not read pathinfo.'; - - return false; - } - - $extension = isset($pathinfo['extension']) ? strtolower($pathinfo['extension']) : false; - $imagename = isset($pathinfo['filename']) ? $pathinfo['filename'] : false; - $newpath = false; - - if($original) - { - $image = glob($this->originalFolder . "$imagename.*"); - if(isset($image[0])) - { - $newpath = 'media/original/' . basename($image[0]); - } - } - elseif($live) - { - $image = glob($this->liveFolder . "$imagename.*"); - if(isset($image[0])) - { - $newpath = 'media/live/' . basename($image[0]); - } - } - - return $newpath; - - } - -/* public function checkPath($folder) { $folderPath = $this->basepath . $folder; diff --git a/system/typemill/Models/User.php b/system/typemill/Models/User.php index 6b7de5e..b8bcdba 100644 --- a/system/typemill/Models/User.php +++ b/system/typemill/Models/User.php @@ -143,6 +143,60 @@ class User return false; } + public function getUserFields($acl, $userrole, $inspectorrole = NULL) + { + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $userfields = $storage->getYaml('systemSettings', '', 'user.yaml'); + + if(!$inspectorrole) + { + # if there is no inspector-role we assume that it is the same role like the userrole + # for example account is always visible by the same user + # edit user can be done by another user like admin. + $inspectorrole = $userrole; + } + + # if a plugin with a role has been deactivated, then users with the role throw an error, so set them back to member... + if(!$acl->hasRole($userrole)) + { + $userrole = 'member'; + } + + # dispatch fields; + #$fields = $this->c->dispatcher->dispatch('onUserfieldsLoaded', new OnUserfieldsLoaded($fields))->getData(); + + # only roles who can edit content need profile image and description + if($acl->isAllowed($userrole, 'mycontent', 'create')) + { + $newfield['image'] = ['label' => 'Profile-Image', 'type' => 'image']; + $newfield['description'] = ['label' => 'Author-Description (Markdown)', 'type' => 'textarea']; + + $userfields = array_slice($userfields, 0, 1, true) + $newfield + array_slice($userfields, 1, NULL, true); + # array_splice($fields,1,0,$newfield); + } + + # Only admin ... + if($acl->isAllowed($inspectorrole, 'user', 'write')) + { + # can change userroles + $definedroles = $acl->getRoles(); + $options = []; + + # we need associative array to make select-field with key/value work + foreach($definedroles as $role) + { + $options[$role] = $role; + } + + $userfields['userrole'] = ['label' => 'Role', 'type' => 'select', 'options' => $options]; + + # can activate api access + $userfields['apiaccess'] = ['label' => 'API access', 'checkboxlabel' => 'Activate API access for this user. Use username and password for api calls.', 'type' => 'checkbox']; + } + + return $userfields; + } + public function login() { if($this->user) diff --git a/system/typemill/Models/Validation.php b/system/typemill/Models/Validation.php index 503a806..07fb188 100644 --- a/system/typemill/Models/Validation.php +++ b/system/typemill/Models/Validation.php @@ -205,7 +205,7 @@ class Validation } /** - * validation for signup form + * validation for signin form * * @param array $params with form data. * @return obj $v the validation object passed to a result method. @@ -230,7 +230,7 @@ class Validation } /** - * validation for signup form + * validation for new user (in backoffice) * * @param array $params with form data. * @return obj $v the validation object passed to a result method. @@ -260,6 +260,7 @@ class Validation return $v->errors(); } + # change user in backoffice public function existingUser(array $params, $userroles) { $v = new Validator($params); @@ -275,50 +276,6 @@ class Validation $v->rule('emailChanged', 'email')->message("Email already taken"); $v->rule('in', 'userrole', $userroles); - return $this->validationResult($v); - } - - public function username($username) - { - $v = new Validator($username); - $v->rule('alphaNum', 'username')->message("Only alpha-numeric characters allowed"); - $v->rule('lengthBetween', 'username', 3, 20)->message("Length between 3 - 20"); - - return $this->validationResult($v); - } - - /** - * validation for changing the password - * - * @param array $params with form data. - * @return obj $v the validation object passed to a result method. - * - - public function newPassword(array $params) - { - $v = new Validator($params); - $v->rule('required', ['password', 'newpassword']); - $v->rule('lengthBetween', 'newpassword', 5, 20); - $v->rule('checkPassword', 'password')->message("Password is wrong"); - - return $this->validationResult($v); - } - */ - - /** - * validation for changing the password api case - * - * @param array $params with form data. - * @return obj $v the validation object passed to a result method. - */ - - public function newPassword(array $params) - { - $v = new Validator($params); - $v->rule('required', ['password', 'newpassword']); - $v->rule('lengthBetween', 'newpassword', 5, 20); - $v->rule('checkPassword', 'password')->message("Password is wrong"); - if($v->validate()) { return true; @@ -327,30 +284,6 @@ class Validation return $v->errors(); } - /** - * validation for password recovery - * - * @param array $params with form data. - * @return obj $v the validation object passed to a result method. - */ - - public function recoverPassword(array $params) - { - $v = new Validator($params); - $v->rule('required', ['password', 'passwordrepeat']); - $v->rule('lengthBetween', 'password', 5, 20); - $v->rule('equals', 'passwordrepeat', 'password'); - - return $this->validationResult($v); - } - - /** - * validation for changing the password api case - * - * @param array $params with form data. - * @return obj $v the validation object passed to a result method. - */ - public function newLicense(array $params) { $v = new Validator($params); @@ -382,13 +315,6 @@ 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); @@ -408,18 +334,11 @@ class Validation 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('required', ['folder_id', 'item_name', 'type']); $v->rule('regex', 'folder_id', '/^(root)|([0-9.]+)$/i'); $v->rule('navigation', 'item_name'); $v->rule('lengthBetween', 'item_name', 1, 60); @@ -435,22 +354,166 @@ class Validation } } + public function blockInput(array $params) + { + $v = new Validator($params); + + $v->rule('required', ['markdown', 'block_id', 'url']); + $v->rule('markdownSecure', 'markdown'); + $v->rule('regex', 'block_id', '/^[0-9.]+$/i'); + + if($v->validate()) + { + return true; + } + else + { + return $v->errors(); + } + } + + public function blockMove(array $params) + { + $v = new Validator($params); + + $v->rule('required', ['index_new', 'index_old', 'url']); + $v->rule('regex', 'index_new', '/^[0-9.]+$/i'); + $v->rule('regex', 'index_old', '/^[0-9.]+$/i'); + + if($v->validate()) + { + return true; + } + else + { + return $v->errors(); + } + } + + public function blockDelete(array $params) + { + $v = new Validator($params); + + $v->rule('required', ['block_id', 'url']); + $v->rule('regex', 'block_id', '/^[0-9.]+$/i'); + + if($v->validate()) + { + return true; + } + else + { + return $v->errors(); + } + } + + public function articlePublish(array $params) + { + $v = new Validator($params); + + # special conditions for startpage + if(isset($params['item_id']) && $params['item_id'] == '') + { + $v->rule('required', ['url']); + $v->rule('markdownSecure', 'markdown'); + } + else + { + $v->rule('required', ['item_id', 'url']); + $v->rule('regex', 'item_id', '/^[0-9.]+$/i'); + $v->rule('markdownSecure', 'markdown'); + } + + if($v->validate()) + { + return true; + } + else + { + return $v->errors(); + } + } + + public function usernameBREAK($username) + { + $v = new Validator($username); + $v->rule('alphaNum', 'username')->message("Only alpha-numeric characters allowed"); + $v->rule('lengthBetween', 'username', 3, 20)->message("Length between 3 - 20"); + return $this->validationResult($v); + } + + /** + * validation for changing the password + * + * @param array $params with form data. + * @return obj $v the validation object passed to a result method. + */ + + public function newPasswordOldBREAK(array $params) + { + $v = new Validator($params); + $v->rule('required', ['password', 'newpassword']); + $v->rule('lengthBetween', 'newpassword', 5, 20); + $v->rule('checkPassword', 'password')->message("Password is wrong"); + + return $this->validationResult($v); + } + + + /** + * validation for changing the password api case + * + * @param array $params with form data. + * @return obj $v the validation object passed to a result method. + */ + + public function newPasswordBREAK(array $params) + { + $v = new Validator($params); + $v->rule('required', ['password', 'newpassword']); + $v->rule('lengthBetween', 'newpassword', 5, 20); + $v->rule('checkPassword', 'password')->message("Password is wrong"); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + /** + * validation for password recovery + * + * @param array $params with form data. + * @return obj $v the validation object passed to a result method. + */ + + public function recoverPasswordBREAK(array $params) + { + $v = new Validator($params); + $v->rule('required', ['password', 'passwordrepeat']); + $v->rule('lengthBetween', 'password', 5, 20); + $v->rule('equals', 'passwordrepeat', 'password'); + + return $this->validationResult($v); + } /** * validation for system settings * * @param array $params with form data. * @return obj $v the validation object passed to a result method. - * + */ - public function settings(array $params, array $copyright, array $formats, $name = false) + public function settingsBREAK(array $params, array $copyright, array $formats, $name = false) { $v = new Validator($params); @@ -477,7 +540,7 @@ class Validation return $this->validationResult($v, $name); } - */ + /** * validation for content editor @@ -505,26 +568,6 @@ class Validation } } - public function blockInput(array $params) - { - $v = new Validator($params); - - $v->rule('required', ['markdown', 'block_id', 'url']); - $v->rule('markdownSecure', 'markdown'); - $v->rule('regex', 'block_id', '/^[0-9.]+$/i'); - - if($v->validate()) - { - return true; - } - else - { - return $v->errors(); - } - } - - - /** * validation for dynamic fields ( settings for themes and plugins) * @@ -582,7 +625,7 @@ class Validation } } break; - case "code": + case "codearea": $v->rule('lengthMax', $fieldName, 10000); break; case "color": diff --git a/system/typemill/Static/Settings.php b/system/typemill/Static/Settings.php index fe7017a..aeecd84 100644 --- a/system/typemill/Static/Settings.php +++ b/system/typemill/Static/Settings.php @@ -137,6 +137,12 @@ class Settings $storage->updateYaml('basepath', 'settings', 'settings.yaml', $settings); } + public static function getSettingsDefinitions() + { + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + return $storage->getYaml('systemSettings', '', 'system.yaml'); + } ### refactor diff --git a/system/typemill/Static/Translations.php b/system/typemill/Static/Translations.php index 84ca844..e1956c7 100644 --- a/system/typemill/Static/Translations.php +++ b/system/typemill/Static/Translations.php @@ -29,13 +29,11 @@ class Translations $plugins_translations = []; # theme labels selected according to the environment: admin or user - $theme_language_folder = DIRECTORY_SEPARATOR . $settings['theme'] . DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR . $environment . DIRECTORY_SEPARATOR; - $theme_translations = $storage->getYaml('themeFolder', $theme_language_folder, $language . '.yaml') ?? []; + $theme_translations = $storage->getYaml('themeFolder', $settings['theme'], $language . '.yaml') ?? []; if($environment == 'admin') { - $system_language_folder = DIRECTORY_SEPARATOR . 'typemill' . DIRECTORY_SEPARATOR . 'author' . DIRECTORY_SEPARATOR . 'translations' . DIRECTORY_SEPARATOR; - $system_translations = $storage->getYaml('translationFolder', $system_language_folder, $language . '.yaml'); + $system_translations = $storage->getYaml('translationFolder', '', $language . '.yaml'); # Next change, to provide labels for the admin and user environments. # There may be plugins that only work in the user environment, only in the admin environment, or in both environments. @@ -46,8 +44,7 @@ class Translations { if(isset($config['active']) && $config['active']) { - $plugin_language_folder = DIRECTORY_SEPARATOR . $plugin . DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR; - $plugins_translations[$plugin] = $storage->getYaml('pluginFolder', $plugin_language_folder, $language . '.yaml'); + $plugins_translations[$plugin] = $storage->getYaml('pluginFolder', $plugin, $language . '.yaml'); } } diff --git a/system/typemill/author/content/blox-editor.twig b/system/typemill/author/content/blox-editor.twig index 48e158f..b11c524 100644 --- a/system/typemill/author/content/blox-editor.twig +++ b/system/typemill/author/content/blox-editor.twig @@ -2,13 +2,35 @@ {% block title %}{{ translate('Visual Editor') }}{% endblock %} {% block content %} - -' . $url . ' ' . "\n"; + $urlset .= '{{ translate('Visual Editor') }}
- - + ++ + {% for block in content %} ++ + + + {% endblock %} + {% block javascript %} -{% endblock %} + + + + + + + +{% endblock %} \ No newline at end of file diff --git a/system/typemill/author/css/custom.css b/system/typemill/author/css/custom.css index 47a861d..a65840d 100644 --- a/system/typemill/author/css/custom.css +++ b/system/typemill/author/css/custom.css @@ -76,7 +76,9 @@ word-break: break-word; } -/* HIGHLIGHT */ +/******************** +* HIGHLIGHT * +********************/ .hljs-comment,.hljs-quote{ color:#d4d0ab @@ -114,6 +116,10 @@ } } +/******************** +* IMAGE BACKGROUND * +********************/ + .bg-chess { background-image: repeating-linear-gradient( 45deg, @@ -131,4 +137,744 @@ #D6D3D1); background-position: 0 0, 10px 10px; background-size: 20px 20px; -} \ No newline at end of file +} + + +/**************** +** BLOX ** +****************/ + +.transition-1{ + transition: color 0.1s ease, background-color 0.1s ease, border-color 0.1s ease; +} +.transition-2{ + transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease; +} + +/*** Editor Buttons ***/ +.blox-wrapper:hover .sideaction, +.edit .sideaction{ + display: block; +} +.sideaction:hover ~ .blox-preview{ + background: rgb(245 245 244); +} + + + +/*** BLOX PREVIEW ***/ + +/* reset margins */ +.blox-preview p, +.blox-preview ul, +.blox-preview ol, +.blox-preview dl, +.blox-preview pre, +.blox-preview blockquote, +.blox-preview .notice1, +.blox-preview .notice2, +.blox-preview .notice3, +.blox-preview .notice4, +.blox-preview .tm-table +{ + margin-top: 0px; + margin-bottom: 0px; +} +.blox-preview ul, .blox-preview ol{ + padding-left: 0px; + margin-left: 18px; +} +.blox-preview ul{ + list-style: disc; +} +.blox-preview ol{ + list-style: decimal; +} +.blox-preview table{ + width: 100%; + border-collapse: collapse; +} +.blox-preview thead{ + border-bottom: 1px solid rgb(20 184 166); + border-top: 1px solid rgb(20 184 166); + background: rgb(245 245 244); + font-weight: 700; +} +.blox-preview tbody{} +.blox-preview th{ padding: 10px 0;} +.blox-preview tr,.blox-editor tr{} +.blox-preview tr:nth-child{ } +.blox-preview tr:nth-child(even){ background-color:rgb(245 245 244); } +.blox-preview td{ padding: 5px;} + +.blox-preview dl{ + border-top: 1px solid rgb(20 184 166); + border-bottom: 1px solid rgb(20 184 166); + padding: 0.5em 0; + box-sizing: border-box; +} +.blox-preview dt, .blox-preview dd{ + width: 100%; + margin-left: 0; + margin-right: 0; + padding: 3px 5px; + box-sizing: border-box; + display: inline-block; + vertical-align: top; +} +.blox-preview dt{ + font-weight: 700; +} +.blox-preview dt::after{ + content: ":"; +} +.blox-preview dd{ + padding-left: 40px; +} + +.blox-preview pre,.blox-preview code{ + white-space: pre; + color: #333; + background: rgb(245 245 244); +} +.blox-preview code{ + display: inline-block; + padding: 0 0.5em; + font-size: 0.8em; + line-height: 1.4em; + border-radius: 3px; +} +.blox-preview code.hljs{ + background: transparent; +} +.blox-preview pre{ + padding: 10px; + display: block; + max-width: 100%; + overflow-x: auto; + border-left: 4px solid rgb(20 184 166); +} +.blox-preview code.language-pagebreak::after{ + content: '---pagebreak with the eBook plugin---'; +} + +.blox-preview blockquote{ + border-left: 4px solid rgb(20 184 166); + background: rgb(245 245 244); + position: relative; + font-style: italic; + font-family: serif; + padding: 5px; + padding-top:12px; + padding-bottom:12px; +} +.blox-preview blockquote:before { + position: absolute; + left: 0px; + top: 0px; + color: #ccc; + content: open-quote; + font-size: 3em; +} +.blox-preview blockquote p{ + margin-left: 50px; +} +.blox-preview a, +.blox-preview a:link, +.blox-preview a:visited +{ + text-decoration: none; + color: rgb(20 184 166); +} +.blox-preview a:focus, +.blox-preview a:hover, +.blox-preview a:active +{ + text-decoration: underline; +} +.blox-preview hr{ + margin-top:24px; + margin-bottom:24px; +} +.blox-preview .notice1, +.blox-preview .notice2, +.blox-preview .notice3, +.blox-preview .notice4{ + position: relative; + border-left: 25px solid rgb(20 184 166); + background: rgb(245 245 244); + padding: 10px; +} +.blox-preview .notice1:before, +.blox-preview .notice2:before, +.blox-preview .notice3:before, +.blox-preview .notice4:before{ + width: 25px; + left: -25px; + top: 16px; + position: absolute; + color: white; + text-align: center; +} +.blox-preview .notice1:before{ content: "!"; } +.blox-preview .notice2:before{ content: "!!"; } +.blox-preview .notice3:before{ content: "!!!"; } +.blox-preview .notice4:before{ content: "!!!!"; } + +.blox-preview img, img.uploadPreview{ + display: block; + margin: auto; + max-width: 100%; + height: auto; +} +.preview-chess{ + min-height:70px; +} + +.blox-preview .video-container{ + position: relative; + text-align: center; +} +.blox-preview img.youtube{ + position: relative; +} +.blox-preview button.play-video { + position: absolute; + top: 50%; + margin-top: -50px; + margin-left: -50px; + height: 100px; + width: 100px; + background: #e0474c; + color: #FFFFFF; + border-radius: 50%; + border: 0px; + padding: 0; + text-align: center; +} +.blox-preview button.play-video:hover { + background: #cc4146; +} +.blox-preview button.play-video::after { + position: absolute; + top: 50%; + margin: -20px 0 0 -15px; + height: 0; + width: 0; + border-style: solid; + border-width: 20px 0 20px 40px; + border-color: transparent transparent transparent rgba(255, 255, 255, 0.75); + content: ' '; +} + +.blox-preview ul.TOC, +.blox-preview .TOC ul{ + list-style: none; + padding-left: 0px; + margin-left: 0px; +} +.blox-preview .TOC li:before{ + content: "\2192"; + color: #bbb; + margin-left: -7px; + margin-right: 7px; +} +.blox-preview .TOC li.h1:before{ + content: ""; +} +.blox-preview ul.TOC{ + background: rgb(245 245 244); + width: 100%; + padding: 20px; + box-sizing:border-box; +} + + + +/* UNKNOWN +.blox-preview li.h1{ + font-weight: 700; + height:auto; +} +.blox-preview li.h2, +.blox-preview li.h3, +.blox-preview li.h4, +.blox-preview li.h5, +.blox-preview li.h6{ + font-weight: 400; + padding-left: 25px; + height:auto; +} +*/ + + +.blox-preview h1, .blox-preview h2, .blox-preview h3, .blox-preview h4, .blox-preview h5, .blox-preview h6, +.blox-editor .h1, .blox-editor .h2, .blox-editor .h3, .blox-editor .h4, .blox-editor .h5, .blox-editor .h6 +{ + font-weight: 700; + line-height: 1em; + margin-bottom: 0.6em; +} +.blox-preview h1, +.blox-editor .h1{ + margin-top: 0.6em; + font-size: 2.2em; +} +.blox-preview h2, +.blox-editor .h2{ + margin-top: 1.3em; + font-size: 1.6em; +} +.blox-preview h3, +.blox-editor .h3{ + margin-top: 1.2em; + font-size: 1.3em; + text-transform: none; +} +.blox-preview h4, +.blox-editor .h4{ + margin-top: 1.2em; + font-size: 1.1em; +} +.blox-preview h5, +.blox-editor .h5{ + margin-top: 1.2em; + font-size: 1em; +} +.blox-preview h6, +.blox-editor .h6{ + margin-top: 1em; + font-size: 1em; + font-style: italic; + font-weight:300; +} + +/*** BLOX EDITOR *** + +.edit .blox-editor .h1, +.edit .blox-editor .h2, +.edit .blox-editor .h3, +.edit .blox-editor .h4, +.edit .blox-editor .h5, +.edit .blox-editor .h6{ + line-height: 1em; + margin-top: 0; + margin-bottom: 0; +} +.edit .blox-editor .h1{ + font-weight: 700; + font-size: 2.2em; + padding-top: 0.6em; + padding-bottom: 0.6em; +} +.edit .blox-editor .h2{ + font-weight: 700; + font-size: 1.6em; + padding-top: 1.3em; + padding-bottom: 0.6em; +} +.edit .blox-editor .h3{ + font-weight: 700; + font-size: 1.3em; + text-transform: none; + padding-top: 1.2em; + padding-bottom: 0.6em; +} +.edit .blox-editor .h4{ + font-weight: 700; + font-size: 1.1em; + padding-top: 1.2em; + padding-bottom: 0.6em; +} +.edit .blox-editor .h5{ + font-weight: 700; + font-size: 1em; + padding-top: 1.2em; + padding-bottom: 0.6em; +} +.edit .blox-editor .h6{ + font-size: 1em; + font-style: italic; + font-weight:300; + padding-top: 1em; + padding-bottom: 0.6em; +} + + +/************************ +** INLINE FORMATG BAR ** +************************/ + +/* format menu */ +.inlineFormatBar { + box-sizing:content-box; + height: 30px; + padding: 5px 10px; + background: #333; + border-radius: 3px; + position: absolute; + top: 0; + left: 0; + transform: translate(-50%, -100%); + display: flex; + justify-content: center; + align-items: center; +} +/* Triangle below format popup */ +.inlineFormatBar:after { + content: ''; + position: absolute; + left: 50%; + bottom: -5px; + transform: translateX(-50%); + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid #333; +} +.inlineFormatItem { + color: #FFF; + cursor: pointer; + font-size: 0.9em; + padding: 3px; +} +.inlineFormatItem:hover { + color: #1199ff; +} +.inlineFormatItem + .inlineFormatItem { + margin-left: 10px; +} +.inlineFormatItem.inlineFormatLink +{ + margin-left: 2px; + margin-right: 2px; +} +.blox-editor input.urlinput{ + width: 75%; + min-height: auto; + background: #555; + color: #fff; + border: 0px; + padding: 5px; +} +.blox-editor input.urlinput:focus{ + outline: 0px; + border: 0px; +} + + +/* +.title input{ + font-size: 2.2em; + font-weight: 700; + padding: 20px; +} + + + +.imageupload{ + width: 50%; + position: relative; + display: inline-block; + border-right: 1px dotted grey; + box-sizing:border-box; +} + + +.imageselect{ + width: 50%; + position: relative; + display: inline-block; + box-sizing:border-box; + border:0px; + padding: 0 0 0 0; + margin: 0 0 0 0; + min-height: 70px; + background: #f9f8f6; + font-family: Helvetica, Calibri, Arial, sans-serif; +} +.dropbox{ + min-height: 70px; + background: #f9f8f6; + padding: 0px; + box-sizing: border-box; + margin-bottom: 10px; +} +.dropbox p{ + position: relative; + cursor: pointer; + line-height: 70px; + width: 100%; + text-align: center; + box-sizing:border-box; + padding: 0; + margin: 0; +} +.dropbox input, .dropbox select{ + background-color: #fff; + width: 80%; + margin: 2px 0; + display: inline-block; +} +.video input{ + width: 75%; + margin: 15px 0; +} +.dropbox select{ + background-image: linear-gradient(45deg, transparent 50%, #444 50%), linear-gradient(135deg, #444 50%, transparent 50%), linear-gradient(to right, #fff, #fff); +} +.dropbox label{ + width: 20%; + display: inline-block; +} +.video label{ + text-align: right; + padding-right: 10px; + box-sizing: border-box; +} +.dropbox .imgmeta{ + padding: 30px; + box-sizing: border-box; +} +.dropbox .input-file{ + opacity: 0; + width: 100%; + height: 70px; + position: absolute; + cursor: pointer; + z-index: 1; +} +.medialib{ + margin: auto; + width: 100%; + height: 80%; + overflow: auto; + background: #f9f8f6; + max-width: 1200px; +} +.imagecard{ + margin: 10px; + box-shadow:0 2px 5px rgba(22,23,26,.05); + display: inline-block; + vertical-align: top; + background: #fff; +} + +sup{} +cite{} +abbr{} +hr{ + background: #ddd; + height: 2px; + margin: 20px 0; + border: 0px; +} +.setupWrapper a, .setupWrapper a:link, .setupWrapper a:visited +{ + text-decoration: none; + color: #444; +} +.setupWrapper a:focus, .setupWrapper a:hover, .setupWrapper a:active +{ + color: #e0474c; +} +.mbfix{ margin-bottom: 0px!important; } + +.slugbutton{ + right: 20px; + height: 52px; + width: 150px; +} + + + + + + + + + + + + + +.blox-body{ + position: relative; + padding: 18px 20px +} +.blox-overlay{ + position:absolute; + display: block; + z-index: 10; + box-sizing: border-box; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: #FFF; + background: rgba(255,255,255,0.8); +} +.blox-editor{ + position: relative; +} +.blox-buttons{ + position: absolute; + bottom: -15px; + text-align: right; + right: 25px; + width: 200px; + z-index: 99; +} +.blox-buttons button{ + display: inline-block; + box-sizing: border-box; + margin: 2px; + padding: 3px 6px; + width: 80px; + text-align: center; + color: #444; + background: #f9f8f6; + border: 2px solid #fff; + border-radius: 2px; + font-size: 0.9em; +} +.blox-buttons button.edit{ + background: #70c1b3; + color: #fff; +} +.blox-buttons button.edit:hover{ + background: #4D978A; +} +.blox-buttons button.cancel:hover{ + background: #e0474c; + color: #fff; +} +.blox-buttons button.edit:disabled, .blox-buttons button.cancel:disabled{ + background: #eee; + color: #444; + border: 1px solid #eee; +} + + +.sideaction{ + position: absolute; + top: 0px; + font-size: 0.8em; + right: -22px; +} +.sideaction button{ + display: block; + font-weight: 300; + font-size: 0.9em; + background: #fff; + color: #fff; + width: 20px; + height: 20px; + line-height: 25px; + text-align: center; + padding: 0px; + margin: 1px; + border: 0px; + border-radius: 1px; +} +.blox-wrapper{ + position: relative; +} +.editactive .sideaction button, +.blox-wrapper:hover button.add, +.blox-wrapper:hover button.delete{ + background-color: #f9f8f6; + color: #666; +} +.sideaction:hover ~ .background-helper { + background-color: #f9f8f6; +} +.editactive .background-helper{ + background-color: transparent!important; +} +.blox-wrapper button.add:hover{ + background: #66b0a3; + color: #fff; +} +.blox-wrapper button.delete:hover{ + background: #e0474c; + color: #fff; +} + + + +.blox-editor textarea{ + font-family: arial; + line-height: 1.5em; + font-size: 16px; + padding-left: 20px; + padding-right: 20px; + box-sizing: border-box; + min-height: 40px; +} +.blox-editor textarea:focus, .blox-editor input:focus{ + box-shadow: none; + outline: none; +} +.blox-editor input.mdcontent.h2,.blox-editor input.mdcontent.h3,.blox-editor input.mdcontent.h4,.blox-editor input.mdcontent.h5,.blox-editor input.mdcontent.h6{ + padding-left: 35px; + height: auto; +} +.blox-editor input.mdcontent.h2{ + font-size: 1.6em; + font-weight: 700; +} +.blox-editor input.mdcontent.h3{ + font-size: 1.3em; + font-weight: 700; +} +.blox-editor input.mdcontent.h4{ + font-size: 1.1em; + font-weight: 700; +} +.blox-editor input.mdcontent.h5{ + font-size: 1em; + font-weight: 700; +} +.blox-editor input.mdcontent.h6{ + font-size: 1em; + font-weight: 300; + font-style: italic; +} +button.hdown{ + position: absolute; + padding: 8px; + top: 1px; + bottom: 1px; + left: 0px; + font-size: 1em; + font-weight: 700; + border: 0px solid #fff; + border-right: 1px solid #fff; +} +button.hdown.headline{ + color: #f9f8f6; + background: #66b0a3; +} +.blox-editor .contenttype { + position: absolute; + top: 15px; + left: -25px; + color: #666; +} +.visible{ + display: block; +} +.hidden{ + visibility: hidden; +} +.hidden .blox:hover{ + background: #fff; +} +.component{ + position: absolute; + width: 100%; + z-index:9; +} +*/ \ No newline at end of file diff --git a/system/typemill/author/css/output.css b/system/typemill/author/css/output.css index 093ff07..620ae0a 100644 --- a/system/typemill/author/css/output.css +++ b/system/typemill/author/css/output.css @@ -661,24 +661,9 @@ video { bottom: 0px; } -.right-0 { - right: 0px; -} - -.left-0 { +.inset-x-0 { left: 0px; -} - -.top-0 { - top: 0px; -} - -.top-1 { - top: 0.25rem; -} - -.right-1 { - right: 0.25rem; + right: 0px; } .bottom-0 { @@ -689,6 +674,62 @@ video { top: 0.75rem; } +.-left-5 { + left: -1.25rem; +} + +.top-0 { + top: 0px; +} + +.left-0 { + left: 0px; +} + +.left-12 { + left: 3rem; +} + +.-bottom-3 { + bottom: -0.75rem; +} + +.right-4 { + right: 1rem; +} + +.-top-3 { + top: -0.75rem; +} + +.left-1\/2 { + left: 50%; +} + +.right-0 { + right: 0px; +} + +.bottom-3 { + bottom: 0.75rem; +} + +.top-1 { + top: 0.25rem; +} + +.right-1 { + right: 0.25rem; +} + +.z-20 { + z-index: 20; +} + +.z-10 { + z-index: 10; +} + .z-50 { z-index: 50; } @@ -697,6 +738,10 @@ video { margin: 0px; } +.m-1 { + margin: 0.25rem; +} + .m-auto { margin: auto; } @@ -706,24 +751,24 @@ video { margin-bottom: 0.5rem; } +.my-5 { + margin-top: 1.25rem; + margin-bottom: 1.25rem; +} + .my-px { margin-top: 1px; margin-bottom: 1px; } -.my-8 { - margin-top: 2rem; - margin-bottom: 2rem; -} - .mx-auto { margin-left: auto; margin-right: auto; } -.my-5 { - margin-top: 1.25rem; - margin-bottom: 1.25rem; +.my-8 { + margin-top: 2rem; + margin-bottom: 2rem; } .my-1 { @@ -745,36 +790,44 @@ video { margin-top: 1.5rem; } -.mb-4 { - margin-bottom: 1rem; -} - -.mr-3 { - margin-right: 0.75rem; -} - -.ml-1 { - margin-left: 0.25rem; -} - -.mr-1 { - margin-right: 0.25rem; -} - .mb-1 { margin-bottom: 0.25rem; } +.mb-2 { + margin-bottom: 0.5rem; +} + .mt-3 { margin-top: 0.75rem; } -.mb-3 { - margin-bottom: 0.75rem; +.ml-4 { + margin-left: 1rem; } -.ml-3 { - margin-left: 0.75rem; +.mr-2 { + margin-right: 0.5rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mr-1 { + margin-right: 0.25rem; +} + +.ml-1 { + margin-left: 0.25rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mr-3 { + margin-right: 0.75rem; } .mt-5 { @@ -789,12 +842,12 @@ video { margin-left: 0.5rem; } -.mb-2 { - margin-bottom: 0.5rem; +.mb-3 { + margin-bottom: 0.75rem; } -.mt-2 { - margin-top: 0.5rem; +.ml-3 { + margin-left: 0.75rem; } .mt-4 { @@ -813,8 +866,12 @@ video { margin-right: 1rem; } -.mr-2 { - margin-right: 0.5rem; +.mb-12 { + margin-bottom: 3rem; +} + +.mb-16 { + margin-bottom: 4rem; } .block { @@ -853,20 +910,16 @@ video { display: none; } -.h-5 { - height: 1.25rem; -} - -.h-8 { - height: 2rem; -} - .h-6 { height: 1.5rem; } -.h-0 { - height: 0px; +.h-24 { + height: 6rem; +} + +.h-5 { + height: 1.25rem; } .h-12 { @@ -877,6 +930,14 @@ video { height: 20rem; } +.h-8 { + height: 2rem; +} + +.h-0 { + height: 0px; +} + .h-48 { height: 12rem; } @@ -897,6 +958,38 @@ video { width: 100%; } +.w-54rem { + width: 54rem; +} + +.w-6 { + width: 1.5rem; +} + +.w-32 { + width: 8rem; +} + +.w-1\/5 { + width: 20%; +} + +.w-4\/5 { + width: 80%; +} + +.w-2\/5 { + width: 40%; +} + +.w-20 { + width: 5rem; +} + +.w-16 { + width: 4rem; +} + .w-5 { width: 1.25rem; } @@ -905,26 +998,6 @@ video { width: 25%; } -.w-2\/5 { - width: 40%; -} - -.w-3\/5 { - width: 60%; -} - -.w-half { - width: 48%; -} - -.w-6 { - width: 1.5rem; -} - -.w-0 { - width: 0px; -} - .w-2\/3 { width: 66.666667%; } @@ -933,6 +1006,10 @@ video { width: 33.333333%; } +.w-half { + width: 48%; +} + .w-80 { width: 20rem; } @@ -953,10 +1030,22 @@ video { width: 91.666667%; } +.w-3\/5 { + width: 60%; +} + +.w-0 { + width: 0px; +} + .w-3\/4 { width: 75%; } +.w-24 { + width: 6rem; +} + .max-w-md { max-width: 28rem; } @@ -965,6 +1054,10 @@ video { max-width: 20rem; } +.max-w-4xl { + max-width: 56rem; +} + .max-w-6xl { max-width: 72rem; } @@ -989,6 +1082,11 @@ video { border-collapse: collapse; } +.-translate-x-1\/2 { + --tw-translate-x: -50%; + 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)); +} + .transform { 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)); } @@ -1104,14 +1202,30 @@ video { border-right-width: 8px; } -.border-l-4 { - border-left-width: 4px; +.border-r-2 { + border-right-width: 2px; +} + +.border-b-2 { + border-bottom-width: 2px; } .border-b { border-bottom-width: 1px; } +.border-r { + border-right-width: 1px; +} + +.border-l { + border-left-width: 1px; +} + +.border-l-4 { + border-left-width: 4px; +} + .border-b-8 { border-bottom-width: 8px; } @@ -1120,10 +1234,6 @@ video { border-top-width: 8px; } -.border-b-2 { - border-bottom-width: 2px; -} - .border-b-4 { border-bottom-width: 4px; } @@ -1132,19 +1242,33 @@ video { border-left-width: 2px; } -.border-r-2 { - border-right-width: 2px; +.border-t { + border-top-width: 1px; } .border-solid { border-style: solid; } +.border-dotted { + border-style: dotted; +} + .border-gray-300 { --tw-border-opacity: 1; border-color: rgb(209 213 219 / var(--tw-border-opacity)); } +.border-stone-700 { + --tw-border-opacity: 1; + border-color: rgb(68 64 60 / var(--tw-border-opacity)); +} + +.border-stone-300 { + --tw-border-opacity: 1; + border-color: rgb(214 211 209 / var(--tw-border-opacity)); +} + .border-stone-200 { --tw-border-opacity: 1; border-color: rgb(231 229 228 / var(--tw-border-opacity)); @@ -1165,24 +1289,14 @@ video { border-color: rgb(250 250 249 / var(--tw-border-opacity)); } -.border-white { - --tw-border-opacity: 1; - border-color: rgb(255 255 255 / var(--tw-border-opacity)); -} - .border-red-500 { --tw-border-opacity: 1; border-color: rgb(239 68 68 / var(--tw-border-opacity)); } -.border-stone-300 { +.border-white { --tw-border-opacity: 1; - border-color: rgb(214 211 209 / var(--tw-border-opacity)); -} - -.border-stone-700 { - --tw-border-opacity: 1; - border-color: rgb(68 64 60 / var(--tw-border-opacity)); + border-color: rgb(255 255 255 / var(--tw-border-opacity)); } .border-rose-100 { @@ -1200,6 +1314,26 @@ video { border-color: rgb(226 232 240 / var(--tw-border-opacity)); } +.border-rose-500 { + --tw-border-opacity: 1; + border-color: rgb(244 63 94 / var(--tw-border-opacity)); +} + +.border-yellow-500 { + --tw-border-opacity: 1; + border-color: rgb(234 179 8 / var(--tw-border-opacity)); +} + +.border-yellow-300 { + --tw-border-opacity: 1; + border-color: rgb(253 224 71 / var(--tw-border-opacity)); +} + +.border-yellow-400 { + --tw-border-opacity: 1; + border-color: rgb(250 204 21 / var(--tw-border-opacity)); +} + .border-x-transparent { border-left-color: transparent; border-right-color: transparent; @@ -1225,10 +1359,30 @@ video { background-color: rgb(255 255 255 / var(--tw-bg-opacity)); } +.bg-stone-300 { + --tw-bg-opacity: 1; + background-color: rgb(214 211 209 / var(--tw-bg-opacity)); +} + .bg-transparent { background-color: transparent; } +.bg-stone-200 { + --tw-bg-opacity: 1; + background-color: rgb(231 229 228 / var(--tw-bg-opacity)); +} + +.bg-stone-700 { + --tw-bg-opacity: 1; + background-color: rgb(68 64 60 / var(--tw-bg-opacity)); +} + +.bg-stone-100 { + --tw-bg-opacity: 1; + background-color: rgb(245 245 244 / var(--tw-bg-opacity)); +} + .bg-teal-500 { --tw-bg-opacity: 1; background-color: rgb(20 184 166 / var(--tw-bg-opacity)); @@ -1239,21 +1393,6 @@ video { background-color: rgb(244 63 94 / var(--tw-bg-opacity)); } -.bg-stone-100 { - --tw-bg-opacity: 1; - background-color: rgb(245 245 244 / var(--tw-bg-opacity)); -} - -.bg-stone-700 { - --tw-bg-opacity: 1; - background-color: rgb(68 64 60 / var(--tw-bg-opacity)); -} - -.bg-stone-200 { - --tw-bg-opacity: 1; - background-color: rgb(231 229 228 / var(--tw-bg-opacity)); -} - .bg-red-100 { --tw-bg-opacity: 1; background-color: rgb(254 226 226 / var(--tw-bg-opacity)); @@ -1264,11 +1403,31 @@ video { background-color: rgb(28 25 23 / var(--tw-bg-opacity)); } +.bg-stone-600 { + --tw-bg-opacity: 1; + background-color: rgb(87 83 78 / var(--tw-bg-opacity)); +} + .bg-stone-50 { --tw-bg-opacity: 1; background-color: rgb(250 250 249 / var(--tw-bg-opacity)); } +.bg-yellow-500 { + --tw-bg-opacity: 1; + background-color: rgb(234 179 8 / var(--tw-bg-opacity)); +} + +.bg-yellow-400 { + --tw-bg-opacity: 1; + background-color: rgb(250 204 21 / var(--tw-bg-opacity)); +} + +.bg-yellow-300 { + --tw-bg-opacity: 1; + background-color: rgb(253 224 71 / var(--tw-bg-opacity)); +} + .bg-opacity-90 { --tw-bg-opacity: 0.9; } @@ -1281,14 +1440,18 @@ video { background-position: center; } -.p-1 { - padding: 0.25rem; +.p-3 { + padding: 0.75rem; } .p-2 { padding: 0.5rem; } +.p-6 { + padding: 1.5rem; +} + .p-8 { padding: 2rem; } @@ -1297,12 +1460,8 @@ video { padding: 1rem; } -.p-3 { - padding: 0.75rem; -} - -.p-6 { - padding: 1.5rem; +.p-1 { + padding: 0.25rem; } .py-5 { @@ -1330,24 +1489,34 @@ video { padding-bottom: 0.75rem; } +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.px-12 { + padding-left: 3rem; + padding-right: 3rem; +} + +.py-8 { + padding-top: 2rem; + padding-bottom: 2rem; +} + .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; } -.px-8 { - padding-left: 2rem; - padding-right: 2rem; -} - .py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; } -.py-4 { - padding-top: 1rem; - padding-bottom: 1rem; +.px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; } .px-4 { @@ -1355,9 +1524,39 @@ video { padding-right: 1rem; } -.px-1 { - padding-left: 0.25rem; - padding-right: 0.25rem; +.py-10 { + padding-top: 2.5rem; + padding-bottom: 2.5rem; +} + +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.pr-6 { + padding-right: 1.5rem; +} + +.pl-10 { + padding-left: 2.5rem; +} + +.pr-2 { + padding-right: 0.5rem; +} + +.pb-6 { + padding-bottom: 1.5rem; +} + +.pl-4 { + padding-left: 1rem; } .pl-3 { @@ -1368,10 +1567,6 @@ video { padding-left: 0.5rem; } -.pl-4 { - padding-left: 1rem; -} - .pl-6 { padding-left: 1.5rem; } @@ -1384,18 +1579,10 @@ video { padding-left: 2.25rem; } -.pl-10 { - padding-left: 2.5rem; -} - .pl-12 { padding-left: 3rem; } -.pr-2 { - padding-right: 0.5rem; -} - .pr-1 { padding-right: 0.25rem; } @@ -1444,6 +1631,10 @@ video { vertical-align: middle; } +.font-mono { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + .text-6xl { font-size: 3.75rem; line-height: 1; @@ -1459,9 +1650,19 @@ video { line-height: 1rem; } -.text-3xl { - font-size: 1.875rem; - line-height: 2.25rem; +.text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; } .text-lg { @@ -1474,14 +1675,9 @@ 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 { @@ -1527,9 +1723,9 @@ video { color: rgb(225 29 72 / var(--tw-text-opacity)); } -.text-stone-50 { +.text-stone-400 { --tw-text-opacity: 1; - color: rgb(250 250 249 / var(--tw-text-opacity)); + color: rgb(168 162 158 / var(--tw-text-opacity)); } .text-stone-700 { @@ -1542,9 +1738,19 @@ video { color: rgb(120 113 108 / var(--tw-text-opacity)); } -.text-teal-500 { +.text-stone-900 { --tw-text-opacity: 1; - color: rgb(20 184 166 / var(--tw-text-opacity)); + color: rgb(28 25 23 / var(--tw-text-opacity)); +} + +.text-stone-300 { + --tw-text-opacity: 1; + color: rgb(214 211 209 / var(--tw-text-opacity)); +} + +.text-stone-50 { + --tw-text-opacity: 1; + color: rgb(250 250 249 / var(--tw-text-opacity)); } .text-red-500 { @@ -1567,6 +1773,21 @@ video { color: rgb(0 0 0 / var(--tw-text-opacity)); } +.text-teal-300 { + --tw-text-opacity: 1; + color: rgb(94 234 212 / var(--tw-text-opacity)); +} + +.text-teal-500 { + --tw-text-opacity: 1; + color: rgb(20 184 166 / var(--tw-text-opacity)); +} + +.text-stone-200 { + --tw-text-opacity: 1; + color: rgb(231 229 228 / var(--tw-text-opacity)); +} + .text-rose-500 { --tw-text-opacity: 1; color: rgb(244 63 94 / var(--tw-text-opacity)); @@ -1577,25 +1798,34 @@ video { color: rgb(6 182 212 / var(--tw-text-opacity)); } +.text-teal-700 { + --tw-text-opacity: 1; + color: rgb(15 118 110 / var(--tw-text-opacity)); +} + .underline { -webkit-text-decoration-line: underline; text-decoration-line: underline; } +.caret-white { + caret-color: #fff; +} + .accent-white { accent-color: #fff; } -.opacity-25 { - opacity: 0.25; +.opacity-0 { + opacity: 0; } .opacity-75 { opacity: 0.75; } -.opacity-0 { - opacity: 0; +.opacity-25 { + opacity: 0.25; } .shadow-lg { @@ -1616,10 +1846,20 @@ video { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } +.outline-none { + outline: 2px solid transparent; + outline-offset: 2px; +} + .outline { outline-style: solid; } +.blur { + --tw-blur: blur(8px); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + .filter { filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); } @@ -1685,11 +1925,6 @@ video { background-color: rgb(229 231 235 / var(--tw-bg-opacity)); } -.hover\:bg-stone-700:hover { - --tw-bg-opacity: 1; - background-color: rgb(68 64 60 / var(--tw-bg-opacity)); -} - .hover\:bg-teal-500:hover { --tw-bg-opacity: 1; background-color: rgb(20 184 166 / var(--tw-bg-opacity)); @@ -1700,11 +1935,31 @@ video { background-color: rgb(231 229 228 / var(--tw-bg-opacity)); } +.hover\:bg-rose-500:hover { + --tw-bg-opacity: 1; + background-color: rgb(244 63 94 / var(--tw-bg-opacity)); +} + +.hover\:bg-stone-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(68 64 60 / var(--tw-bg-opacity)); +} + +.hover\:bg-stone-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(245 245 244 / var(--tw-bg-opacity)); +} + .hover\:bg-stone-900:hover { --tw-bg-opacity: 1; background-color: rgb(28 25 23 / var(--tw-bg-opacity)); } +.hover\:bg-stone-300:hover { + --tw-bg-opacity: 1; + background-color: rgb(214 211 209 / var(--tw-bg-opacity)); +} + .hover\:bg-teal-600:hover { --tw-bg-opacity: 1; background-color: rgb(13 148 136 / var(--tw-bg-opacity)); @@ -1715,26 +1970,11 @@ video { background-color: rgb(15 118 110 / var(--tw-bg-opacity)); } -.hover\:bg-rose-500:hover { - --tw-bg-opacity: 1; - background-color: rgb(244 63 94 / var(--tw-bg-opacity)); -} - -.hover\:bg-stone-300:hover { - --tw-bg-opacity: 1; - background-color: rgb(214 211 209 / var(--tw-bg-opacity)); -} - .hover\:bg-rose-700:hover { --tw-bg-opacity: 1; background-color: rgb(190 18 60 / var(--tw-bg-opacity)); } -.hover\:bg-stone-100:hover { - --tw-bg-opacity: 1; - background-color: rgb(245 245 244 / var(--tw-bg-opacity)); -} - .hover\:bg-cyan-500:hover { --tw-bg-opacity: 1; background-color: rgb(6 182 212 / var(--tw-bg-opacity)); @@ -1745,19 +1985,29 @@ video { background-color: rgb(250 250 249 / var(--tw-bg-opacity)); } +.hover\:bg-yellow-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(202 138 4 / var(--tw-bg-opacity)); +} + .hover\:text-stone-50:hover { --tw-text-opacity: 1; color: rgb(250 250 249 / var(--tw-text-opacity)); } +.hover\:text-white:hover { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + .hover\:text-stone-100:hover { --tw-text-opacity: 1; color: rgb(245 245 244 / var(--tw-text-opacity)); } -.hover\:text-white:hover { +.hover\:text-stone-700:hover { --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); + color: rgb(68 64 60 / var(--tw-text-opacity)); } .hover\:underline:hover { @@ -1811,6 +2061,24 @@ video { background-color: rgb(250 250 249 / var(--tw-bg-opacity)); } +.disabled\:cursor-default:disabled { + cursor: default; +} + +.disabled\:cursor-not-allowed:disabled { + cursor: not-allowed; +} + +.disabled\:bg-stone-50:disabled { + --tw-bg-opacity: 1; + background-color: rgb(250 250 249 / var(--tw-bg-opacity)); +} + +.disabled\:text-stone-900:disabled { + --tw-text-opacity: 1; + color: rgb(28 25 23 / var(--tw-text-opacity)); +} + .group:hover .group-hover\:visible { visibility: visible; } diff --git a/system/typemill/author/js/autosize.min.js b/system/typemill/author/js/autosize.min.js new file mode 100644 index 0000000..2a162d3 --- /dev/null +++ b/system/typemill/author/js/autosize.min.js @@ -0,0 +1,6 @@ +/*! + Autosize 4.0.0 + license: MIT + http://www.jacklmoore.com/autosize +*/ +!function(e,t){if("function"==typeof define&&define.amd)define(["exports","module"],t);else if("undefined"!=typeof exports&&"undefined"!=typeof module)t(exports,module);else{var n={exports:{}};t(n.exports,n),e.autosize=n.exports}}(this,function(e,t){"use strict";function n(e){function t(){var t=window.getComputedStyle(e,null);"vertical"===t.resize?e.style.resize="none":"both"===t.resize&&(e.style.resize="horizontal"),s="content-box"===t.boxSizing?-(parseFloat(t.paddingTop)+parseFloat(t.paddingBottom)):parseFloat(t.borderTopWidth)+parseFloat(t.borderBottomWidth),isNaN(s)&&(s=0),l()}function n(t){var n=e.style.width;e.style.width="0px",e.offsetWidth,e.style.width=n,e.style.overflowY=t}function o(e){for(var t=[];e&&e.parentNode&&e.parentNode instanceof Element;)e.parentNode.scrollTop&&t.push({node:e.parentNode,scrollTop:e.parentNode.scrollTop}),e=e.parentNode;return t}function r(){var t=e.style.height,n=o(e),r=document.documentElement&&document.documentElement.scrollTop;e.style.height="";var i=e.scrollHeight+s;return 0===e.scrollHeight?void(e.style.height=t):(e.style.height=i+"px",u=e.clientWidth,n.forEach(function(e){e.node.scrollTop=e.scrollTop}),void(r&&(document.documentElement.scrollTop=r)))}function l(){r();var t=Math.round(parseFloat(e.style.height)),o=window.getComputedStyle(e,null),i="content-box"===o.boxSizing?Math.round(parseFloat(o.height)):e.offsetHeight;if(i!==t?"hidden"===o.overflowY&&(n("scroll"),r(),i="content-box"===o.boxSizing?Math.round(parseFloat(window.getComputedStyle(e,null).height)):e.offsetHeight):"hidden"!==o.overflowY&&(n("hidden"),r(),i="content-box"===o.boxSizing?Math.round(parseFloat(window.getComputedStyle(e,null).height)):e.offsetHeight),a!==i){a=i;var l=d("autosize:resized");try{e.dispatchEvent(l)}catch(e){}}}if(e&&e.nodeName&&"TEXTAREA"===e.nodeName&&!i.has(e)){var s=null,u=e.clientWidth,a=null,c=function(){e.clientWidth!==u&&l()},p=function(t){window.removeEventListener("resize",c,!1),e.removeEventListener("input",l,!1),e.removeEventListener("keyup",l,!1),e.removeEventListener("autosize:destroy",p,!1),e.removeEventListener("autosize:update",l,!1),Object.keys(t).forEach(function(n){e.style[n]=t[n]}),i.delete(e)}.bind(e,{height:e.style.height,resize:e.style.resize,overflowY:e.style.overflowY,overflowX:e.style.overflowX,wordWrap:e.style.wordWrap});e.addEventListener("autosize:destroy",p,!1),"onpropertychange"in e&&"oninput"in e&&e.addEventListener("keyup",l,!1),window.addEventListener("resize",c,!1),e.addEventListener("input",l,!1),e.addEventListener("autosize:update",l,!1),e.style.overflowX="hidden",e.style.wordWrap="break-word",i.set(e,{destroy:p,update:l}),t()}}function o(e){var t=i.get(e);t&&t.destroy()}function r(e){var t=i.get(e);t&&t.update()}var i="function"==typeof Map?new Map:function(){var e=[],t=[];return{has:function(t){return e.indexOf(t)>-1},get:function(n){return t[e.indexOf(n)]},set:function(n,o){e.indexOf(n)===-1&&(e.push(n),t.push(o))},delete:function(n){var o=e.indexOf(n);o>-1&&(e.splice(o,1),t.splice(o,1))}}}(),d=function(e){return new Event(e,{bubbles:!0})};try{new Event("test")}catch(e){d=function(e){var t=document.createEvent("Event");return t.initEvent(e,!0,!1),t}}var l=null;"undefined"==typeof window||"function"!=typeof window.getComputedStyle?(l=function(e){return e},l.destroy=function(e){return e},l.update=function(e){return e}):(l=function(e,t){return e&&Array.prototype.forEach.call(e.length?e:[e],function(e){return n(e,t)}),e},l.destroy=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],o),e},l.update=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],r),e}),t.exports=l}); \ No newline at end of file diff --git a/system/typemill/author/js/vue-blox-components.js b/system/typemill/author/js/vue-blox-components.js new file mode 100644 index 0000000..3200086 --- /dev/null +++ b/system/typemill/author/js/vue-blox-components.js @@ -0,0 +1,2285 @@ +bloxeditor.component('title-component', { + props: ['markdown', 'disabled', 'index'], + template: `{{ block.html|raw }}+ {% endfor %} + ++ +`, + mounted: function(){ + this.$refs.markdown.focus(); + autosize(document.querySelectorAll('textarea')); + }, + methods: { + updatemarkdown(content) + { + this.$emit('updateMarkdownEvent', content); + }, + }, +}) + +bloxeditor.component('markdown-component', { + props: ['markdown', 'disabled', 'index'], + template: `+`, + mounted: function(){ + this.$refs.markdown.focus(); + autosize(document.querySelectorAll('textarea')); + }, + methods: { + updatemarkdown(content) + { + var emptyline = /^\s*$(?:\r\n?|\n)/gm; + + if(content.search(emptyline) > -1) + { + this.$emit('updateMarkdownEvent', content.trim()); + this.$emit('saveBlockEvent'); + } + else + { + this.$emit('updateMarkdownEvent', content); + } + } + }, +}) + +bloxeditor.component('headline-component', { + props: ['markdown', 'disabled', 'index'], + template: `+ +++ + ++`, + data: function(){ + return { + level: '', + hlevel: '', + compmarkdown: '' + } + }, + mounted: function(){ + + this.$refs.markdown.focus(); + + this.compmarkdown = this.markdown; + + if(!this.compmarkdown) + { + this.compmarkdown = '## '; + this.level = '2'; + this.hlevel = 'h2'; + } + else + { + this.level = this.getHeadlineLevel(this.markdown); + this.hlevel = 'h' + this.level; + } + }, + methods: { + updatemarkdown: function(event) + { + this.level = this.getHeadlineLevel(this.compmarkdown); + if(this.level > 6) + { + this.compmarkdown = '######' + this.compmarkdown.substr(this.level); + this.level = 6; + } + else if(this.level < 2) + { + this.compmarkdown = '##' + this.compmarkdown.substr(this.level); + this.level = 2; + } + this.hlevel = 'h' + this.level; + + this.$emit('updateMarkdownEvent', this.compmarkdown); + }, + headlinedown: function() + { + this.level = this.getHeadlineLevel(this.compmarkdown); + if(this.level < 6) + { + this.compmarkdown = this.compmarkdown.substr(0, this.level) + '#' + this.compmarkdown.substr(this.level); + this.level = this.level+1; + this.hlevel = 'h' + this.level; + } + else + { + this.compmarkdown = '##' + this.compmarkdown.substr(this.level); + this.level = 2; + this.hlevel = 'h2'; + } + + this.$emit('updateMarkdownEvent', this.compmarkdown); + }, + getHeadlineLevel: function(str) + { + var count = 0; + for(var i = 0; i < str.length; i++){ + if(str[i] != '#'){ return count } + count++; + } + return count; + }, + }, +}) + +bloxeditor.component('ulist-component', { + props: ['markdown', 'disabled', 'index'], + template: `+ ++ + ++`, + data: function(){ + return { + compmarkdown: '' + } + }, + mounted: function(){ + + this.compmarkdown = this.markdown; + + if(this.compmarkdown == '') + { + this.compmarkdown = '* '; + } + else + { + var lines = this.compmarkdown.split("\n"); + var length = lines.length + var md = ''; + + for(i = 0; i < length; i++) + { + var clean = lines[i]; + clean = clean.replace(/^- /, '* '); + clean = clean.replace(/^\+ /, '* '); + if(i == length-1) + { + md += clean; + } + else + { + md += clean + '\n'; + } + } + this.compmarkdown = md; + } + + this.$nextTick(function () { + autosize(document.querySelectorAll('textarea')); + }); + + this.$refs.markdown.focus(); + }, + methods: { + updatemarkdown: function(event) + { + this.$emit('updateMarkdownEvent', event.target.value); + }, + newLine: function(event) + { + let listend = '* \n'; // '1. \n'; + let liststyle = '* '; // '1. '; + + if(this.compmarkdown.endsWith(listend)) + { + this.compmarkdown = this.compmarkdown.replace(listend, ''); + this.$emit('updateMarkdownEvent', this.compmarkdown); + this.$emit('saveBlockEvent'); + } + else + { + let mdtextarea = document.getElementsByTagName('textarea'); + let start = mdtextarea[0].selectionStart; + let end = mdtextarea[0].selectionEnd; + + this.compmarkdown = this.compmarkdown.substr(0, end) + liststyle + this.compmarkdown.substr(end); + + mdtextarea[0].focus(); + if(mdtextarea[0].setSelectionRange) + { + setTimeout(function(){ + // var spacer = (this.componentType == "ulist-component") ? 2 : 3; + mdtextarea[0].setSelectionRange(end+2, end+2); + }, 1); + } + } + }, + } +}) + +bloxeditor.component('olist-component', { + props: ['markdown', 'disabled', 'index'], + template: `+ +++ + ++`, + data: function(){ + return { + compmarkdown: '' + } + }, + mounted: function(){ + + this.compmarkdown = this.markdown; + + if(this.compmarkdown == '') + { + this.compmarkdown = '1. '; + } + + this.$nextTick(function () { + autosize(document.querySelectorAll('textarea')); + }); + + this.$refs.markdown.focus(); + }, + methods: { + updatemarkdown: function(event) + { + this.$emit('updateMarkdownEvent', event.target.value); + }, + newLine: function(event) + { + let listend = '1. \n'; + let liststyle = '1. '; + + if(this.compmarkdown.endsWith(listend)) + { + this.compmarkdown = this.compmarkdown.replace(listend, ''); + this.$emit('updateMarkdownEvent', this.compmarkdown); + this.$emit('saveBlockEvent'); + } + else + { + let mdtextarea = document.getElementsByTagName('textarea'); + let start = mdtextarea[0].selectionStart; + let end = mdtextarea[0].selectionEnd; + + this.compmarkdown = this.compmarkdown.substr(0, end) + liststyle + this.compmarkdown.substr(end); + + mdtextarea[0].focus(); + if(mdtextarea[0].setSelectionRange) + { + setTimeout(function(){ + mdtextarea[0].setSelectionRange(end+3, end+3); + }, 1); + } + } + }, + }, +}) + +bloxeditor.component('code-component', { + props: ['markdown', 'disabled', 'index'], + template: `+ +++ + ++`, + data: function(){ + return { + prefix: '```', + language: '', + codeblock: '', + } + }, + mounted: function(){ + this.$refs.markdown.focus(); + if(this.markdown) + { + var codelines = this.markdown.split(/\r\n|\n\r|\n|\r/); + var linelength = codelines.length; + var codeblock = ''; + + for(i=0;i+ +++ + ++ ++ + ++ + `, + mounted: function(){ + + this.$refs.markdown.focus(); + + autosize(document.querySelectorAll('textarea')); + + this.$emit('updateMarkdownEvent', '---'); + }, + methods: { + updatemarkdown: function(event) + { + var emptyline = /^\s*$(?:\r\n?|\n)/gm; + + if(event.target.value.search(emptyline) > -1) + { + this.$emit('updateMarkdownEvent', event.target.value.trim()); + this.$emit('saveBlockEvent'); + } + + this.$emit('updateMarkdownEvent', event.target.value); + }, + }, +}) + +bloxeditor.component('toc-component', { + props: ['markdown', 'disabled', 'index'], + template: `+`, + mounted: function(){ + + this.$refs.markdown.focus(); + + autosize(document.querySelectorAll('textarea')); + + this.$emit('updateMarkdownEvent', '[TOC]'); + }, + methods: { + updatemarkdown: function(event) + { + var emptyline = /^\s*$(?:\r\n?|\n)/gm; + + if(event.target.value.search(emptyline) > -1) + { + this.$emit('updateMarkdownEvent', event.target.value.trim()); + this.$emit('saveBlockEvent'); + } + + this.$emit('updateMarkdownEvent', event.target.value); + }, + }, +}) + +bloxeditor.component('quote-component', { + props: ['markdown', 'disabled', 'index'], + template: `+ ++ ++`, + data: function(){ + return { + prefix: '> ', + quote: '' + } + }, + mounted: function(){ + + this.$refs.markdown.focus(); + + if(this.markdown) + { + var lines = this.markdown.match(/^.*([\n\r]+|$)/gm); + for (var i = 0; i < lines.length; i++) { + lines[i] = lines[i].replace(/(^[\> ]+)/mg, ''); + } + + this.quote = lines.join(''); + } + this.$nextTick(function () { + autosize(document.querySelectorAll('textarea')); + }); + }, + methods: { + updatemarkdown: function(value) + { + this.quote = value; + + let emptyline = /^\s*$(?:\r\n?|\n)/gm; + + if(value.search(emptyline) > -1) + { + + let cleanvalue = value.trim(); + let lines = cleanvalue.match(/^.*([\n\r]|$)/gm); + let quote = this.prefix + lines.join(this.prefix); + + this.$emit('updateMarkdownEvent', quote); + this.$emit('saveBlockEvent'); + } + else + { + let lines = value.match(/^.*([\n\r]|$)/gm); + let quote = this.prefix + lines.join(this.prefix); + + this.$emit('updateMarkdownEvent', quote); + } + } + } +}) + +bloxeditor.component('notice-component', { + props: ['markdown', 'disabled', 'index'], + template: `+ +++ + ++`, + data: function(){ + return { + prefix: '!', + notice: '', + noteclass: 'note1' + } + }, + mounted: function(){ + + this.$refs.markdown.focus(); + + if(this.markdown) + { + this.prefix = this.getNoticePrefix(this.markdown); + + var lines = this.markdown.match(/^.*([\n\r]+|$)/gm); + for (var i = 0; i < lines.length; i++) + { + lines[i] = lines[i].replace(/(^[\! ]+(?!\[))/mg, ''); + } + + this.notice = lines.join(''); + this.noteclass = 'note'+this.prefix.length; + } + this.$nextTick(function () { + autosize(document.querySelectorAll('textarea')); + }); + }, + methods: { + noticedown: function() + { + this.prefix = this.getNoticePrefix(this.markdown); + + /* initially it is empty string, so we add it here if user clicks downgrade button */ + if(this.prefix == '') + { + this.prefix = '!'; + } + + this.prefix = this.prefix + '!'; + if(this.prefix.length > 4) + { + this.prefix = '!'; + } + this.noteclass = 'note' + (this.prefix.length); + this.updatemarkdown(this.notice); + }, + getNoticePrefix: function(str) + { + var prefix = ''; + if(str === undefined) + { + return prefix; + } + for(var i = 0; i < str.length; i++) + { + if(str[i] != '!'){ return prefix } + prefix += '!'; + } + return prefix; + }, + updatemarkdown: function(value) + { + this.notice = value; + + var lines = value.match(/^.*([\n\r]|$)/gm); + + var notice = this.prefix + ' ' + lines.join(this.prefix+' '); + + this.$emit('updateMarkdownEvent', notice); + } + }, +}) + +bloxeditor.component('table-component', { + props: ['markdown', 'disabled', 'index'], + data: function(){ + return { + table: [ + ['0', '1', '2'], + ['1', 'Head', 'Head'], + ['2', 'cell', 'cell'], + ['3', 'cell', 'cell'], + ], + editable: 'editable', + noteditable: 'noteditable', + cellcontent: '', + columnbar: false, + rowbar: false, + tablekey: 1, + } + }, + template: `+ ++ + ++`, + mounted: function(){ + + this.$refs.markdown.focus(); + + if(this.markdown) + { + this.generateTable(this.markdown); + } + }, + methods: { + generateTable(markdown) + { + var table = []; + var lines = markdown.split("\n"); + var length = lines.length + var c = 1; + + for(i = 0; i < length; i++) + { + if(i == 1){ continue } + + var line = lines[i].trim(); + var row = line.split("|").map(function(cell){ + return cell.trim(); + }); + if(row[0] == ''){ row.shift() } + if(row[row.length-1] == ''){ row.pop() } + if(i == 0) + { + var rlength = row.length; + var row0 = []; + for(y = 0; y <= rlength; y++) { row0.push(y) } + table.push(row0); + } + row.splice(0,0,c); + c++; + table.push(row); + } + this.table = table; + }, + enter() + { + return false; + }, + updatedata(event,col,row) + { + this.table[row][col] = event.target.innerText; + this.markdowntable(); + }, + switchcolumnbar(event, value) + { + this.rowbar = false; + (this.columnbar == value || value == 0) ? this.columnbar = false : this.columnbar = value; + }, + switchrowbar(event, value) + { + this.columnbar = false; + (this.rowbar == value || value == 0 || value == 1 )? this.rowbar = false : this.rowbar = value; + }, + addaboverow(event, index) + { + var row = []; + var cols = this.table[0].length; + for(var i = 0; i < cols; i++){ row.push("new"); } + this.table.splice(index,0,row); + this.markdowntable(); + }, + addbelowrow(event, index) + { + var row = []; + var cols = this.table[0].length; + for(var i = 0; i < cols; i++){ row.push("new"); } + this.table.splice(index+1,0,row); + this.markdowntable(); + }, + deleterow(event, index) + { + this.table.splice(index,1); + this.markdowntable(); + }, + addrightcolumn(event, index) + { + var tableLength = this.table.length; + for (var i = 0; i < tableLength; i++) + { + this.table[i].splice(index+1,0,"new"); + } + this.markdowntable(); + }, + addleftcolumn(event, index) + { + var tableLength = this.table.length; + for (var i = 0; i < tableLength; i++) + { + this.table[i].splice(index,0,"new"); + } + this.markdowntable(); + }, + deletecolumn(event, index) + { + var tableLength = this.table.length; + for (var i = 0; i < tableLength; i++) + { + this.table[i].splice(index,1); + } + this.markdowntable(); + }, + markdowntable() + { + var compmarkdown = ''; + var separator = '\n|'; + var rows = this.table.length; + var cols = this.table[0].length; + + for(var i = 0; i < cols; i++) + { + if(i == 0){ continue; } + separator += '---|'; + } + + for(var i = 0; i < rows; i++) + { + var row = this.table[i]; + + if(i == 0){ continue; } + + for(var y = 0; y < cols; y++) + { + if(y == 0){ continue; } + + var value = row[y].trim(); + + if(y == 1) + { + compmarkdown += '\n| ' + value + ' | '; + } + else + { + compmarkdown += value + ' | '; + } + } + if(i == 1) { compmarkdown = compmarkdown + separator; } + } + + compmarkdown = compmarkdown.trim(); + + this.$emit('updateMarkdownEvent', compmarkdown); + + this.generateTable(compmarkdown); + }, + }, +}) + +bloxeditor.component('definition-component', { + props: ['markdown', 'disabled', 'index', 'load'], + data: function(){ + return { + definitionList: [], + } + }, + template: `+ +++
++ + ++ + + +{{value}} + +++add left column+add right column+delete column+{{ value }} + ++ +++ {{ value }} +add row above+add row below+delete row++`, + mounted: function(){ + if(this.markdown) + { + var definitionList = this.markdown.replace("\r\n", "\n"); + definitionList = definitionList.replace("\r", "\n"); + definitionList = definitionList.split("\n\n"); + + for(var i=0; i < definitionList.length; i++) + { + var definitionBlock = definitionList[i].split("\n"); + var term = definitionBlock[0]; + var descriptions = []; + var description = false; + + if(term.trim() == '') + { + continue; + } + + /* parse one or more descriptions */ + for(var y=0; y < definitionBlock.length; y++) + { + if(y == 0) + { + continue; + } + + if(definitionBlock[y].substring(0, 2) == ": ") + { + /* if there is another description in the loop, then push that first then start a new one */ + if(description) + { + descriptions.push(description); + } + var cleandefinition = definitionBlock[y].substr(1).trim(); + var description = cleandefinition; + } + else + { + description += "\n" + definitionBlock[y]; + } + } + + if(description) + { + descriptions.push(description); + } + this.definitionList.push({'term': term ,'descriptions': descriptions, 'id': i}); + } + } + else + { + this.addDefinition(); + } + }, + methods: { + enter() + { + return false; + }, + updateterm(event, dtindex) + { + let content = event.target.value.trim(); + if(content != '') + { + this.definitionList[dtindex].term = content; + } + }, + updatedescription(event, dtindex, ddindex) + { + let content = event.target.value.trim(); + if(content != '') + { + this.definitionList[dtindex].descriptions[ddindex] = content; + } + }, + addDefinition() + { + var id = this.definitionList.length; + this.definitionList.push({'term': '', 'descriptions': [''], 'id': id}); + }, + deleteDefinition(event,dtindex) + { + this.definitionList.splice(dtindex,1); + this.updateMarkdown(); + }, + addItem(event,dtindex) + { + this.definitionList[dtindex].descriptions.push(''); + }, + deleteItem(event,dtindex,ddindex) + { + if(this.definitionList[dtindex].descriptions.length == 1) + { + this.deleteDefinition(false,dtindex); + } + else + { + this.definitionList[dtindex].descriptions.splice(ddindex,1); + this.updateMarkdown(); + } + }, + moveDefinition(evt) + { + this.updateMarkdown(); + }, + updateMarkdown() + { + var dllength = this.definitionList.length; + var markdown = ''; + + for(i = 0; i < dllength; i++) + { + let term = this.definitionList[i].term; + if(term != '') + { + markdown = markdown + term; + var ddlength = this.definitionList[i].descriptions.length; + for(y = 0; y < ddlength; y++) + { + let description = this.definitionList[i].descriptions[y]; + if(description != '') + { + markdown = markdown + "\n: " + description; + } + } + markdown = markdown + "\n\n"; + } + } + this.$emit('updateMarkdownEvent', markdown); + }, + }, +}) + +bloxeditor.component('inline-formats', { + template: `+ +++ + +++ ++++ +++++ + + ++ + Add description ++ + Add definition ++++`, + data: function(){ + return { + formatBar: false, + formatElements: 0, + startX: 0, + startY: 0, + x: 0, + y: 0, + z: 150, + textComponent: '', + selectedText: '', + startPos: false, + endPos: false, + showInlineFormat: false, + link: false, + stopNext: false, + url: '', + code: (formatConfig.indexOf("code") > -1) ? true : false, + math: (formatConfig.indexOf("math") > -1) ? true : false, + } + }, + mounted: function() { + this.formatBar = document.getElementById('formatBar'); + console.info(this.formatBar); + window.addEventListener('mouseup', this.onMouseup), + window.addEventListener('mousedown', this.onMousedown) + }, + beforeDestroy: function() { + window.removeEventListener('mouseup', this.onMouseup), + window.removeEventListener('mousedown', this.onMousedown) + }, + computed: { + styleObject() { + return { + 'left': this.x + 'px', + 'top': this.y + 'px', + 'width': this.z + 'px' + } + }, + highlightableEl: function () { + return this.$slots.default[0].elm + } + }, + methods: { + onMousedown: function(event) { + this.startX = event.offsetX; + this.startY = event.offsetY; + }, + onMouseup: function(event) { + + /* if click is on format popup */ + if(this.formatBar.contains(event.target) || this.stopNext) + { + this.stopNext = false; + return; + } + + /* if click is outside the textarea * + if(!this.highlightableEl.contains(event.target)) + { + this.showInlineFormat = false; + this.link = false; + return; + } + */ + + this.textComponent = document.getElementsByClassName("iformat")[0]; + if(typeof this.textComponent == "undefined") + { + return; + } + + /* grab the selected text */ + if (document.selection != undefined) + { + this.textComponent.focus(); + var sel = document.selection.createRange(); + selectedText = sel.text; + } + /* Mozilla version */ + else if (this.textComponent.selectionStart != undefined) + { + this.startPos = this.textComponent.selectionStart; + this.endPos = this.textComponent.selectionEnd; + selectedText = this.textComponent.value.substring(this.startPos, this.endPos) + } + + var trimmedSelection = selectedText.replace(/\s/g, ''); + + if(trimmedSelection.length == 0) + { + this.showInlineFormat = false; + this.link = false; + return; + } + + /* determine the width of selection to position the format bar */ + if(event.offsetX > this.startX) + { + var width = event.offsetX - this.startX; + this.x = event.offsetX - (width/2); + } + else + { + var width = this.startX - event.offsetX; + this.x = event.offsetX + (width/2); + } + + this.y = event.offsetY - 15; + + /* calculate the width of the format bar */ + this.formatElements = document.getElementsByClassName('inlineFormatItem').length; + this.z = this.formatElements * 30; + + this.showInlineFormat = true; + this.selectedText = selectedText; + }, + formatBold() + { + content = this.textComponent.value; + content = content.substring(0, this.startPos) + '**' + this.selectedText + '**' + content.substring(this.endPos, content.length); + eventBus.$emit('inlineFormat', content); + this.showInlineFormat = false; + }, + formatItalic() + { + content = this.textComponent.value; + content = content.substring(0, this.startPos) + '_' + this.selectedText + '_' + content.substring(this.endPos, content.length); + eventBus.$emit('inlineFormat', content); + this.showInlineFormat = false; + }, + formatCode() + { + content = this.textComponent.value; + content = content.substring(0, this.startPos) + '`' + this.selectedText + '`' + content.substring(this.endPos, content.length); + eventBus.$emit('inlineFormat', content); + this.showInlineFormat = false; + }, + formatMath() + { + content = this.textComponent.value; + content = content.substring(0, this.startPos) + '$' + this.selectedText + '$' + content.substring(this.endPos, content.length); + eventBus.$emit('inlineFormat', content); + this.showInlineFormat = false; + }, + formatLink() + { + if(this.url == "") + { + this.stopNext = true; + this.link = false; + this.showInlineFormat = false; + return; + } + content = this.textComponent.value; + content = content.substring(0, this.startPos) + '[' + this.selectedText + '](' + this.url + ')' + content.substring(this.endPos, content.length); + eventBus.$emit('inlineFormat', content); + this.showInlineFormat = false; + this.link = false; + }, + openLink() + { + this.link = true; + this.url = ''; + this.z = 200; + this.$nextTick(() => this.$refs.urlinput.focus()); + }, + closeLink() + { + this.stopNext = true; + this.link = false; + this.url = ''; + this.showInlineFormat = false; + } + } +}) + +bloxeditor.component('image-component', { + props: ['markdown', 'disabled', 'index'], + template: `+++ + + + + + + +++ + + + + + + + + + + + + + + +++ + +`, + data: function(){ + return { + compmarkdown: '', + saveimage: false, + maxsize: 5, // megabyte + imgpreview: false, + showmedialib: false, + load: false, + imgmeta: false, + imgalt: '', + imgtitle: '', + imgcaption: '', + imglink: '', + imgclass: 'center', + imgid: '', + imgwidth: 0, + imgheight: 0, + originalwidth: 0, + originalheight: 0, + imgloading: 'lazy', + imgattr: '', + imgfile: '', + showresize: true, + noresize: false, + } + }, + mounted: function(){ + + this.$refs.markdown.focus(); + + if(this.markdown) + { + this.showresize = false; + + this.imgmeta = true; + + var imgmarkdown = this.markdown; + + var imgcaption = imgmarkdown.match(/\*.*?\*/); + if(imgcaption) + { + this.imgcaption = imgcaption[0].slice(1,-1); + + imgmarkdown = imgmarkdown.replace(this.imgcaption,''); + imgmarkdown = imgmarkdown.replace(/\r?\n|\r/g,''); + + } + + if(this.markdown[0] == '[') + { + var imglink = this.markdown.match(/\(.*?\)/g); + if(imglink[1]) + { + this.imglink = imglink[1].slice(1,-1); + + imgmarkdown = imgmarkdown.replace(imglink[1],''); + imgmarkdown = imgmarkdown.slice(1, -1); + + } + } + + var imgalt = imgmarkdown.match(/\[.*?\]/); + if(imgalt) + { + this.imgalt = imgalt[0].slice(1,-1); + } + + var imgattr = imgmarkdown.match(/\{.*?\}/); + if(imgattr) + { + imgattr = imgattr[0].slice(1,-1); + imgattr = imgattr.trim(); + imgattr = imgattr.split(' '); + + var widthpattern = /width=\"?([0-9]*)[a-zA-Z%]*\"?/; + var heightpattern = /height=\"?([0-9]*)[a-zA-Z%]*\"?/; + var lazypattern = /loading=\"?([0-9a-zA-Z]*)\"?/; + + for (var i = 0; i < imgattr.length; i++) + { + var widthattr = imgattr[i].match(widthpattern); + var heightattr = imgattr[i].match(heightpattern); + var lazyattr = imgattr[i].match(lazypattern); + + if(imgattr[i].charAt(0) == '.') + { + this.imgclass = imgattr[i].slice(1); + } + else if(imgattr[i].charAt(0) == '#') + { + this.imgid = imgattr[i].slice(1); + } + else if(widthattr) + { + this.imgwidth = parseInt(widthattr[1]); + } + else if(heightattr) + { + this.imgheight = parseInt(heightattr[1]); + } + else if(lazyattr && lazyattr[1] != '') + { + this.imgloading = lazyattr[1]; + } + else + { + this.imgattr += ' ' + imgattr[i]; + } + } + } + + var imgfile = imgmarkdown.match(/\(.*?\)/); + if(imgfile) + { + imgfilestring = imgfile[0]; + var imgtitle = imgfilestring.match(/\".*?\"/); + if(imgtitle) + { + this.imgtitle = imgtitle[0].slice(1,-1); + imgfilestring = imgfilestring.replace(imgtitle[0], ''); + } + + this.imgfile = imgfilestring.slice(1,-1).trim(); + this.imgpreview = data.urlinfo.baseurl + '/' + this.imgfile; + } + + this.createmarkdown(); + } + }, + methods: { + updatemarkdown(event) + { + this.$emit('updateMarkdownEvent', event.target.value); + }, + createmarkdown() + { + if(this.imgpreview) + { + var img = new Image(); + img.src = this.imgpreview; + + var self = this; + + img.onload = function(){ + + self.originalwidth = img.width; + self.originalheight = img.height; + self.originalratio = self.originalwidth / self.originalheight; + + self.calculatewidth(); + self.calculateheight(); + self.createmarkdownimageloaded(); + } + } + else + { + this.createmarkdownimageloaded(); + } + }, + createmarkdownimageloaded() + { + var errors = false; + + var imgmarkdown = ''; + + if(this.imgalt.length < 101) + { + imgmarkdown = '![' + this.imgalt + ']'; + } + else + { + errors = 'Maximum size of image alt-text is 100 characters'; + imgmarkdown = '![]'; + } + + if(this.imgtitle != '') + { + if(this.imgtitle.length < 101) + { + imgmarkdown = imgmarkdown + '(' + this.imgfile + ' "' + this.imgtitle + '")'; + } + else + { + errors = 'Maximum size of image title is 100 characters'; + } + } + else + { + imgmarkdown = imgmarkdown + '(' + this.imgfile + ')'; + } + + var imgattr = ''; + + if(this.imgid != '') + { + if(this.imgid.length < 100) + { + imgattr = imgattr + ' #' + this.imgid; + } + else + { + errors = 'Maximum size of image id is 100 characters'; + } + } + if(this.imgclass != '') + { + if(this.imgclass.length < 100) + { + imgattr = imgattr + ' .' + this.imgclass; + } + else + { + errors = 'Maximum size of image class is 100 characters'; + } + } + if(this.imgloading != '') + { + imgattr = imgattr + ' loading="' + this.imgloading + '"'; + } + if(this.imgwidth != '') + { + imgattr = imgattr + ' width="' + this.imgwidth + '"'; + } + if(this.imgheight != '') + { + imgattr = imgattr + ' height="' + this.imgheight + '"'; + } + if(this.imgattr != '') + { + imgattr += this.imgattr; + } + if(imgattr != '') + { + imgmarkdown = imgmarkdown + '{' + imgattr.trim() + '}'; + } + + if(this.imglink != '') + { + if(this.imglink.length < 101) + { + imgmarkdown = '[' + imgmarkdown + '](' + this.imglink + ')'; + } + else + { + errors = 'Maximum size of image link is 100 characters'; + } + } + + if(this.imgcaption != '') + { + if(this.imgcaption.length < 140) + { + imgmarkdown = imgmarkdown + '\n*' + this.imgcaption + '*'; + } + else + { + errors = 'Maximum size of image caption is 140 characters'; + } + } + + if(this.noresize === true) + { + imgmarkdown = imgmarkdown + '|noresize'; + } + + if(errors) + { + console.info(errors); +// this.$parent.freezePage(); +// publishController.errors.message = errors; + } + else + { + this.compmarkdown = imgmarkdown; +// publishController.errors.message = false; +// this.$parent.activatePage(); + this.$emit('updateMarkdownEvent', imgmarkdown); + } + }, + calculatewidth() + { + this.setdefaultsize(); + if(this.imgheight && this.imgheight > 0) + { + this.imgwidth = Math.round(this.imgheight * this.originalratio); + } + else + { + this.imgwidth = ''; + } + }, + calculateheight() + { + this.setdefaultsize(); + if(this.imgwidth && this.imgwidth > 0) + { + this.imgheight = Math.round(this.imgwidth / this.originalratio); + } + else + { + this.imgheight = ''; + } + }, + setdefaultsize() + { + if( + (this.imgheight == 0 && this.imgwidth == 0) || + (this.imgheight > this.originalheight) || + (this.imgwidth > this.originalwidth) + ) + { + this.imgwidth = this.originalwidth; + this.imgheight = this.originalheight; + } + }, + changewidth() + { + this.calculateheight(); + this.createmarkdownimageloaded(); + }, + changeheight() + { + this.calculatewidth(); + this.createmarkdownimageloaded(); + }, + openmedialib() + { + this.showresize = false; + this.noresize = false; + this.showmedialib = true; + }, + isChecked(classname) + { + if(this.imgclass == classname) + { + return ' checked'; + } + }, + onFileChange( e ) + { + if(e.target.files.length > 0) + { + let imageFile = e.target.files[0]; + let size = imageFile.size / 1024 / 1024; + + if (!imageFile.type.match('image.*')) + { + alert("wrong format"); +// publishController.errors.message = "Only images are allowed."; + } + else if (size > this.maxsize) + { + alert("wrong size"); +// publishController.errors.message = "The maximal size of images is " + this.maxsize + " MB"; + } + else + { + self = this; + + self.load = true; + self.showresize = true; + self.noresize = false; + self.imgwidth = 0; + self.imgheight = 0; + + let reader = new FileReader(); + reader.readAsDataURL(imageFile); + reader.onload = function(e) { + + self.imgpreview = e.target.result; + + self.createmarkdown(); + + tmaxios.post('/api/v1/image',{ + 'url': data.urlinfo.route, + 'image': e.target.result, + 'name': imageFile.name, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + }) + .then(function (response) { + + self.load = false; + self.saveimage = true; + // self.$parent.activatePage(); + + self.imgmeta = true; + self.imgfile = response.data.name; + // self.$emit('updateMarkdownEvent', ''); + }) + .catch(function (error) + { + /* + if(httpStatus == 400) + { + self.activatePage(); + publishController.errors.message = "Looks like you are logged out. Please login and try again."; + } + */ + + if(error.response) + { + alert("errror in response"); + // publishController.errors.message = error.response.data.errors.message; + } + + }); + } + } + } + }, + saveBlock() + { + /* saves image, after that saves markdown */ + if(!this.imgfile) + { + alert("no file"); + return; + } + + if(!this.saveimage) + { + this.saveMarkdown(); + } + else + { + var self = this; + + tmaxios.put('/api/v1/image',{ + 'url': data.urlinfo.route, + 'imgfile': this.imgfile, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + }) + .then(function (response) + { + self.imgfile = response.data.path; + // self.createmarkdownimageloaded(); + self.saveMarkdown(); + + // console.info(response.data.path); + return; + // self.$root.$data.content = response.data.content; + // self.closeEditor(); + }) + .catch(function (error) + { + if(error.response) + { + console.info(error.response); + // publishController.errors.message = error.response.data.errors.message; + } + }); + } + }, + saveMarkdown() + { + this.createmarkdownimageloaded(); + + var self = this; + + tmaxios.put('/api/v1/block',{ + 'url': data.urlinfo.route, + 'block_id': this.index, + 'markdown': this.compmarkdown, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + }) + .then(function (response) + { + self.$root.$data.content = response.data.content; + eventBus.$emit('closeComponents'); + }) + .catch(function (error) + { + if(error.response) + { + publishController.errors.message = error.response.data.errors.message; + } + }); + }, + } +}) + +bloxeditor.component('file-component', { + props: ['markdown', 'disabled', 'index'], + template: `++ ++ ++ +drag a picture or click to select
++ +++++
++ +`, + data: function(){ + return { + maxsize: 20, // megabyte + showmedialib: false, + load: false, + filemeta: false, + filetitle: '', + fileextension: '', + fileurl: '', + fileid: '', + userroles: ['all'], + selectedrole: '', + } + }, + mounted: function(){ + + this.$refs.markdown.focus(); + + if(this.markdown) + { + this.filemeta = true; + + var filemarkdown = this.markdown; + + var filetitle = filemarkdown.match(/\[.*?\]/); + if(filetitle) + { + filemarkdown = filemarkdown.replace(filetitle[0],''); + this.filetitle = filetitle[0].slice(1,-1); + } + + var fileattr = filemarkdown.match(/\{.*?\}/); + var fileextension = filemarkdown.match(/file-(.*)?\}/); + if(fileattr && fileextension) + { + filemarkdown = filemarkdown.replace(fileattr[0],''); + this.fileextension = fileextension[1].trim(); + } + + var fileurl = filemarkdown.match(/\(.*?\)/g); + if(fileurl) + { + filemarkdown = filemarkdown.replace(fileurl[0],''); + this.fileurl = fileurl[0].slice(1,-1); + } + } + + this.getrestriction(); + }, + methods: { + openmedialib: function() + { + this.showmedialib = true; + }, + isChecked: function(classname) + { + if(this.fileclass == classname) + { + return ' checked'; + } + }, + updatemarkdown: function(event, url) + { + this.$emit('updateMarkdownEvent', event.target.value); + this.getrestriction(url); + }, + createmarkdown: function() + { + var errors = false; + + if(this.filetitle.length < 101) + { + filemarkdown = '[' + this.filetitle + ']'; + } + else + { + errors = 'Maximum size of file-text is 100 characters'; + filemarkdown = '[]'; + } + if(this.fileurl != '') + { + if(this.fileurl.length < 101) + { + filemarkdown = '[' + this.filetitle + '](' + this.fileurl + ')'; + } + else + { + errors = 'Maximum size of file link is 100 characters'; + } + } + if(this.fileextension != '') + { + filemarkdown = filemarkdown + '{.tm-download file-' + this.fileextension + '}'; + } + if(errors) + { + alert("errors"); +// this.$parent.freezePage(); +// publishController.errors.message = errors; + } + else + { +// publishController.errors.message = false; +// this.$parent.activatePage(); + this.$emit('updateMarkdownEvent', filemarkdown); + this.compmarkdown = filemarkdown; + } + }, + getrestriction: function(url) + { + var fileurl = this.fileurl; + if(url) + { + fileurl = url; + } + + var myself = this; + + tmaxios.get('/api/v1/filerestrictions',{ + params: { + 'url': data.urlinfo.route, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + 'filename': fileurl, + } + }) + .then(function (response) { + myself.userroles = ['all']; + myself.userroles = myself.userroles.concat(response.data.userroles); + myself.selectedrole = response.data.restriction; + }) + .catch(function (error) + { + alert("error response"); + }); + }, + updaterestriction: function() + { + tmaxios.post('/api/v1/filerestrictions',{ + 'url': data.urlinfo.route, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + 'filename': this.fileurl, + 'role': this.selectedrole, + }) + .then(function (response) {}) + .catch(function (error){ alert("reponse error")}); + }, + onFileChange: function( e ) + { + if(e.target.files.length > 0) + { + let uploadedFile = e.target.files[0]; + let size = uploadedFile.size / 1024 / 1024; + + if (size > this.maxsize) + { + alert("error size"); + // publishController.errors.message = "The maximal size of a file is " + this.maxsize + " MB"; + } + else + { + self = this; + +// self.$parent.freezePage(); +// self.$root.$data.file = true; + self.load = true; + + let reader = new FileReader(); + reader.readAsDataURL(uploadedFile); + reader.onload = function(e) { + + tmaxios.post('/api/v1/file',{ + 'url': data.urlinfo.route, + 'file': e.target.result, + 'name': uploadedFile.name, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + }) + .then(function (response) { + + self.load = false; +// self.$parent.activatePage(); + + self.filemeta = true; + self.filetitle = response.data.fileinfo.title; + self.fileextension = response.data.fileinfo.extension; + self.fileurl = response.data.fileinfo.url; + self.selectedrole = ''; + + self.createmarkdown(); + }) + .catch(function (error) + { + self.load = false; +// self.$parent.activatePage(); + if(error.response) + { + alert("error response") +// publishController.errors.message = error.response.data.errors; + } + }); + } + } + } + } + } +}) + +bloxeditor.component('shortcode-component', { + props: ['markdown', 'disabled', 'index'], + data: function(){ + return { + shortcodedata: false, + shortcodename: '', + compmarkdown: '', + } + }, + template: `++ ++ ++ ++ + upload file +
++ +++ ++`, + mounted: function(){ + + var myself = this; + + tmaxios.get('/api/v1/shortcodedata',{ + params: { + 'url': data.urlinfo.route, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + } + }) + .then(function (response) { + if(response.data.shortcodedata !== false) + { + myself.shortcodedata = response.data.shortcodedata; + myself.parseshortcode(); + } + }) + .catch(function (error) + { + if(error.response) + { + + } + }); + }, + methods: { + parseshortcode() + { + if(this.markdown) + { + var shortcodestring = this.markdown.trim(); + shortcodestring = shortcodestring.slice(2,-2); + this.shortcodename = shortcodestring.substr(0,shortcodestring.indexOf(' ')); + + var regexp = /(\w+)\s*=\s*("[^"]*"|\'[^\']*\'|[^"\'\\s>]*)/g; + var matches = shortcodestring.matchAll(regexp); + matches = Array.from(matches); + matchlength = matches.length; + + if(matchlength > 0) + { + for(var i=0;i+ ++++ ++ + +++ + + +++ + ++{{ markdown }}++ + ++ `, + data: function(){ + return { + edited: false, + url: false, + videoid: false, + param: false, + path: false, + provider: false, + providerurl: false, + compmarkdown: '', + } + }, + mounted: function(){ + + this.$refs.markdown.focus(); + + if(this.markdown) + { + this.parseImageMarkdown(this.markdown); + } + }, + methods: { + generateMarkdown() + { + this.compmarkdown = '{#' + this.videoid + ' .' + this.provider + '}'; + }, + parseImageMarkdown(imageMarkdown) + { + let regexpurl = /\((.*)(".*")\)/; + let match = imageMarkdown.match(regexpurl); + let imageUrl = match[1]; + + let regexprov = /live\/(.*?)-/; + let matchprov = imageUrl.match(regexprov); + this.provider = matchprov[1]; + + if(this.provider == 'youtube') + { + this.providerurl = "https://www.youtube.com/watch"; + this.param = "v="; + } + + let videoid = imageMarkdown.match(/#.*? /); + if(videoid) + { + this.videoid = videoid[0].trim().substring(1); + } + + this.updatemarkdown(this.providerurl + "?" + this.param + this.videoid); + }, + parseUrl(url) + { + alert("parse: " + url); + + let urlparts = url.split('?'); + let urlParams = new URLSearchParams(urlparts[1]); + + this.providerurl = urlparts[0]; + + if(urlParams.has("v")) + { + this.param = "v="; + this.videoid = urlParams.get("v"); + this.provider = "youtube"; + } + if(this.provider != "youtube") + { + this.updatemarkdown(""); + alert("we only support youtube right now"); + } + }, + updatemarkdown(url) + { + alert("update: " + url); + this.edited = true; + this.url = url; + this.parseUrl(url); + this.generateMarkdown(); + this.$emit('updateMarkdownEvent', url); + }, + saveBlock() + { + if(!this.edited) + { + eventBus.$emit('closeComponents'); + return; + } + var self = this; + + tmaxios.post('/api/v1/video',{ + 'url': data.urlinfo.route, + 'videourl': this.url, + 'provider': this.provider, + 'providerurl': this.providerurl, + 'videoid': this.videoid, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + }) + .then(function (response) + { + self.path = response.data.path; + self.saveMarkdown(); + }) + .catch(function (error) + { + if(error.response) + { + console.info(error.response); + } + }); + }, + saveMarkdown() + { + this.generateMarkdown(); + + var myself = this; + + tmaxios.put('/api/v1/block',{ + 'url': data.urlinfo.route, + 'block_id': this.index, + 'markdown': this.compmarkdown, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + }) + .then(function (response) + { + myself.$root.$data.content = response.data.content; + eventBus.$emit('closeComponents'); + }) + .catch(function (error) + { + if(error.response) + { + publishController.errors.message = error.response.data.errors.message; + } + }); + }, + }, +}) diff --git a/system/typemill/author/js/vue-blox-config.js b/system/typemill/author/js/vue-blox-config.js new file mode 100644 index 0000000..3609268 --- /dev/null +++ b/system/typemill/author/js/vue-blox-config.js @@ -0,0 +1,136 @@ +const determiner = { + + hr: function(block,lines,firstChar,secondChar,thirdChar){ + if(lines[0] == '---') + { + return "hr-component"; + } + return false; + }, + toc: function(block,lines,firstChar,secondChar,thirdChar){ + if(lines[0] == '[TOC]') + { + return "toc-component"; + } + return false; + }, + olist: function(block,lines,firstChar,secondChar,thirdChar){ + if(block.match(/^\d+\./)) + { + return "olist-component"; + } + return false; + }, + definition: function(block,lines,firstChar,secondChar,thirdChar){ + if(lines.length > 1 && lines[1].substr(0,2) == ': ') + { + return "definition-component"; + } + return false; + }, + table: function(block,lines,firstChar,secondChar,thirdChar){ + if(lines.length > 2 && lines[0].indexOf('|') != -1 && /[\-\|: ]{3,}$/.test(lines[1])) + { + return "table-component"; + } + return false; + }, + quote: function(block,lines,firstChar,secondChar,thirdChar){ + if(firstChar == '>') + { + return "quote-component"; + } + return false; + }, + headline: function(block,lines,firstChar,secondChar,thirdChar){ + if(firstChar == '#') + { + return "headline-component"; + } + return false; + }, + /* + video: function(block,lines,firstChar,secondChar,thirdChar){ + if( (firstChar == '!' && secondChar == '[' && lines[0].indexOf('.youtube') != -1) || (firstChar == '[' && secondChar == '!' && lines[0].indexOf('.youtube') != -1) ) + { + return "video-component"; + } + return false; + }, + */ + image: function(block,lines,firstChar,secondChar,thirdChar){ + if( (firstChar == '!' && secondChar == '[' ) || (firstChar == '[' && secondChar == '!' && thirdChar == '[') ) + { + if(block.indexOf("-video") != -1) + { + return "video-component"; + } + return "image-component"; + } + return false; + }, + file: function(block,lines,firstChar,secondChar,thirdChar){ + if( (firstChar == '[' && lines[0].indexOf('{.tm-download') != -1) ) + { + return "file-component"; + } + return false; + }, + code: function(block,lines,firstChar,secondChar,thirdChar){ + if( firstChar == '`' && secondChar == '`' && thirdChar == '`') + { + return "code-component"; + } + return false; + }, + shortcode: function(block,lines,firstChar,secondChar,thirdChar){ + if( firstChar == '[' && secondChar == ':') + { + return "shortcode-component"; + } + return false; + }, + notice: function(block,lines,firstChar,secondChar,thirdChar){ + if( firstChar == '!' && ( secondChar == '!' || secondChar == ' ') ) + { + return "notice-component"; + } + return false; + }, + ulist: function(block,lines,firstChar,secondChar,thirdChar){ + if( (firstChar == '*' || firstChar == '-' || firstChar == '+') && secondChar == ' ') + { + return "ulist-component"; + } + return false; + } +} + +const bloxFormats = { + markdown: { label: '', title: 'Paragraph', component: 'markdown-component' }, + headline: { label: '', title: 'Headline', component: 'headline-component' }, + ulist: { label: '', title: 'Bullet List', component: 'ulist-component' }, + olist: { label: '', title: 'Numbered List', component: 'olist-component' }, + table: { label: '', title: 'Table', component: 'table-component' }, + quote: { label: '', title: 'Quote', component: 'quote-component' }, + notice: { label: '', title: 'Notice', component: 'notice-component' }, + image: { label: '', title: 'Image', component: 'image-component' }, + video: { label: '', title: 'Video', component: 'video-component' }, + file: { label: '', title: 'File', component: 'file-component' }, + toc: { label: '', title: 'Table of Contents', component: 'toc-component' }, + hr: { label: '', title: 'Horizontal Line', component: 'hr-component' }, + definition: { label: '', title: 'Definition List', component: 'definition-component' }, + code: { label: '', title: 'Code', component: 'code-component' }, + shortcode: { label: '', title: 'Shortcode', component: 'shortcode-component' }, +}; + +const formatConfig = data.settings.formats; +const activeFormats = []; + +for(var i = 0; i < formatConfig.length; i++) +{ + if(bloxFormats[formatConfig[i]] !== undefined) + { + activeFormats.push(bloxFormats[formatConfig[i]]); + } +} diff --git a/system/typemill/author/js/vue-blox.js b/system/typemill/author/js/vue-blox.js new file mode 100644 index 0000000..9990d89 --- /dev/null +++ b/system/typemill/author/js/vue-blox.js @@ -0,0 +1,449 @@ +const bloxeditor = Vue.createApp({ + template: `+ + ++ + + `, + data() { + return { + content: data.content, + freeze: false, + } + }, + computed: + { + dragOptions() + { + return { + animation: 150, + disabled: this.freeze, + ghostClass: "ghost", + }; + }, + }, + mounted() { + + document.getElementById("initial-content").remove(); + + eventBus.$on('freeze', this.freezeOn ); + eventBus.$on('unfreeze', this.freezeOff ); + + eventBus.$on('content', content => { + this.content = content; + }); + }, + methods: { + checkMove(event) + { + if(event.draggedContext.index == 0 || event.draggedContext.futureIndex == 0) + { + return false; + } + }, + onStart(event) + { + }, + onEnd(evt) + { + tmaxios.put('/api/v1/block/move',{ + 'url': data.urlinfo.route, + 'index_old': evt.oldIndex, + 'index_new': evt.newIndex, +// 'csrf_name': document.getElementById("csrf_name").value, +// 'csrf_value': document.getElementById("csrf_value").value, + }) + .then(function (response) + { + self.content = response.data.content; + }) + .catch(function (error) + { + if(error.response) + { + publishController.errors.message = error.response.data.errors.message; + } + }); + }, + freezeOn() + { + this.freeze = true; + }, + freezeOff() + { + this.freeze = false; + }, + + + +/* + activateTab: function(tab){ + this.currentTab = tab; + this.reset(); + }, + + reset: function() + { + this.errors = {}; + this.message = ''; + this.messageClass = ''; + }, + save: function() + { + this.reset(); + var self = this; + + tmaxios.post('/api/v1/settings',{ + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + 'settings': this.formData + }) + .then(function (response) + { + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + }) + .catch(function (error) + { + self.messageClass = 'bg-rose-500'; + self.message = error.response.data.message; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + }); + }, +*/ + + }, +}) + +bloxeditor.component('draggable', vuedraggable); + +bloxeditor.component('new-block',{ + props: ['markdown', 'index'], + template: ` + ++ `, + data: function () { + return { + formats: bloxFormats, + componentType: false, + disabled: false, + newblockmarkdown: '' + } + }, + methods: { + setComponentType(event, componenttype) + { + this.componentType = componenttype; + }, + closeComponent() + { + this.componentType = false; + this.newblockmarkdown = ''; + eventBus.$emit('publisherclear'); + }, + updateMarkdownFunction(value) + { + this.newblockmarkdown = value; + }, + saveNewBlock() + { + if( + this.newblockmarkdown == undefined || + this.newblockmarkdown.replace(/(\r\n|\n|\r|\s)/gm,"") == '' + ) + { + this.closeComponent(); + return; + } + + if(typeof this.$refs.activeComponent.saveBlock === "function") + { + this.$refs.activeComponent.saveBlock(this.updatedmarkdown); + return; + } + + var self = this; + + eventBus.$emit('publisherclear'); + + tmaxios.post('/api/v1/block',{ + 'url': data.urlinfo.route, + 'block_id': this.index, + 'markdown': this.newblockmarkdown.trim(), + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + }) + .then(function (response) + { + self.$root.$data.content = response.data.content; + self.closeComponent(); + eventBus.$emit('closeComponents'); + }) + .catch(function (error) + { + if(error.response) + { + eventBus.$emit('publishermessage', error.response.data.message); + } + }); + }, + } +}); + + +bloxeditor.component('content-block', { + props: ['element', 'index'], + template: ` ++ +++++ + ++ `, + data: function () { + return { + edit: false, + disabled: false, + componentType: false, + updatedmarkdown: false, + preview: false, + edit: false, + newblock: false, + formats: bloxFormats, + load: false, + } + }, + mounted: function() + { + this.updatedmarkdown = this.element.markdown; + + eventBus.$on('closeComponents', this.closeEditor); + + eventBus.$on('inlineFormat', content => { + this.updatedmarkdown = content; + }); + }, + methods: { + openNewBlock() + { + eventBus.$emit('freeze'); + + this.newblock = true; + this.edit = false; + }, + closeNewBlock() + { + eventBus.$emit('unfreeze'); + eventBus.$emit('publisherclear'); + + this.newblock = false; + }, + closeEditor() + { + eventBus.$emit('closeEditor'); + eventBus.$emit('unfreeze'); + eventBus.$emit('publisherclear'); + + this.edit = false; + this.newblock = false; + this.componentType = false; + this.updatedmarkdown = false; + }, + showEditor() + { + eventBus.$emit('closeComponents'); + eventBus.$emit('freeze'); + + this.edit = true; + + this.componentType = this.determineBlockType(); + + this.updatedmarkdown = this.element.markdown; + }, + determineBlockType() + { + if(this.index == 0) + { + return 'title-component'; + } + + let markdown = this.element.markdown; + let lines = markdown.split("\n"); + let blockType = 'markdown-component'; + + for (var method in determiner) + { + var specialBlock = determiner[method](markdown,lines,markdown[0],markdown[1],markdown[2]); + + if(specialBlock) + { + blockType = specialBlock; + } + } + + return blockType; + }, + updateMarkdownFunction(value) + { + this.updatedmarkdown = value; + }, + disableSort() + { + console.info("we have to disable sort function"); + }, + deleteBlock() + { + this.load = true; + + var self = this; + + eventBus.$emit('publisherclear'); + + tmaxios.delete('/api/v1/block',{ + data: { + 'url': data.urlinfo.route, + 'block_id': this.index, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + } + }) + .then(function (response) + { + self.load = false; + self.$root.$data.content = response.data.content; + if(response.data.navigation) + { + eventBus.$emit('navigation', response.data.navigation); + } + if(response.data.item) + { + eventBus.$emit('item', response.data.item); + } + + // update the navigation and mark navigation item as modified + // navi.getNavi(); + }) + .catch(function (error) + { + self.load = false; + if(error.response) + { + eventBus.$emit('publishermessage', error.response.data.message); + } + }); + }, + getHtml(html) + { + /* fix for empty html of abbreviations */ + if(html == '') + { + return '++++Choose a content type
+ ++ ++ + ++++ +++ + Invisible: ' + this.element.markdown + '
'; + } + return html; + }, + saveBlock() + { + if( + this.updatedmarkdown == undefined || + this.updatedmarkdown.replace(/(\r\n|\n|\r|\s)/gm,"") == '' || + this.updatedmarkdown == this.element.markdown + ) + { + this.closeEditor(); + return; + } + + if(typeof this.$refs.activeComponent.saveBlock === "function" && force == false) + { + this.$refs.activeComponent.saveBlock(this.updatedmarkdown); + return; + } + + var self = this; + + this.load = true; + eventBus.$emit('publisherclear'); + + tmaxios.put('/api/v1/block',{ + 'url': data.urlinfo.route, + 'block_id': this.index, + 'markdown': this.updatedmarkdown.trim(), + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + }) + .then(function (response) + { + self.load = false; + self.$root.$data.content = response.data.content; + if(response.data.navigation) + { + eventBus.$emit('navigation', response.data.navigation); + } + if(response.data.item) + { + eventBus.$emit('item', response.data.item); + } + self.closeEditor(); + }) + .catch(function (error) + { + self.load = false; + if(error.response) + { + eventBus.$emit('publishermessage', error.response.data.message); + } + }); + }, + }, +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-contentnavi.js b/system/typemill/author/js/vue-contentnavi.js index a825925..5c22c75 100644 --- a/system/typemill/author/js/vue-contentnavi.js +++ b/system/typemill/author/js/vue-contentnavi.js @@ -6,8 +6,10 @@ const navigation = Vue.createApp({- - Home + + + {{ home.name }} +@@ -15,6 +17,8 @@ const navigation = Vue.createApp({ data: function () { return { navigation: data.navigation, + home: data.home, + backup: false, isExpended: false, expanded: [], } @@ -35,14 +39,50 @@ const navigation = Vue.createApp({ } this.expanded = expanded.split(','); } + eventBus.$on('toggleFolder', this.toggleFolder); + + eventBus.$on('backupNavigation', this.backupNavigation); + + eventBus.$on('revertNavigation', this.revertNavigation); + + eventBus.$on('navigation', navigation => { + this.navigation = navigation; + }); + + eventBus.$on('item', item => { + if(item.originalName == 'home') + { + this.home = item; + } + }); }, methods: { - getHomeUrl() + getStatusClass(status) { - return tmaxios.defaults.baseURL + '/tm/content/visual'; + if(status == 'published') + { + return "border-teal-500"; + } + else if(status == 'modified') + { + return "border-yellow-400"; + } }, - toggleFolder: function(name) + getNaviClass(home) + { + if(home.active) + { + return "text-stone-50 bg-teal-500"; + } + + return ''; + }, + getUrl() + { + return tmaxios.defaults.baseURL + '/tm/content/' + data.settings.editor; + }, + toggleFolder(name) { var index = this.expanded.indexOf(name); if (index > -1) @@ -93,6 +133,14 @@ const navigation = Vue.createApp({ } } return result; + }, + backupNavigation() + { + this.backup = this.navigation; + }, + revertNavigation() + { + this.navigation = this.backup; } } }); @@ -118,7 +166,7 @@ navigation.component('navilevel',{ - + {{ element.name }} @@ -207,6 +255,21 @@ navigation.component('navilevel',{ }, methods: { + getStatusClass(status) + { + if(status == 'published') + { + return "border-teal-500"; + } + else if(status == 'unpublished') + { + return "border-rose-500"; + } + else if(status == 'modified') + { + return "border-yellow-400"; + } + }, getNaviClass(active, activeParent, keyPathArray) { var level = 3; @@ -240,7 +303,7 @@ navigation.component('navilevel',{ }, getUrl(segment) { - return tmaxios.defaults.baseURL + '/tm/content/visual' + segment; + return tmaxios.defaults.baseURL + '/tm/content/' + data.settings.editor + segment; }, callToggle(name) { @@ -256,6 +319,7 @@ navigation.component('navilevel',{ }, onStart(evt) { + eventBus.$emit('backupNavigation'); /* delete error messages if exist */ // publishController.errors.message = false; }, @@ -296,7 +360,6 @@ navigation.component('navilevel',{ '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, }) @@ -318,16 +381,18 @@ navigation.component('navilevel',{ { if(error.response.data.errors.message) { + eventBus.$emit('revertNavigation'); // 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) + eventBus.$emit('publisherclear'); + + 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.'; + eventBus.$emit('publishermessage', 'Special Characters are not allowed. Length between 1 and 40.'); return; } @@ -339,8 +404,8 @@ navigation.component('navilevel',{ tmaxios.post('/api/v1/article',{ 'item_name': this.newItem, 'folder_id': parent, - 'type': type, - // 'url': document.getElementById("path").value, + 'type': type +// 'url': evt.item.dataset.url, // 'csrf_name': document.getElementById("csrf_name").value, // 'csrf_value': document.getElementById("csrf_value").value, }) @@ -360,7 +425,7 @@ navigation.component('navilevel',{ }) .catch(function (error) { -// publishController.errors.message = error.response.data.errors; + eventBus.$emit('publishermessage', error.response.data.message); }); }, emitter(value) { diff --git a/system/typemill/author/js/vue-forms.js b/system/typemill/author/js/vue-forms.js new file mode 100644 index 0000000..66fdc0a --- /dev/null +++ b/system/typemill/author/js/vue-forms.js @@ -0,0 +1,1053 @@ +app.component('component-text', { + props: ['id', 'description', 'maxlength', 'hidden', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'css', 'errors'], + template: `+ + +`, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-textarea', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + template: `{{ errors[name] }}
+{{ $filters.translate(description) }}
++ + +`, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + formatValue: function(value) + { + /* + if(value !== null && typeof value === 'object') + { + this.textareaclass = 'codearea'; + return JSON.stringify(value, undefined, 4); + } + return value; + */ + }, + }, +}) + +app.component('component-codearea', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + data: function() { + return { + highlighted: '', + } + }, + template: `{{ errors[name] }}
+{{ $filters.translate(description) }}
++ +`, + mounted: function() + { + this.resizeCodearea(); + this.highlight(this.value); + }, + methods: { + update: function($event, name) + { + this.highlight($event.target.value); + this.resizeCodearea(); + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + resizeCodearea: function() + { + let codeeditor = this.$refs["editor"]; + + window.requestAnimationFrame(() => { + codeeditor.style.height = '200px'; + if (codeeditor.scrollHeight > 200) + { + codeeditor.style.height = `${codeeditor.scrollHeight + 2}px`; + } + }); + }, + highlight: function(code) + { + if(code === undefined) + { + return; + } + + window.requestAnimationFrame(() => { + highlighted = hljs.highlightAuto(code, ['xml','css','yaml','markdown']).value; + this.highlighted = highlighted; + }); + }, + }, +}) + +app.component('component-select', { + props: ['id', 'description', 'readonly', 'required', 'disabled', 'label', 'name', 'type', 'css', 'options', 'value', 'errors', 'dataset', 'userroles'], + template: `+ ++ +{{ errors[name] }}
+{{ $filters.translate(description) }}
++ + +`, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-checkbox', { + props: ['id', 'description', 'readonly', 'required', 'disabled', 'label', 'checkboxlabel', 'name', 'type', 'css', 'value', 'errors'], + data() { + return { + checked: false + } + }, + template: `{{ errors[name] }}
+{{ $filters.translate(description) }}
++`, + mounted: function() + { + if(this.value === true || this.value == 'on') + { + this.checked = true; + } + }, + methods: { + update: function(checked, name) + { + eventBus.$emit('forminput', {'name': name, 'value': checked}); + }, + }, +}) + +app.component('component-checkboxlist', { + props: ['description', 'readonly', 'required', 'disabled', 'label', 'checkboxlabel', 'options', 'name', 'type', 'css', 'value', 'errors'], + data() { + return { + checkedoptions: [] + } + }, + template: `{{ $filters.translate(label) }}+ +{{ errors[name] }}
+{{ $filters.translate(description) }}
++`, + mounted: function() + { + if(typeof this.value === 'object') + { + this.checkedoptions = this.value; + } + }, + methods: { + update: function(checkedoptions, name) + { + eventBus.$emit('forminput', {'name': name, 'value': checkedoptions}); + }, + }, +}) + +app.component('component-radio', { + props: ['id', 'description', 'readonly', 'required', 'disabled', 'options', 'label', 'name', 'type', 'css', 'value', 'errors'], + data() { + return { + picked: this.value + } + }, + template: `{{ $filters.translate(label) }}+ +{{ errors[name] }}
+{{ $filters.translate(description) }}
++`, + methods: { + update: function(picked, name) + { + eventBus.$emit('forminput', {'name': name, 'value': picked}); + }, + }, +}) + +app.component('component-number', { + props: ['id', 'description', 'min', 'max', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + template: `{{ $filters.translate(label) }}+ +{{ errors[name] }}
+{{ $filters.translate(description) }}
++ + +`, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-date', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + template: `{{ errors[name] }}
+{{ $filters.translate(description) }}
++ +`, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-email', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + template: `+++ ++ +{{ errors[name] }}
+{{ $filters.translate(description) }}
++ +`, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-tel', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + template: `+++ ++ +{{ errors[name] }}
+{{ $filters.translate(description) }}
++ +`, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-url', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + template: `+++ ++ +{{ errors[name] }}
+{{ $filters.translate(description) }}
++ +`, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-color', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + template: `+++ ++ +{{ errors[name] }}
+{{ $filters.translate(description) }}
++ +`, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-password', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'autocomplete', 'generator', 'css', 'value', 'errors'], + data() { + return { + fieldType: "password" + }; + }, + template: `+++ ++ +{{ errors[name] }}
+{{ $filters.translate(description) }}
++ +`, + methods: { + update: function(newvalue, name) + { + eventBus.$emit('forminput', {'name': name, 'value': newvalue}); + }, + toggleFieldType: function() + { + if (this.fieldType === "password") + { + this.fieldType = "text"; + } + else + { + this.fieldType = "password"; + } + }, + generatePassword: function() + { + const digits = '0123456789'; + const upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + const lower = upper.toLowerCase(); + const characters = digits + upper + lower; + const length = 40; + + const randomCharacters = Array.from({ length }, (_) => + this.getRandomCharacter(characters), + ).join('') + + const passwordLength = this.getRandomInt(30,40); + + const password = randomCharacters.substring(0,passwordLength); + + this.update(password, this.name); + }, + getRandomInt: function(min,max) + { + return Math.floor(Math.random() * (max - min + 1) + min); + }, + getRandomCharacter: function(characters) + { + let randomNumber + + do{ + randomNumber = crypto.getRandomValues(new Uint8Array(1))[0] + } while (randomNumber >= 256 - (256 % characters.length)) + + return characters[randomNumber % characters.length] + } + }, +}) + +app.component('component-hidden', { + props: ['id', 'maxlength', 'required', 'disabled', 'name', 'type', 'css', 'value', 'errors'], + template: `+++ ++ ++ + ++++++{{ errors[name] }}
+{{ $filters.translate(description) }}
++ +++ +`, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-customfields', { + props: ['id', 'description', 'readonly', 'required', 'disabled', 'options', 'label', 'name', 'type', 'value', 'errors'], + data: function () { + return { + fielderrors: false, + fielddetails: {}, + disableaddbutton: false, + cfvalue: [{}] + } + }, + template: `+ +`, + mounted: function(){ + if(typeof this.value === 'undefined' || this.value === null || this.value.length == 0) + { + // this.cfvalue = [{}]; + // this.update(this.cfvalue, this.name); + this.disableaddbutton = 'disabled'; + } + else + { + /* turn object { key:value, key:value } into array [[key,value][key,value]] */ + this.cfvalue = Object.entries(this.value); + /* and back into array of objects [ {key: key, value: value}{key:key, value: value }] */ + this.cfvalue = this.cfvalue.map(function(item){ return { 'key': item[0], 'value': item[1] } }); + } + }, + methods: { + update: function(value, name) + { + this.fielderrors = false; + this.errors = false; + + /* transform array of objects [{key:mykey, value:myvalue}] into array [[mykey,myvalue]] */ + var storedvalue = value.map(function(item){ return [item.key, item.value]; }); + + /* transform array [[mykey,myvalue]] into object { mykey:myvalue } */ + storedvalue = Object.fromEntries(storedvalue); + + FormBus.$emit('forminput', {'name': name, 'value': storedvalue}); + }, + updatePairKey: function(index,event) + { + this.cfvalue[index].key = event.target.value; + + var regex = /^[a-z0-9]+$/i; + + if(!this.keyIsUnique(event.target.value,index)) + { + this.cfvalue[index].keyerror = 'red'; + this.fielderrors = 'Error: The key already exists'; + this.disableaddbutton = 'disabled'; + return; + } + else if(!regex.test(event.target.value)) + { + this.cfvalue[index].keyerror = 'red'; + this.fielderrors = 'Error: Only alphanumeric for keys allowed'; + this.disableaddbutton = 'disabled'; + return; + } + + delete this.cfvalue[index].keyerror; + this.disableaddbutton = false; + this.update(this.cfvalue,this.name); + }, + keyIsUnique: function(keystring, index) + { + for(obj in this.cfvalue) + { + if( (obj != index) && (this.cfvalue[obj].key == keystring) ) + { + return false; + } + } + return true; + }, + updatePairValue: function(index, event) + { + this.cfvalue[index].value = event.target.value; + + var regex = /<.*(?=>)/gm; + if(event.target.value == '' || regex.test(event.target.value)) + { + this.cfvalue[index].valueerror = 'red'; + this.fielderrors = 'Error: No empty values or html tags are allowed'; + } + else + { + delete this.cfvalue[index].valueerror; + this.update(this.cfvalue,this.name); + } + }, + addField: function() + { + for(object in this.cfvalue) + { + if(Object.keys(this.cfvalue[object]).length === 0) + { + return; + } + } + this.cfvalue.push({}); + this.disableaddbutton = 'disabled'; + }, + deleteField: function(index) + { + this.cfvalue.splice(index,1); + this.disableaddbutton = false; + this.update(this.cfvalue,this.name); + }, + }, +}) + + +app.component('component-image', { + props: ['id', 'description', 'maxlength', 'hidden', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'css', 'errors'], + data: function(){ + return { + maxsize: 10, // megabyte + imagepreview: '', + showmedialib: false, +// load: false, + quality: false, + qualitylabel: false, + } + }, + template: `{{ $filters.translate(description) }}+{{ errors[name] }}++ {{ fielderrors }}+ + ++ + + + +++ +`, + mounted: function() { + if(this.hasValue(this.value)) + { + this.imagepreview = tmaxios.defaults.baseURL + '/' + this.value; + + /* switcher for quality */ + if(this.value.indexOf("/live/") > -1 ) + { + this.quality = 'optimized'; + this.qualitylabel = 'switch size to: maximum'; + } + else if(this.value.indexOf("/original/") > -1) + { + this.quality = 'maximum'; + this.qualitylabel = 'switch size to: optimized'; + } + } + }, + methods: { + hasValue: function(value) + { + if(typeof this.value !== "undefined" && this.value !== false && this.value !== null && this.value !== '') + { + return true; + } + return false; + }, + switchQuality: function(value) + { + if(this.hasValue(value)) + { + if(this.quality == 'optimized') + { + this.quality = 'maximum'; + this.qualitylabel = 'switch size to: optimized'; + var newUrl = value.replace("/live/", "/original/"); + this.update(newUrl); + } + else + { + this.quality = 'optimized'; + this.qualitylabel = 'switch quality to: maximum'; + var newUrl = value.replace("/original/", "/live/"); + this.update(newUrl); + } + } + }, + update: function(filepath) + { + eventBus.$emit('forminput', {'name': this.name, 'value': filepath}); + }, + updatemarkdown: function(markdown, url) + { + /* is called from child component medialib */ + this.update(url); + }, + createmarkdown: function(url) + { + /* is called from child component medialib */ + this.update(url); + }, + deleteImage: function() + { + this.imagepreview = ''; + this.update(''); + }, + openmedialib: function() + { + this.showmedialib = true; + }, + onFileChange: function( e ) + { + if(e.target.files.length > 0) + { + let imageFile = e.target.files[0]; + let size = imageFile.size / 1024 / 1024; + + if (!imageFile.type.match('image.*')) + { + alert('only images allowed'); +/* publishController.errors.message = "Only images are allowed."; */ + } + else if (size > this.maxsize) + { + alert('too big'); +/* publishController.errors.message = "The maximal size of images is " + this.maxsize + " MB"; */ + } + else + { + sharedself = this; + + let reader = new FileReader(); + reader.readAsDataURL(imageFile); + reader.onload = function(e) + { + sharedself.imagepreview = e.target.result; + + tmaxios.post('/api/v1/image',{ + 'image': e.target.result, + 'name': imageFile.name, + 'publish': true, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + }) + .then(function (response) { + sharedself.update(response.data.name); + }) + .catch(function (error) + { + sharedself.load = false; + if(error.response) + { + console.info(error.response); +/* publishController.errors.message = error.response.data.errors; */ + } + }); + } + } + } + } + }, +}) + +app.component('component-file', { + props: ['id', 'description', 'maxlength', 'hidden', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'errors'], + template: `+++++++
++++{{ $filters.translate('upload an image') }}
+ ++ +++ +++ + +++ ++{{ errors[name] }}
+{{ $filters.translate(description) }}
++ ++++ +`, + data: function(){ + return { + maxsize: 20, // megabyte + showmedialib: false, + fileid: '', + load: false, + userroles: ['all'], + selectedrole: '', + } + }, + mounted: function(){ + this.getrestriction(); + }, + methods: { + update: function(value) + { + FormBus.$emit('forminput', {'name': this.name, 'value': value}); + }, + updatemarkdown: function(markdown, url) + { + /* is called from child component medialib if file has been selected */ + this.update(url); + this.getrestriction(url); + }, + createmarkdown: function(url) + { + /* is called from child component medialib */ + this.update(url); + }, + openmedialib: function() + { + this.showmedialib = true; + }, + deleteFile: function() + { + this.update(''); + this.selectedrole = 'all'; + }, + getrestriction: function(url) + { + var filename = this.value; + if(url) + { + filename = url; + } + + var myself = this; + + myaxios.get('/api/v1/filerestrictions',{ + params: { + 'url': document.getElementById("path").value, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + 'filename': filename, + } + }) + .then(function (response) { + myself.userroles = ['all']; + myself.userroles = myself.userroles.concat(response.data.userroles); + myself.selectedrole = response.data.restriction; + }) + .catch(function (error) + { + if(error.response) + { + } + }); + }, + updaterestriction: function() + { + myaxios.post('/api/v1/filerestrictions',{ + 'url': document.getElementById("path").value, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + 'filename': this.value, + 'role': this.selectedrole, + }) + .then(function (response) { + + }) + .catch(function (error) + { + if(error.response) + { + } + }); + }, + onFileChange: function( e ) + { + if(e.target.files.length > 0) + { + let uploadedFile = e.target.files[0]; + let size = uploadedFile.size / 1024 / 1024; + + if (size > this.maxsize) + { + publishController.errors.message = "The maximal size of a file is " + this.maxsize + " MB"; + } + else + { + sharedself = this; + + sharedself.load = true; + + let reader = new FileReader(); + reader.readAsDataURL(uploadedFile); + reader.onload = function(e) { + myaxios.post('/api/v1/file',{ + 'url': document.getElementById("path").value, + 'file': e.target.result, + 'name': uploadedFile.name, + 'publish': true, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + }) + .then(function (response) { + sharedself.load = false; + sharedself.selectedrole = 'all'; + sharedself.update(response.data.info.url); + }) + .catch(function (error) + { + sharedself.load = false; + if(error.response) + { + publishController.errors.message = error.response.data.errors; + } + }); + } + } + } + } + } +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-kixote.js b/system/typemill/author/js/vue-kixote.js new file mode 100644 index 0000000..72dc6a4 --- /dev/null +++ b/system/typemill/author/js/vue-kixote.js @@ -0,0 +1,425 @@ +const promptlist = [ + { + name: 'help', + description: 'List all awailable prompts with a short description.', + method: function() + { + let result = ['+ + ++++ ++++ +++ + +++++ ++{{ $filters.translate('upload') }}
++ +++ + + ++']; + promptlist.forEach((prompt) => + { + let block = '
'); + + eventBus.$emit('answer', result); + }, + answer: '- ' + prompt.name + ': ' + prompt.description + '
'; + result.push(block); + }) + result.push('You can use the following prompts:
', + }, + { + name: 'exit', + description: 'Exit Kixote and close the Kixote window.', + }, + { + name: 'skip', + description: 'Skip the current task and start a new prompt.', + answer: ['We skipped the current task. Waiting for your next prompt.'], + }, + { + name: 'refresh cache', + description: 'Refresh the cache and recreate the navigation.', + method: function() + { + var self = this; + + tmaxios.get('/api/v1/settings',{ + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + }) + .then(function (response) + { + eventBus.$emit('answer', ['cache has been refreshed']); + }) + .catch(function (error) + { + alert("no answer"); + }); + }, + answer: ['Asking server...'], + }, + { + name: 'show security log', + description: 'Not awailable.', + method: function() + { + eventBus.$emit('nextPrompts', ['delete security log']); + eventBus.$emit('answer', ['security log shown']); + }, + answer: ['Loading security log...'], + }, + { + name: 'delete security log', + description: 'Not awailable.', + method: function() + { + eventBus.$emit('answer', ['Security log deleted']); + }, + }, + { + name: 'publish folder', + description: 'Publishes all unpublished and modified pages inside a folder.', + answer: ['Not available yet.'], + }, + { + name: 'unpublish folder', + description: 'Unpublishes all pages inside a folder.', + answer: ['Not available yet.'], + }, + { + name: 'create content', + description: 'Create content with artificial intelligence.', + params: [ + { + name: 'topic', + value: false, + question: 'Please describe a topic in few words:', + required: true, + regex: false, + }, + { + name: 'length', + value: false, + question: 'How many words should the text have?', + required: true, + regex: false, + }, + ], + method: function(params) + { + eventBus.$emit('storable', ['Lorem ipsum in markdown.']); + eventBus.$emit('nextPrompts', ['transform', 'translate', 'save to page']); + eventBus.$emit('answer', ['This is the answer from the server. The server can ask an AI service with the collected parameters and return any kind of answer in HTML and preferably in markdown, so that typemill can process the content again (e.g. store, translate, and more).']); + }, + answer: ['Creating content...'], + }, + { + name: 'save to page', + description: 'Save markdown to current page.', + method: function(params) + { + console.info(params[0]); + eventBus.$emit('answer', ['saved content to page']); + }, + answer: ['Save content...'], + }, + ]; + +const kixote = Vue.createApp({ + template: `+ +`, + data() { + return { + showKixote: false, + messenger: [], + messengerIndex: false, + prompt: '', + params: false, + } + }, + mounted() { + + this.clear(); + + eventBus.$on('answer', messages => { + let lastKey = this.messenger.length - 1; + messages.forEach((message) => + { + this.messenger[lastKey].answer.push(message); + }); + }); + + eventBus.$on('nextPrompts', nextprompts => { + let lastKey = this.messenger.length - 1; + nextprompts.forEach((nextprompt) => + { + this.messenger[lastKey].nextPrompts.push(nextprompt); + }); + }); + + eventBus.$on('storable', data => { + let lastKey = this.messenger.length - 1; + this.messenger[lastKey].storable = data; + }); + }, + methods: { + clear() + { + this.messenger = []; + this.params = false; + this.prompt = ''; + }, + startKixote() + { + this.clear(); + this.showKixote = true; + this.focusOnInput(); + }, + stopKixote() + { + this.clear(); + this.showKixote = false; + }, + focusOnInput() + { + this.$nextTick(() => { + if(this.showKixote) + { + const inputRef = this.$refs.kinput; + inputRef.focus(); + } + }); + }, + scrollToBottom() + { + this.$nextTick(() => { + const displayRef = this.$refs.kdisplay; + displayRef.scrollTop = displayRef.scrollHeight; + }); + }, + finishPrompt() + { + this.prompt = ''; + this.focusOnInput(); + this.scrollToBottom(); + }, + submitInlinePrompt(prompt, index) + { + this.prompt = prompt; + this.messengerIndex = index; + // should we submit this.messenger[index].storable as params? + let storable = this.messenger[index].storable; + this.submitPrompt(false, storable); + }, + submitPrompt(event, params = false) + { + if(this.prompt.trim() == '') + { + return; + } + + let currentPrompt = 'Ki> ' + this.prompt; + + let message = { 'prompt' : currentPrompt, 'answer' : [], 'storable' : false, 'nextPrompts' : [] } + + if(this.prompt == 'exit') + { + this.stopKixote(); + + return; + } + + if(this.prompt == 'skip') + { + message.answer.push('We skipped the current task. Start with a new prompt.'); + + this.messenger.push(message); + + this.params = false; + + this.finishPrompt(); + + return; + } + + if(this.params) + { + let question = this.getNextQuestion(this.params); + + if(question) + { + message.answer.push(question); + + this.messenger.push(message); + + this.finishPrompt(); + + return; + } + + // if no further question submit inital prompt with params + let params = this.params; + + this.params = false; + + this.prompt = params[0].value; + + this.submitPrompt(false, params); + + return; + } + + let promptObject = this.getPromptObject(this.prompt); + + if(!promptObject) + { + message.answer.push('Prompt not found. Type "help" to see a list of awailable prompts.'); + + this.messenger.push(message); + + this.finishPrompt(); + + return; + } + + if(params) + { + message.answer.push('Working ...'); + + this.messenger.push(message); + + promptObject.method(params); + + this.finishPrompt(); + + return; + } + + let initialParams = this.getPromptParams(promptObject); + + if(initialParams) + { + this.params = initialParams; + + let question = this.getFirstQuestion(initialParams); + + if(question) + { + message.answer.push(question); + + this.messenger.push(message); + + this.finishPrompt(); + + return; + } + + console.info("no questions found"); + } + + if(promptObject.answer) + { + message.answer.push(promptObject.answer); + } + + this.messenger.push(message); + + promptObject.method(); + + this.finishPrompt(); + }, + getPromptObject(prompt) + { + let result = false; + + promptlist.forEach((promptObject) => + { + if(promptObject.name == prompt) + { + result = promptObject; + } + }); + + return result; + }, + getPromptParams(promptObject) + { + if(promptObject.params) + { + let params = [ + { + name: 'submitWithPrompt', + value: promptObject.name + } + ]; + + promptObject.params.forEach((param) => + { + param.value = false; + params.push(param); + }); + + return params; + } + + return false; + }, + getFirstQuestion(params) + { + if(typeof params[1].question != "undefined") + { + return params[1].question; + } + + return false; + }, + getNextQuestion(params) + { + let length = params.length; + + for (var index = 0; index < length; index++) + { + if(!params[index].value) + { + // TODO: validate the param + console.info("validate params"); + + console.info("return error message"); + + console.info("repeat current question (set next to index)."); + + console.info("if valid, set value and set next to index +1"); + // set param if valid + this.params[index].value = this.prompt; + + // go to the next param if exists + let next = index + 1; + if(typeof params[next] != "undefined") + { + return params[next].question; + } + } + } + + return false; + }, + }, +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-medialib.js b/system/typemill/author/js/vue-medialib.js new file mode 100644 index 0000000..943a06c --- /dev/null +++ b/system/typemill/author/js/vue-medialib.js @@ -0,0 +1,498 @@ +const medialib = { + props: ['parentcomponent'], + template: `++++Hello, I am Kixote from Typemill. How can I help?
++++ +++ +++ +++++++ Ki> + + +
+++Enter "help" to see a list of prompts
++`, + data: function(){ + return { + imagedata: false, + showimages: true, + imagedetaildata: false, + showimagedetails: false, + filedata: false, + showfiles: false, + filedetaildata: false, + showfiledetails: false, + detailindex: false, + load: false, + baseurl: myaxios.defaults.baseURL, + adminurl: false, + search: '', + errors: false, + } + }, + mounted: function(){ + + if(this.parentcomponent == 'files') + { + this.showFiles(); + } + + this.errors = false; + var self = this; + + myaxios.get('/api/v1/medialib/images',{ + params: { + 'url': document.getElementById("path").value, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + } + }) + .then(function (response) + { + self.imagedata = response.data.images; + }) + .catch(function (error) + { + if(error.response) + { + self.errors = error.response.data.errors; + } + }); + }, + computed: { + filteredImages() { + + var searchimages = this.search; + var filteredImages = {}; + var images = this.imagedata; + if(images) + { + Object.keys(images).forEach(function(key) { + var searchindex = key + ' ' + images[key].name; + if(searchindex.toLowerCase().indexOf(searchimages.toLowerCase()) !== -1) + { + filteredImages[key] = images[key]; + } + }); + } + return filteredImages; + }, + filteredFiles() { + + var searchfiles = this.search; + var filteredFiles = {}; + var files = this.filedata; + if(files) + { + Object.keys(files).forEach(function(key) { + var searchindex = key + ' ' + files[key].name; + if(searchindex.toLowerCase().indexOf(searchfiles.toLowerCase()) !== -1) + { + filteredFiles[key] = files[key]; + } + }); + } + return filteredFiles; + } + }, + methods: { + isImagesActive: function() + { + if(this.showimages) + { + return 'bg-tm-green white'; + } + return 'bg-light-gray black'; + }, + isFilesActive: function() + { + if(this.showfiles) + { + return 'bg-tm-green white'; + } + return 'bg-light-gray black'; + }, + closemedialib: function() + { + this.$parent.showmedialib = false; + }, + getBackgroundImage: function(image) + { + return 'background-image: url(' + this.baseurl + '/' + image.src_thumb + ');width:250px'; + }, + getImageUrl(relativeUrl) + { + return this.baseurl + '/' + relativeUrl; + }, + showImages: function() + { + this.errors = false; + this.showimages = true; + this.showfiles = false; + this.showimagedetails = false; + this.showfiledetails = false; + this.imagedetaildata = false; + this.detailindex = false; + }, + showFiles: function() + { + this.showimages = false; + this.showfiles = true; + this.showimagedetails = false; + this.showfiledetails = false; + this.imagedetaildata = false; + this.filedetaildata = false; + this.detailindex = false; + + if(!this.files) + { + this.errors = false; + var filesself = this; + + myaxios.get('/api/v1/medialib/files',{ + params: { + 'url': document.getElementById("path").value, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + } + }) + .then(function (response) + { + filesself.filedata = response.data.files; + }) + .catch(function (error) + { + if(error.response) + { + filesself.errors = error.response.data.errors; + } + }); + } + }, + showImageDetails: function(image,index) + { + this.errors = false; + this.showimages = false; + this.showfiles = false; + this.showimagedetails = true; + this.detailindex = index; + this.adminurl = myaxios.defaults.baseURL + '/tm/content/visual'; + + var imageself = this; + + myaxios.get('/api/v1/image',{ + params: { + 'url': document.getElementById("path").value, + 'name': image.name, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + } + }) + .then(function (response) + { + imageself.imagedetaildata = response.data.image; + }) + .catch(function (error) + { + if(error.response) + { + imageself.errors = error.response.data.errors; + } + }); + }, + showFileDetails: function(file,index) + { + this.errors = false; + this.showimages = false; + this.showfiles = false; + this.showimagedetails = false; + this.showfiledetails = true; + this.detailindex = index; + + this.adminurl = myaxios.defaults.baseURL + '/tm/content/visual'; + + var fileself = this; + + myaxios.get('/api/v1/file',{ + params: { + 'url': document.getElementById("path").value, + 'name': file.name, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + } + }) + .then(function (response) + { + fileself.filedetaildata = response.data.file; + }) + .catch(function (error) + { + if(error.response) + { + fileself.errors = error.response.data.errors; + } + }); + }, + selectImage: function(image) + { + this.showImages(); + + if(this.parentcomponent == 'images') + { + var imgmarkdown = {target: {value: '' }}; + + this.$parent.imgfile = image.src_live; + this.$parent.imgpreview = this.baseurl + '/' + image.src_live; + this.$parent.imgmeta = true; + + this.$parent.showmedialib = false; + + this.$parent.createmarkdown(image.src_live); +/* this.$parent.updatemarkdown(imgmarkdown, image.src_live); */ + } + if(this.parentcomponent == 'files') + { + var filemarkdown = {target: {value: '[' + image.name + '](' + image.src_live +'){.tm-download}' }}; + + this.$parent.filemeta = true; + this.$parent.filetitle = image.name; + + this.$parent.showmedialib = false; + + this.$parent.updatemarkdown(filemarkdown, image.src_live); + } + }, + selectFile: function(file) + { + /* if image component is open */ + if(this.parentcomponent == 'images') + { + var imgextensions = ['png','jpg', 'jpeg', 'gif', 'svg', 'webp']; + if(imgextensions.indexOf(file.info.extension) == -1) + { + this.errors = "you cannot insert a file into an image component"; + return; + } + var imgmarkdown = {target: {value: '' }}; + + this.$parent.imgfile = file.url; + this.$parent.imgpreview = this.baseurl + '/' + file.url; + this.$parent.imgmeta = true; + + this.$parent.showmedialib = false; + + this.$parent.createmarkdown(file.url); +/* this.$parent.updatemarkdown(imgmarkdown, file.url);*/ + } + if(this.parentcomponent == 'files') + { + var filemarkdown = {target: {value: '['+ file.name +']('+ file.url +'){.tm-download file-' + file.info.extension + '}' }}; + + this.$parent.showmedialib = false; + + this.$parent.filemeta = true; + this.$parent.filetitle = file.info.filename + ' (' + file.info.extension.toUpperCase() + ')'; + + this.$parent.updatemarkdown(filemarkdown, file.url); + } + this.showFiles(); + }, + removeImage: function(index) + { + this.imagedata.splice(index,1); + }, + removeFile: function(index) + { + this.filedata.splice(index,1); + }, + deleteImage: function(image, index) + { + imageself = this; + + myaxios.delete('/api/v1/image',{ + data: { + 'url': document.getElementById("path").value, + 'name': image.name, + 'index': index, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + } + }) + .then(function (response) + { + imageself.showImages(); + imageself.removeImage(index); + }) + .catch(function (error) + { + if(error.response) + { + imageself.errors = error.response.data.errors; + } + }); + }, + deleteFile: function(file, index) + { + fileself = this; + + myaxios.delete('/api/v1/file',{ + data: { + 'url': document.getElementById("path").value, + 'name': file.name, + 'index': index, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + } + }) + .then(function (response) + { + fileself.showFiles(); + fileself.removeFile(index); + }) + .catch(function (error) + { + if(error.response) + { + fileself.errors = error.response.data.errors; + } + }); + }, + getDate(timestamp) + { + date = new Date(timestamp * 1000); + + datevalues = { + 'year': date.getFullYear(), + 'month': date.getMonth()+1, + 'day': date.getDate(), + 'hour': date.getHours(), + 'minute': date.getMinutes(), + 'second': date.getSeconds(), + }; + return datevalues.year + '-' + datevalues.month + '-' + datevalues.day; + }, + getSize(bytes) + { + var i = Math.floor(Math.log(bytes) / Math.log(1024)), + sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; + }, + isChecked: function(classname) + { + if(this.imgclass == classname) + { + return ' checked'; + } + }, + }, +} \ No newline at end of file diff --git a/system/typemill/author/js/vue-publisher.js b/system/typemill/author/js/vue-publisher.js new file mode 100644 index 0000000..77a0e31 --- /dev/null +++ b/system/typemill/author/js/vue-publisher.js @@ -0,0 +1,321 @@ +const publisher = Vue.createApp({ + template: ` ++++ +++ ++ + +++{{errors}}++ +++ +++{{ image.name }}+ + +++++ + ++++++
++Name{{ imagedetaildata.name}}+URL{{ getImageUrl(imagedetaildata.src_live)}}+++++Size{{ getSize(imagedetaildata.bytes) }}+++Dimensions{{ imagedetaildata.width }}x{{ imagedetaildata.height }} px+++Type{{ imagedetaildata.type }}+++Date{{ getDate(imagedetaildata.timestamp) }}++ + +++ ++ + + + ++++{{ file.name }}+ + +++++ + +++++{{ filedetaildata.info.extension }}+++Name{{ filedetaildata.name}}+URL{{ filedetaildata.url}}+++++Size{{ getSize(filedetaildata.bytes) }}+++Type{{ filedetaildata.info.extension }}+++Date{{ getDate(filedetaildata.timestamp) }}++ + +++`, + data() { + return { + item: data.item, + visual: false, + raw: false, + showModal: false, + message: false, + messageClass: '', + } + }, + components: { + 'modal': modal + }, + mounted() { + if(window.location.href.indexOf('/content/raw') > -1) + { + this.raw = true; + } + if(window.location.href.indexOf('/content/visual') > -1) + { + this.visual = true; + } + + eventBus.$on('item', item => { + this.item = item; + }); + + eventBus.$on('publishermessage', message => { + this.message = message; + this.messageClass = 'bg-rose-500'; + }); + + eventBus.$on('publisherclear', this.clearPublisher); + + }, + computed: { + isPublished() + { + return this.item.status == 'published' ? true : false; + }, + isModified() + { + return this.item.status == 'modified' ? true : false; + }, + isUnpublished() + { + return this.item.status == 'unpublished' ? true : false; + }, + publishClass() + { + if(this.item.status == 'unpublished') + { + return 'bg-teal-500 hover:bg-teal-600'; + } + if(this.item.status == 'modified') + { + return 'bg-yellow-500 hover:bg-yellow-600'; + } + }, + }, + methods: { + clearPublisher() + { + this.message = false; + this.messageClass = false; + this.showModal = false; + }, + getStatusClass(status) + { + if(status == 'published') + { + return "border-teal-500"; + } + else if(status == 'unpublished') + { + return "border-rose-500"; + } + else if(status == 'modified') + { + return "border-yellow-500"; + } + }, + publishArticle() + { + var self = this; + + tmaxios.post('/api/v1/article/publish',{ + 'url': data.urlinfo.route, + 'item_id': this.item.keyPath, + }) + .then(function (response) + { + self.clearPublisher(); + eventBus.$emit('item', response.data.item); + eventBus.$emit('navigation', response.data.navigation); + }) + .catch(function (error) + { + if(error.response) + { + alert(error.response); + } + }); + }, + checkUnpublish() + { + if(this.item.status == 'modified') + { + this.showModal = 'unpublish'; + } + else + { + this.clearPublisher(); + this.unpublishArticle(); + } + }, + unpublishArticle() + { + self = this; + + tmaxios.delete('/api/v1/article/unpublish',{ + data: { + 'url': data.urlinfo.route, + 'item_id': this.item.keyPath, + } + }) + .then(function (response) + { + self.clearPublisher(); + eventBus.$emit('item', response.data.item); + eventBus.$emit('navigation', response.data.navigation); + }) + .catch(function (error) + { + if(error.response) + { + alert(error.response); + } + }); + }, + discardChanges() + { + self = this; + + tmaxios.delete('/api/v1/article/discard',{ + data: { + 'url': data.urlinfo.route, + 'item_id': this.item.keyPath, + } + }) + .then(function (response) + { + self.clearPublisher(); + eventBus.$emit('item', response.data.item); + eventBus.$emit('navigation', response.data.navigation); + eventBus.$emit('content', response.data.content); + }) + .catch(function (error) + { + if(error.response) + { + alert(error.response); + } + }); + }, + saveDraft() + { + var self = this; + tmaxios.put('/api/v1/article',{ + + }) + .then(function (response) { + self.draftResult = 'success'; + navi.getNavi(); + }) + .catch(function (error) + { + self.draftDisabled = false; + self.draftResult = 'fail'; + self.handleErrors(error); + }); + }, + deleteArticle() + { + var self = this; + + tmaxios.delete('/api/v1/article',{ + data: { + 'url': data.urlinfo.route, + 'item_id': this.item.keyPath, + } + }) + .then(function (response) + { + window.location.replace(response.data.url); + }) + .catch(function (error) + { + if(error.response) + { + alert(error.response); + } + }); + }, + }, +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-shared.js b/system/typemill/author/js/vue-shared.js index 14ac79d..c0b8fbf 100644 --- a/system/typemill/author/js/vue-shared.js +++ b/system/typemill/author/js/vue-shared.js @@ -1,1059 +1,4 @@ -app.component('component-text', { - props: ['id', 'description', 'maxlength', 'hidden', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'css', 'errors'], - template: `{{ message }}+++++ ++ {{ item.status }} ++ + + + + ++ ++ + +Discard changes
+ + +Do you want to discard your changes and set the content back to the live version?
+ + + + ++ ++ + +Delete page
+ + +Do you really want to delete this page? Please confirm.
+ + + + ++ ++ + +Unpublish page
+ + +This page has been modified. If you unpublish the page, then we will delete the published version and keep the modified version. Please confirm.
+ + + + +- - -`, - methods: { - update: function($event, name) - { - eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); - }, - }, -}) - -app.component('component-textarea', { - props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], - template: `{{ errors[name] }}
-{{ $filters.translate(description) }}
-- - -`, - methods: { - update: function($event, name) - { - eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); - }, - formatValue: function(value) - { - /* - if(value !== null && typeof value === 'object') - { - this.textareaclass = 'codearea'; - return JSON.stringify(value, undefined, 4); - } - return value; - */ - }, - }, -}) - -app.component('component-codearea', { - props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], - data: function() { - return { - highlighted: '', - } - }, - template: `{{ errors[name] }}
-{{ $filters.translate(description) }}
-- -`, - mounted: function() - { - this.resizeCodearea(); - this.highlight(this.value); - }, - methods: { - update: function($event, name) - { - this.highlight($event.target.value); - this.resizeCodearea(); - eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); - }, - resizeCodearea: function() - { - let codeeditor = this.$refs["editor"]; - - window.requestAnimationFrame(() => { - codeeditor.style.height = '200px'; - if (codeeditor.scrollHeight > 200) - { - codeeditor.style.height = `${codeeditor.scrollHeight + 2}px`; - } - }); - }, - highlight: function(code) - { - if(code === undefined) - { - return; - } - - window.requestAnimationFrame(() => { - highlighted = hljs.highlightAuto(code, ['xml','css','yaml','markdown']).value; - this.highlighted = highlighted; - }); - }, - }, -}) - -app.component('component-select', { - props: ['id', 'description', 'readonly', 'required', 'disabled', 'label', 'name', 'type', 'css', 'options', 'value', 'errors', 'dataset', 'userroles'], - template: `- -- -{{ errors[name] }}
-{{ $filters.translate(description) }}
-- - -`, - methods: { - update: function($event, name) - { - eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); - }, - }, -}) - -app.component('component-checkbox', { - props: ['id', 'description', 'readonly', 'required', 'disabled', 'label', 'checkboxlabel', 'name', 'type', 'css', 'value', 'errors'], - data() { - return { - checked: false - } - }, - template: `{{ errors[name] }}
-{{ $filters.translate(description) }}
--`, - mounted: function() - { - if(this.value === true || this.value == 'on') - { - this.checked = true; - } - }, - methods: { - update: function(checked, name) - { - eventBus.$emit('forminput', {'name': name, 'value': checked}); - }, - }, -}) - -app.component('component-checkboxlist', { - props: ['description', 'readonly', 'required', 'disabled', 'label', 'checkboxlabel', 'options', 'name', 'type', 'css', 'value', 'errors'], - data() { - return { - checkedoptions: [] - } - }, - template: `{{ $filters.translate(label) }}- -{{ errors[name] }}
-{{ $filters.translate(description) }}
--`, - mounted: function() - { - if(typeof this.value === 'object') - { - this.checkedoptions = this.value; - } - }, - methods: { - update: function(checkedoptions, name) - { - eventBus.$emit('forminput', {'name': name, 'value': checkedoptions}); - }, - }, -}) - -app.component('component-radio', { - props: ['id', 'description', 'readonly', 'required', 'disabled', 'options', 'label', 'name', 'type', 'css', 'value', 'errors'], - data() { - return { - picked: this.value - } - }, - template: `{{ $filters.translate(label) }}- -{{ errors[name] }}
-{{ $filters.translate(description) }}
--`, - methods: { - update: function(picked, name) - { - eventBus.$emit('forminput', {'name': name, 'value': picked}); - }, - }, -}) - -app.component('component-number', { - props: ['id', 'description', 'min', 'max', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], - template: `{{ $filters.translate(label) }}- -{{ errors[name] }}
-{{ $filters.translate(description) }}
-- - -`, - methods: { - update: function($event, name) - { - eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); - }, - }, -}) - -app.component('component-date', { - props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], - template: `{{ errors[name] }}
-{{ $filters.translate(description) }}
-- -`, - methods: { - update: function($event, name) - { - eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); - }, - }, -}) - -app.component('component-email', { - props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], - template: `--- -- -{{ errors[name] }}
-{{ $filters.translate(description) }}
-- -`, - methods: { - update: function($event, name) - { - eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); - }, - }, -}) - -app.component('component-tel', { - props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], - template: `--- -- -{{ errors[name] }}
-{{ $filters.translate(description) }}
-- -`, - methods: { - update: function($event, name) - { - eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); - }, - }, -}) - -app.component('component-url', { - props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], - template: `--- -- -{{ errors[name] }}
-{{ $filters.translate(description) }}
-- -`, - methods: { - update: function($event, name) - { - eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); - }, - }, -}) - -app.component('component-color', { - props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], - template: `--- -- -{{ errors[name] }}
-{{ $filters.translate(description) }}
-- -`, - methods: { - update: function($event, name) - { - eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); - }, - }, -}) - -app.component('component-password', { - props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'autocomplete', 'generator', 'css', 'value', 'errors'], - data() { - return { - fieldType: "password" - }; - }, - template: `--- -- -{{ errors[name] }}
-{{ $filters.translate(description) }}
-- -`, - methods: { - update: function(newvalue, name) - { - eventBus.$emit('forminput', {'name': name, 'value': newvalue}); - }, - toggleFieldType: function() - { - if (this.fieldType === "password") - { - this.fieldType = "text"; - } - else - { - this.fieldType = "password"; - } - }, - generatePassword: function() - { - const digits = '0123456789'; - const upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - const lower = upper.toLowerCase(); - const characters = digits + upper + lower; - const length = 40; - - const randomCharacters = Array.from({ length }, (_) => - this.getRandomCharacter(characters), - ).join('') - - const passwordLength = this.getRandomInt(30,40); - - const password = randomCharacters.substring(0,passwordLength); - - this.update(password, this.name); - }, - getRandomInt: function(min,max) - { - return Math.floor(Math.random() * (max - min + 1) + min); - }, - getRandomCharacter: function(characters) - { - let randomNumber - - do{ - randomNumber = crypto.getRandomValues(new Uint8Array(1))[0] - } while (randomNumber >= 256 - (256 % characters.length)) - - return characters[randomNumber % characters.length] - } - }, -}) - -app.component('component-hidden', { - props: ['id', 'maxlength', 'required', 'disabled', 'name', 'type', 'css', 'value', 'errors'], - template: `--- -- -- - ------{{ errors[name] }}
-{{ $filters.translate(description) }}
-- --- -`, - methods: { - update: function($event, name) - { - eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); - }, - }, -}) - -app.component('component-customfields', { - props: ['id', 'description', 'readonly', 'required', 'disabled', 'options', 'label', 'name', 'type', 'value', 'errors'], - data: function () { - return { - fielderrors: false, - fielddetails: {}, - disableaddbutton: false, - cfvalue: [{}] - } - }, - template: `- -`, - mounted: function(){ - if(typeof this.value === 'undefined' || this.value === null || this.value.length == 0) - { - // this.cfvalue = [{}]; - // this.update(this.cfvalue, this.name); - this.disableaddbutton = 'disabled'; - } - else - { - /* turn object { key:value, key:value } into array [[key,value][key,value]] */ - this.cfvalue = Object.entries(this.value); - /* and back into array of objects [ {key: key, value: value}{key:key, value: value }] */ - this.cfvalue = this.cfvalue.map(function(item){ return { 'key': item[0], 'value': item[1] } }); - } - }, - methods: { - update: function(value, name) - { - this.fielderrors = false; - this.errors = false; - - /* transform array of objects [{key:mykey, value:myvalue}] into array [[mykey,myvalue]] */ - var storedvalue = value.map(function(item){ return [item.key, item.value]; }); - - /* transform array [[mykey,myvalue]] into object { mykey:myvalue } */ - storedvalue = Object.fromEntries(storedvalue); - - FormBus.$emit('forminput', {'name': name, 'value': storedvalue}); - }, - updatePairKey: function(index,event) - { - this.cfvalue[index].key = event.target.value; - - var regex = /^[a-z0-9]+$/i; - - if(!this.keyIsUnique(event.target.value,index)) - { - this.cfvalue[index].keyerror = 'red'; - this.fielderrors = 'Error: The key already exists'; - this.disableaddbutton = 'disabled'; - return; - } - else if(!regex.test(event.target.value)) - { - this.cfvalue[index].keyerror = 'red'; - this.fielderrors = 'Error: Only alphanumeric for keys allowed'; - this.disableaddbutton = 'disabled'; - return; - } - - delete this.cfvalue[index].keyerror; - this.disableaddbutton = false; - this.update(this.cfvalue,this.name); - }, - keyIsUnique: function(keystring, index) - { - for(obj in this.cfvalue) - { - if( (obj != index) && (this.cfvalue[obj].key == keystring) ) - { - return false; - } - } - return true; - }, - updatePairValue: function(index, event) - { - this.cfvalue[index].value = event.target.value; - - var regex = /<.*(?=>)/gm; - if(event.target.value == '' || regex.test(event.target.value)) - { - this.cfvalue[index].valueerror = 'red'; - this.fielderrors = 'Error: No empty values or html tags are allowed'; - } - else - { - delete this.cfvalue[index].valueerror; - this.update(this.cfvalue,this.name); - } - }, - addField: function() - { - for(object in this.cfvalue) - { - if(Object.keys(this.cfvalue[object]).length === 0) - { - return; - } - } - this.cfvalue.push({}); - this.disableaddbutton = 'disabled'; - }, - deleteField: function(index) - { - this.cfvalue.splice(index,1); - this.disableaddbutton = false; - this.update(this.cfvalue,this.name); - }, - }, -}) - - -app.component('component-image', { - props: ['id', 'description', 'maxlength', 'hidden', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'css', 'errors'], - data: function(){ - return { - maxsize: 10, // megabyte - imagepreview: '', - showmedialib: false, -// load: false, - quality: false, - qualitylabel: false, - } - }, - template: `{{ $filters.translate(description) }}-{{ errors[name] }}-- {{ fielderrors }}- - -- - - - --- -`, - mounted: function() { - if(this.hasValue(this.value)) - { - this.imagepreview = tmaxios.defaults.baseURL + '/' + this.value; - - /* switcher for quality */ - if(this.value.indexOf("/live/") > -1 ) - { - this.quality = 'optimized'; - this.qualitylabel = 'switch size to: maximum'; - } - else if(this.value.indexOf("/original/") > -1) - { - this.quality = 'maximum'; - this.qualitylabel = 'switch size to: optimized'; - } - } - }, - methods: { - hasValue: function(value) - { - if(typeof this.value !== "undefined" && this.value !== false && this.value !== null && this.value !== '') - { - return true; - } - return false; - }, - switchQuality: function(value) - { - if(this.hasValue(value)) - { - if(this.quality == 'optimized') - { - this.quality = 'maximum'; - this.qualitylabel = 'switch size to: optimized'; - var newUrl = value.replace("/live/", "/original/"); - this.update(newUrl); - } - else - { - this.quality = 'optimized'; - this.qualitylabel = 'switch quality to: maximum'; - var newUrl = value.replace("/original/", "/live/"); - this.update(newUrl); - } - } - }, - update: function(filepath) - { - eventBus.$emit('forminput', {'name': this.name, 'value': filepath}); - }, - updatemarkdown: function(markdown, url) - { - /* is called from child component medialib */ - this.update(url); - }, - createmarkdown: function(url) - { - /* is called from child component medialib */ - this.update(url); - }, - deleteImage: function() - { - this.imagepreview = ''; - this.update(''); - }, - openmedialib: function() - { - this.showmedialib = true; - }, - onFileChange: function( e ) - { - if(e.target.files.length > 0) - { - let imageFile = e.target.files[0]; - let size = imageFile.size / 1024 / 1024; - - if (!imageFile.type.match('image.*')) - { - alert('only images allowed'); -/* publishController.errors.message = "Only images are allowed."; */ - } - else if (size > this.maxsize) - { - alert('too big'); -/* publishController.errors.message = "The maximal size of images is " + this.maxsize + " MB"; */ - } - else - { - sharedself = this; - - let reader = new FileReader(); - reader.readAsDataURL(imageFile); - reader.onload = function(e) - { - sharedself.imagepreview = e.target.result; - - tmaxios.post('/api/v1/image',{ - 'image': e.target.result, - 'name': imageFile.name, - 'publish': true, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - }) - .then(function (response) { - sharedself.update(response.data.name); - }) - .catch(function (error) - { - sharedself.load = false; - if(error.response) - { - console.info(error.response); -/* publishController.errors.message = error.response.data.errors; */ - } - }); - } - } - } - } - }, -}) - -app.component('component-file', { - props: ['id', 'description', 'maxlength', 'hidden', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'errors'], - template: `-------
----{{ $filters.translate('upload an image') }}
- -- --- --- - --- --{{ errors[name] }}
-{{ $filters.translate(description) }}
-- ---- -`, - data: function(){ - return { - maxsize: 20, // megabyte - showmedialib: false, - fileid: '', - load: false, - userroles: ['all'], - selectedrole: '', - } - }, - mounted: function(){ - this.getrestriction(); - }, - methods: { - update: function(value) - { - FormBus.$emit('forminput', {'name': this.name, 'value': value}); - }, - updatemarkdown: function(markdown, url) - { - /* is called from child component medialib if file has been selected */ - this.update(url); - this.getrestriction(url); - }, - createmarkdown: function(url) - { - /* is called from child component medialib */ - this.update(url); - }, - openmedialib: function() - { - this.showmedialib = true; - }, - deleteFile: function() - { - this.update(''); - this.selectedrole = 'all'; - }, - getrestriction: function(url) - { - var filename = this.value; - if(url) - { - filename = url; - } - - var myself = this; - - myaxios.get('/api/v1/filerestrictions',{ - params: { - 'url': document.getElementById("path").value, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - 'filename': filename, - } - }) - .then(function (response) { - myself.userroles = ['all']; - myself.userroles = myself.userroles.concat(response.data.userroles); - myself.selectedrole = response.data.restriction; - }) - .catch(function (error) - { - if(error.response) - { - } - }); - }, - updaterestriction: function() - { - myaxios.post('/api/v1/filerestrictions',{ - 'url': document.getElementById("path").value, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - 'filename': this.value, - 'role': this.selectedrole, - }) - .then(function (response) { - - }) - .catch(function (error) - { - if(error.response) - { - } - }); - }, - onFileChange: function( e ) - { - if(e.target.files.length > 0) - { - let uploadedFile = e.target.files[0]; - let size = uploadedFile.size / 1024 / 1024; - - if (size > this.maxsize) - { - publishController.errors.message = "The maximal size of a file is " + this.maxsize + " MB"; - } - else - { - sharedself = this; - - sharedself.load = true; - - let reader = new FileReader(); - reader.readAsDataURL(uploadedFile); - reader.onload = function(e) { - myaxios.post('/api/v1/file',{ - 'url': document.getElementById("path").value, - 'file': e.target.result, - 'name': uploadedFile.name, - 'publish': true, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - }) - .then(function (response) { - sharedself.load = false; - sharedself.selectedrole = 'all'; - sharedself.update(response.data.info.url); - }) - .catch(function (error) - { - sharedself.load = false; - if(error.response) - { - publishController.errors.message = error.response.data.errors; - } - }); - } - } - } - } - } -}) - - -app.component('modal', { +const modal = { props: ['labelconfirm', 'labelcancel'], template: `- - ---- ---- --- - ----- --{{ $filters.translate('upload') }}
-- --- - - -- @@ -1084,504 +29,24 @@ app.component('modal', { { eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); }, - }, -}) + } +} -const medialib = app.component('medialib', { - props: ['parentcomponent'], - template: `-`, - data: function(){ - return { - imagedata: false, - showimages: true, - imagedetaildata: false, - showimagedetails: false, - filedata: false, - showfiles: false, - filedetaildata: false, - showfiledetails: false, - detailindex: false, - load: false, - baseurl: myaxios.defaults.baseURL, - adminurl: false, - search: '', - errors: false, +const translatefilter = { + translate(value) + { + if(typeof data.labels === 'undefined') return; + if (!value) return ''; + + translation_key = value.replace(/[ ]/g,"_").replace(/[.]/g, "_").replace(/[,]/g, "_").replace(/[-]/g, "_").replace(/[,]/g,"_").toUpperCase(); + translation_value = data.labels[translation_key]; + if(!translation_value || translation_value.length === 0) + { + return value } - }, - mounted: function(){ - - if(this.parentcomponent == 'files') + else { - this.showFiles(); + return data.labels[translation_key] } - - this.errors = false; - var self = this; - - myaxios.get('/api/v1/medialib/images',{ - params: { - 'url': document.getElementById("path").value, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - } - }) - .then(function (response) - { - self.imagedata = response.data.images; - }) - .catch(function (error) - { - if(error.response) - { - self.errors = error.response.data.errors; - } - }); - }, - computed: { - filteredImages() { - - var searchimages = this.search; - var filteredImages = {}; - var images = this.imagedata; - if(images) - { - Object.keys(images).forEach(function(key) { - var searchindex = key + ' ' + images[key].name; - if(searchindex.toLowerCase().indexOf(searchimages.toLowerCase()) !== -1) - { - filteredImages[key] = images[key]; - } - }); - } - return filteredImages; - }, - filteredFiles() { - - var searchfiles = this.search; - var filteredFiles = {}; - var files = this.filedata; - if(files) - { - Object.keys(files).forEach(function(key) { - var searchindex = key + ' ' + files[key].name; - if(searchindex.toLowerCase().indexOf(searchfiles.toLowerCase()) !== -1) - { - filteredFiles[key] = files[key]; - } - }); - } - return filteredFiles; - } - }, - methods: { - isImagesActive: function() - { - if(this.showimages) - { - return 'bg-tm-green white'; - } - return 'bg-light-gray black'; - }, - isFilesActive: function() - { - if(this.showfiles) - { - return 'bg-tm-green white'; - } - return 'bg-light-gray black'; - }, - closemedialib: function() - { - this.$parent.showmedialib = false; - }, - getBackgroundImage: function(image) - { - return 'background-image: url(' + this.baseurl + '/' + image.src_thumb + ');width:250px'; - }, - getImageUrl(relativeUrl) - { - return this.baseurl + '/' + relativeUrl; - }, - showImages: function() - { - this.errors = false; - this.showimages = true; - this.showfiles = false; - this.showimagedetails = false; - this.showfiledetails = false; - this.imagedetaildata = false; - this.detailindex = false; - }, - showFiles: function() - { - this.showimages = false; - this.showfiles = true; - this.showimagedetails = false; - this.showfiledetails = false; - this.imagedetaildata = false; - this.filedetaildata = false; - this.detailindex = false; - - if(!this.files) - { - this.errors = false; - var filesself = this; - - myaxios.get('/api/v1/medialib/files',{ - params: { - 'url': document.getElementById("path").value, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - } - }) - .then(function (response) - { - filesself.filedata = response.data.files; - }) - .catch(function (error) - { - if(error.response) - { - filesself.errors = error.response.data.errors; - } - }); - } - }, - showImageDetails: function(image,index) - { - this.errors = false; - this.showimages = false; - this.showfiles = false; - this.showimagedetails = true; - this.detailindex = index; - this.adminurl = myaxios.defaults.baseURL + '/tm/content/visual'; - - var imageself = this; - - myaxios.get('/api/v1/image',{ - params: { - 'url': document.getElementById("path").value, - 'name': image.name, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - } - }) - .then(function (response) - { - imageself.imagedetaildata = response.data.image; - }) - .catch(function (error) - { - if(error.response) - { - imageself.errors = error.response.data.errors; - } - }); - }, - showFileDetails: function(file,index) - { - this.errors = false; - this.showimages = false; - this.showfiles = false; - this.showimagedetails = false; - this.showfiledetails = true; - this.detailindex = index; - - this.adminurl = myaxios.defaults.baseURL + '/tm/content/visual'; - - var fileself = this; - - myaxios.get('/api/v1/file',{ - params: { - 'url': document.getElementById("path").value, - 'name': file.name, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - } - }) - .then(function (response) - { - fileself.filedetaildata = response.data.file; - }) - .catch(function (error) - { - if(error.response) - { - fileself.errors = error.response.data.errors; - } - }); - }, - selectImage: function(image) - { - this.showImages(); - - if(this.parentcomponent == 'images') - { - var imgmarkdown = {target: {value: '' }}; - - this.$parent.imgfile = image.src_live; - this.$parent.imgpreview = this.baseurl + '/' + image.src_live; - this.$parent.imgmeta = true; - - this.$parent.showmedialib = false; - - this.$parent.createmarkdown(image.src_live); -/* this.$parent.updatemarkdown(imgmarkdown, image.src_live); */ - } - if(this.parentcomponent == 'files') - { - var filemarkdown = {target: {value: '[' + image.name + '](' + image.src_live +'){.tm-download}' }}; - - this.$parent.filemeta = true; - this.$parent.filetitle = image.name; - - this.$parent.showmedialib = false; - - this.$parent.updatemarkdown(filemarkdown, image.src_live); - } - }, - selectFile: function(file) - { - /* if image component is open */ - if(this.parentcomponent == 'images') - { - var imgextensions = ['png','jpg', 'jpeg', 'gif', 'svg', 'webp']; - if(imgextensions.indexOf(file.info.extension) == -1) - { - this.errors = "you cannot insert a file into an image component"; - return; - } - var imgmarkdown = {target: {value: '' }}; - - this.$parent.imgfile = file.url; - this.$parent.imgpreview = this.baseurl + '/' + file.url; - this.$parent.imgmeta = true; - - this.$parent.showmedialib = false; - - this.$parent.createmarkdown(file.url); -/* this.$parent.updatemarkdown(imgmarkdown, file.url);*/ - } - if(this.parentcomponent == 'files') - { - var filemarkdown = {target: {value: '['+ file.name +']('+ file.url +'){.tm-download file-' + file.info.extension + '}' }}; - - this.$parent.showmedialib = false; - - this.$parent.filemeta = true; - this.$parent.filetitle = file.info.filename + ' (' + file.info.extension.toUpperCase() + ')'; - - this.$parent.updatemarkdown(filemarkdown, file.url); - } - this.showFiles(); - }, - removeImage: function(index) - { - this.imagedata.splice(index,1); - }, - removeFile: function(index) - { - this.filedata.splice(index,1); - }, - deleteImage: function(image, index) - { - imageself = this; - - myaxios.delete('/api/v1/image',{ - data: { - 'url': document.getElementById("path").value, - 'name': image.name, - 'index': index, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - } - }) - .then(function (response) - { - imageself.showImages(); - imageself.removeImage(index); - }) - .catch(function (error) - { - if(error.response) - { - imageself.errors = error.response.data.errors; - } - }); - }, - deleteFile: function(file, index) - { - fileself = this; - - myaxios.delete('/api/v1/file',{ - data: { - 'url': document.getElementById("path").value, - 'name': file.name, - 'index': index, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - } - }) - .then(function (response) - { - fileself.showFiles(); - fileself.removeFile(index); - }) - .catch(function (error) - { - if(error.response) - { - fileself.errors = error.response.data.errors; - } - }); - }, - getDate(timestamp) - { - date = new Date(timestamp * 1000); - - datevalues = { - 'year': date.getFullYear(), - 'month': date.getMonth()+1, - 'day': date.getDate(), - 'hour': date.getHours(), - 'minute': date.getMinutes(), - 'second': date.getSeconds(), - }; - return datevalues.year + '-' + datevalues.month + '-' + datevalues.day; - }, - getSize(bytes) - { - var i = Math.floor(Math.log(bytes) / Math.log(1024)), - sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - - return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; - }, - isChecked: function(classname) - { - if(this.imgclass == classname) - { - return ' checked'; - } - }, - }, -}) + } +} \ No newline at end of file diff --git a/system/typemill/author/js/vue-system.js b/system/typemill/author/js/vue-system.js index d29ed25..868bbfd 100644 --- a/system/typemill/author/js/vue-system.js +++ b/system/typemill/author/js/vue-system.js @@ -9,13 +9,13 @@ const app = Vue.createApp({--- --- -- - ---{{errors}}-- --- ---{{ image.name }}- - ----- - ------
--Name{{ imagedetaildata.name}}-URL{{ getImageUrl(imagedetaildata.src_live)}}-----Size{{ getSize(imagedetaildata.bytes) }}---Dimensions{{ imagedetaildata.width }}x{{ imagedetaildata.height }} px---Type{{ imagedetaildata.type }}---Date{{ getDate(imagedetaildata.timestamp) }}-- - --- -- - - - ----{{ file.name }}- - ----- - -----{{ filedetaildata.info.extension }}---Name{{ filedetaildata.name}}-URL{{ filedetaildata.url}}-----Size{{ getSize(filedetaildata.bytes) }}---Type{{ filedetaildata.info.extension }}---Date{{ getDate(filedetaildata.timestamp) }}-- - --@@ -38,8 +38,8 @@ const app = Vue.createApp({ }, mounted() { - for (var key in this.formDefinitions) - { + for (var key in this.formDefinitions) + { if (this.formDefinitions.hasOwnProperty(key)) { this.tabs.push(this.formDefinitions[key].legend); diff --git a/system/typemill/author/js/vue-translate.js b/system/typemill/author/js/vue-translate.js deleted file mode 100644 index 9b06a45..0000000 --- a/system/typemill/author/js/vue-translate.js +++ /dev/null @@ -1,13 +0,0 @@ -app.config.globalProperties.$filters = { - translate(value) - { - if (!value) return '' - translation_key = value.replace(/[ ]/g,"_").replace(/[.]/g, "_").replace(/[,]/g, "_").replace(/[-]/g, "_").replace(/[,]/g,"_").toUpperCase() - translation_value = data.labels[translation_key] - if(!translation_value || translation_value.length === 0){ - return value - } else { - return data.labels[translation_key] - } - } -} diff --git a/system/typemill/author/js/vue-user.js b/system/typemill/author/js/vue-user.js index a6637cd..eafdfd5 100644 --- a/system/typemill/author/js/vue-user.js +++ b/system/typemill/author/js/vue-user.js @@ -27,20 +27,20 @@ const app = Vue.createApp({{{ message }}- +- +@@ -57,6 +57,9 @@ const app = Vue.createApp({ showModal: false, } }, + components: { + 'modal': modal + }, mounted() { eventBus.$on('forminput', formdata => { this.formData[formdata.name] = formdata.value; diff --git a/system/typemill/author/layouts/layoutContent.twig b/system/typemill/author/layouts/layoutContent.twig index fda17e7..c6391cd 100644 --- a/system/typemill/author/layouts/layoutContent.twig +++ b/system/typemill/author/layouts/layoutContent.twig @@ -34,11 +34,11 @@ {% include 'partials/flash.twig' %} -- Delete user
+{{ $filters.translate('Delete user') }}
Do you really want to delete this user?
- ++-@@ -51,6 +51,7 @@ const labels = {{ translations|json_encode() }}; + + - + {% block javascript %}{% endblock %} diff --git a/system/typemill/author/layouts/layoutSystem.twig b/system/typemill/author/layouts/layoutSystem.twig index 8c1b957..e5d1be5 100644 --- a/system/typemill/author/layouts/layoutSystem.twig +++ b/system/typemill/author/layouts/layoutSystem.twig @@ -65,7 +65,10 @@ - + + {% block javascript %}{% endblock %} {{ assets.renderJS() }} diff --git a/system/typemill/author/partials/editorNavi.twig b/system/typemill/author/partials/editorNavi.twig deleted file mode 100644 index a7d4a0c..0000000 --- a/system/typemill/author/partials/editorNavi.twig +++ /dev/null @@ -1,114 +0,0 @@ - - -{% verbatim %} - - - -{% endverbatim %} \ No newline at end of file diff --git a/system/typemill/author/partials/mainNavi.twig b/system/typemill/author/partials/mainNavi.twig index 43882ea..7aabb15 100644 --- a/system/typemill/author/partials/mainNavi.twig +++ b/system/typemill/author/partials/mainNavi.twig @@ -1,6 +1,7 @@+ {% block content %}{% endblock %}