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 ! ![alt-text](media/markdown.png "my title"){#myid .otherclass width=150px} ```` + + + + + The same rules as with links, but with a ! -![alt](media/files/markdown.png){.center} -*With Caption* +![](media/tmp/kai-dahms-v0nbnxlwjzk-3unsp-1.webp) -![alt](media/files/markdown.png "title"){.center} -*With a caption that spans over several lines.* +![alt](media/live/kai-dahms-v0nbnxlwjzk-3unsp-1-3.webp "title"){.center loading="lazy" width="820" height="464"} +*With a caption that spans over two lines.* ![alt-text](media/files/markdown.png "my title"){#myid .otherclass width=150px} @@ -166,6 +202,11 @@ You can link an image with a nested syntax like this: [![alt-text](media/markdown.png)](https://typemill.net) ```` + + + + + You can link an image with a nested syntax like this: [![alt-text](media/files/markdown.png){.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* ```` -![image float left](media/files/markdown.png){.left} -*With caption that spans over several lines* + + + + + +![](media/tmp/kai-dahms-v0nbnxlwjzk-3unsp-1.webp) 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. ![youtube-video](media/live/youtube-6i2-uv88gke.jpeg "click to load video"){#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 .= '' . "\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($structureLive) - { - $urlset = ''; - - foreach($structureLive 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 .= ' ' . $url . '' . "\n"; - $urlset .= ' ' . "\n"; - return $urlset; - } - - 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/Controllers/ControllerApiAuthorArticle.php b/system/typemill/Controllers/ControllerApiAuthorArticle.php index a2e776d..e475be8 100644 --- a/system/typemill/Controllers/ControllerApiAuthorArticle.php +++ b/system/typemill/Controllers/ControllerApiAuthorArticle.php @@ -5,173 +5,211 @@ namespace Typemill\Controllers; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use Slim\Routing\RouteContext; - use Typemill\Models\StorageWrapper; use Typemill\Models\Validation; use Typemill\Models\Navigation; +use Typemill\Models\Content; use Typemill\Static\Slug; class ControllerApiAuthorArticle extends Controller { - public function sortArticle(Request $request, Response $response, $args) + public function publishArticle(Request $request, Response $response, $args) { - $params = $request->getParsedBody(); - - # input validation - $validate = new Validation(); - $result = $validate->navigationSort($params); - if(!$result) + $validRights = $this->validateRights($request->getAttribute('c_userrole'), 'content', 'update'); + if(!$validRights) { $response->getBody()->write(json_encode([ - 'message' => 'Data not valid. Please refresh the page and try again.', - 'errors' => $result + 'message' => 'You do not have enough rights.', ])); return $response->withHeader('Content-Type', 'application/json')->withStatus(422); } - # set variables - $itemKeyPath = explode('.', $params['item_id']); - $parentKeyFrom = explode('.', $params['parent_id_from']); - $parentKeyTo = explode('.', $params['parent_id_to']); - $urlinfo = $this->c->get('urlinfo'); - $langattr = $this->settings['langattr']; - - # get navigation - $navigation = new Navigation(); - $draftNavigation = $navigation->getDraftNavigation($urlinfo, $langattr); - - # validate user rights - $acl = $this->c->get('acl'); - - # if user has no right to update content from others (eg admin or editor) - if(!$acl->isAllowed($request->getAttribute('c_userrole'), 'content', 'update')) + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->articlePublish($params); + if($validInput !== true) { - # check ownership. This code should nearly never run, because there is no button/interface to trigger it. - if(!$this->checkContentOwnership()) - { - $response->getBody()->write(json_encode([ - 'message' => 'You are not allowed to move that content.', - 'navigation' => $draftNavigation - ])); + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); - return $response->withHeader('Content-Type', 'application/json')->withStatus(422); - } + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); } - $item = $navigation->getItemWithKeyPath($draftNavigation, $itemKeyPath); - - $extendedNavigation = $navigation->getExtendedNavigation($urlinfo, $langattr); - - $pageinfo = $extendedNavigation[$params['url']] ?? false; - - if(!$pageinfo) - { + $navigation = new Navigation(); + $urlinfo = $this->c->get('urlinfo'); + $item = $this->getItem($navigation, $params['url'], $urlinfo); + if(!$item) + { $response->getBody()->write(json_encode([ 'message' => 'page not found', ])); return $response->withHeader('Content-Type', 'application/json')->withStatus(404); - } - - # if an item is moved to the first level - if($params['parent_id_to'] == '') - { - # create empty and default values so that the logic below still works - $newFolder = new \stdClass(); - $newFolder->path = ''; - $folderContent = $draftNavigation; - } - else - { - # get the target folder from navigation - $newFolder = $navigation->getItemWithKeyPath($draftNavigation, $parentKeyTo); - - # get the content of the target folder - $folderContent = $newFolder->folderContent; - } - - # if the item has been moved within the same folder - if($params['parent_id_from'] == $params['parent_id_to']) - { - # no need to ping search engines - $ping = false; - - # get key of item - $itemKey = end($itemKeyPath); - reset($itemKeyPath); - - # delete item from folderContent - unset($folderContent[$itemKey]); - } - else - { - # let us ping search engines - $ping = true; - - # rename links in extended file - #$navigation->renameExtended($item, $newFolder); - - # an active file has been moved to another folder, so send new url with response - if($params['active'] == 'active') - { - $url = $urlinfo['baseurl'] . '/tm/content/' . $this->settings['editor'] . $newFolder->urlRelWoF . '/' . $item->slug; - } } - # add item to newFolder - array_splice($folderContent, $params['index_new'], 0, array($item)); - - # move and rename files in the new folder - $index = 0; - $writeError = false; - $storage = new StorageWrapper('\Typemill\Models\Storage'); - foreach($folderContent as $folderItem) - { - if(!$storage->moveContentFile($folderItem, $newFolder->path, $index)) - { - $writeError = true; - } - $index++; - } - if($writeError) + # publish content + $content = new Content($urlinfo['baseurl']); + $draftMarkdown = $content->getDraftMarkdown($item); + $content->publishMarkdown($item, $draftMarkdown); + + # refresh navigation and item + $navigation->clearNavigation(); + $draftNavigation = $navigation->getDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItems($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + $response->getBody()->write(json_encode([ + 'navigation' => $draftNavigation, + 'item' => $item + ])); + + return $response->withHeader('Content-Type', 'application/json'); + +/* + # update the sitemap + $this->updateSitemap($ping = true); + + # complete the page meta if title or description not set + $writeMeta = new WriteMeta(); + $meta = $writeMeta->completePageMeta($this->content, $this->settings, $this->item); + + # dispatch event + $page = ['content' => $this->content, 'meta' => $meta, 'item' => $this->item]; + $page = $this->c->dispatcher->dispatch('onPagePublished', new OnPagePublished($page))->getData(); +*/ + } + + public function unpublishArticle(Request $request, Response $response, $args) + { + $validRights = $this->validateRights($request->getAttribute('c_userrole'), 'content', 'update'); + if(!$validRights) { $response->getBody()->write(json_encode([ - 'message' => 'Something went wrong. Please refresh the page and check, if all folders and files are writable.', - 'navigation' => $draftNavigation, - 'url' => false + 'message' => 'You do not have enough rights.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->articlePublish($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $navigation = new Navigation(); + $urlinfo = $this->c->get('urlinfo'); + $item = $this->getItem($navigation, $params['url'], $urlinfo); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => 'page not found', ])); return $response->withHeader('Content-Type', 'application/json')->withStatus(404); } - # if everything worked, we have to recreate the navigation + # publish content + $content = new Content($urlinfo['baseurl']); + $draftMarkdown = $content->getDraftMarkdown($item); + $content->unpublishMarkdown($item, $draftMarkdown); + + # refresh navigation and item $navigation->clearNavigation(); - -/* - # get item for url and set it active again - if(isset($this->params['url'])) + $draftNavigation = $navigation->getDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItems($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + # check if it is a folder and if the folder has published pages. + $message = false; + if($item->elementType == 'folder' && isset($item->folderContent)) { - $activeItem = Folder::getItemForUrl($this->structureDraft, $this->params['url'], $this->uri->getBaseUrl()); + foreach($item->folderContent as $folderContent) + { + if($folderContent->status == 'published' OR $folderContent->status == 'modified') + { + $message = 'There are published pages within this folder. The pages are not visible on your website anymore.'; + } + } } - # update the structure for website - $this->setFreshStructureLive(); + $response->getBody()->write(json_encode([ + 'message' => $message, + 'navigation' => $draftNavigation, + 'item' => $item + ])); - # update the navigation - $this->setFreshNavigation(); + return $response->withHeader('Content-Type', 'application/json'); + } - # update the sitemap - $this->updateSitemap($ping); + public function discardArticleChanges(Request $request, Response $response, $args) + { + $validRights = $this->validateRights($request->getAttribute('c_userrole'), 'mycontent', 'edit'); + if(!$validRights) + { + $response->getBody()->write(json_encode([ + 'message' => 'You do not have enough rights.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->articlePublish($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $navigation = new Navigation(); + $urlinfo = $this->c->get('urlinfo'); + $item = $this->getItem($navigation, $params['url'], $urlinfo); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => 'page not found', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # publish content + $content = new Content($urlinfo['baseurl']); + $content->deleteDraft($item); + + # refresh navigation and item + $navigation->clearNavigation(); + $draftNavigation = $navigation->getDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItems($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); - # dispatch event - $this->c->dispatcher->dispatch('onPageSorted', new OnPageSorted($this->params)); -*/ + # refresh content + $draftMarkdown = $content->getDraftMarkdown($item); + $draftMarkdownHtml = $content->addDraftHtml($draftMarkdown); $response->getBody()->write(json_encode([ - 'navigation' => $navigation->getDraftNavigation($urlinfo, $langattr), - 'message' => '', - 'url' => false + 'item' => $item, + 'navigation' => $draftNavigation, + 'content' => $draftMarkdownHtml ])); return $response->withHeader('Content-Type', 'application/json'); @@ -179,34 +217,30 @@ class ControllerApiAuthorArticle extends Controller public function createArticle(Request $request, Response $response, $args) { - # validate user rights - $acl = $this->c->get('acl'); - - # if user has no right to update content from others (eg admin or editor) - if(!$acl->isAllowed($request->getAttribute('c_userrole'), 'mycontent', 'create')) + $validRights = $this->validateRights($request->getAttribute('c_userrole'), 'mycontent', 'create'); + if(!$validRights) { $response->getBody()->write(json_encode([ - 'message' => 'You are not allowed to create content.', - ])); - - return $response->withHeader('Content-Type', 'application/json')->withStatus(403); - } - - $params = $request->getParsedBody(); - - # input validation - $validate = new Validation(); - $result = $validate->navigationItem($params); - if(!$result) - { - $response->getBody()->write(json_encode([ - 'message' => 'Input not valid.', - 'errors' => $result + 'message' => 'You do not have enough rights.', ])); return $response->withHeader('Content-Type', 'application/json')->withStatus(422); } + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->navigationItem($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + # set variables $urlinfo = $this->c->get('urlinfo'); $langattr = $this->settings['langattr'] ?? 'en'; @@ -214,7 +248,6 @@ class ControllerApiAuthorArticle extends Controller # get navigation $navigation = new Navigation(); $draftNavigation = $navigation->getDraftNavigation($urlinfo, $langattr); - if($params['folder_id'] == 'root') { $folderContent = $draftNavigation; @@ -313,41 +346,6 @@ class ControllerApiAuthorArticle extends Controller $navigation->clearNavigation(); -/* - # get extended structure - $extended = $navigation->getExtendedNavigation($urlinfo, $langattr); - - # create the url for the item - if($params['folder_id'] == 'root') - { - $urlWoF = '/' . $slug; - } - else - { - $urlWoF = $folder->urlRelWoF . '/' . $slug; - } - - # add the navigation name to the item htmlspecialchars needed for french language - $extended[$urlWoF] = ['hide' => false, 'navtitle' => $name]; - - # store the extended structure - $writeYaml->updateYaml('cache', 'structure-extended.yaml', $extended); - - # update the structure for editor - $this->setFreshStructureDraft(); - - # get item for url and set it active again - if(isset($this->params['url'])) - { - $activeItem = Folder::getItemForUrl($this->structureDraft, $this->params['url'], $this->uri->getBaseUrl()); - } - - # activate this if you want to redirect after creating the page... - # $url = $this->uri->getBaseUrl() . '/tm/content/' . $this->settings['editor'] . $folder->urlRelWoF . '/' . $slug; - - return $response->withJson(array('data' => $this->structureDraft, 'errors' => false, 'url' => $url)); -*/ - $response->getBody()->write(json_encode([ 'navigation' => $navigation->getDraftNavigation($urlinfo, $langattr), 'message' => '', @@ -356,4 +354,210 @@ class ControllerApiAuthorArticle extends Controller return $response->withHeader('Content-Type', 'application/json'); } + + public function sortArticle(Request $request, Response $response, $args) + { + $validRights = $this->validateRights($request->getAttribute('c_userrole'), 'content', 'update'); + if(!$validRights) + { + $response->getBody()->write(json_encode([ + 'message' => 'You do not have enough rights.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->navigationSort($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # get navigation + $navigation = new Navigation(); + $draftNavigation = $navigation->getDraftNavigation($urlinfo, $langattr); + $item = $navigation->getItemWithKeyPath($draftNavigation, $itemKeyPath); + $extendedNavigation = $navigation->getExtendedNavigation($urlinfo, $langattr); + $pageinfo = $extendedNavigation[$params['url']] ?? false; + if(!$pageinfo) + { + $response->getBody()->write(json_encode([ + 'message' => 'page not found', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $itemKeyPath = explode('.', $params['item_id']); + $parentKeyFrom = explode('.', $params['parent_id_from']); + $parentKeyTo = explode('.', $params['parent_id_to']); + + # if an item is moved to the first level + if($params['parent_id_to'] == '') + { + # create empty and default values so that the logic below still works + $newFolder = new \stdClass(); + $newFolder->path = ''; + $folderContent = $draftNavigation; + } + else + { + # get the target folder from navigation + $newFolder = $navigation->getItemWithKeyPath($draftNavigation, $parentKeyTo); + + # get the content of the target folder + $folderContent = $newFolder->folderContent; + } + + # if the item has been moved within the same folder + if($params['parent_id_from'] == $params['parent_id_to']) + { + # no need to ping search engines + $ping = false; + + # get key of item + $itemKey = end($itemKeyPath); + reset($itemKeyPath); + + # delete item from folderContent + unset($folderContent[$itemKey]); + } + else + { + # let us ping search engines + $ping = true; + + # an active file has been moved to another folder, so send new url with response + if($params['active'] == 'active') + { + $url = $urlinfo['baseurl'] . '/tm/content/' . $this->settings['editor'] . $newFolder->urlRelWoF . '/' . $item->slug; + } + } + + # add item to newFolder + array_splice($folderContent, $params['index_new'], 0, array($item)); + + # move and rename files in the new folder + $index = 0; + $writeError = false; + $storage = new StorageWrapper('\Typemill\Models\Storage'); + foreach($folderContent as $folderItem) + { + if(!$storage->moveContentFile($folderItem, $newFolder->path, $index)) + { + $writeError = true; + } + $index++; + } + if($writeError) + { + $response->getBody()->write(json_encode([ + 'message' => 'Something went wrong. Please refresh the page and check, if all folders and files are writable.', + 'navigation' => $draftNavigation, + 'url' => false + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # refresh navigation and item + $navigation->clearNavigation(); + + $response->getBody()->write(json_encode([ + 'navigation' => $navigation->getDraftNavigation($urlinfo, $langattr), + 'message' => '', + 'url' => false + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function deleteArticle(Request $request, Response $response, $args) + { + $validRights = $this->validateRights($request->getAttribute('c_userrole'), 'content', 'delete'); + if(!$validRights) + { + $response->getBody()->write(json_encode([ + 'message' => 'You do not have enough rights.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->articlePublish($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $navigation = new Navigation(); + $urlinfo = $this->c->get('urlinfo'); + $item = $this->getItem($navigation, $params['url'], $urlinfo); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => 'page not found', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # check if it is a folder and if the folder has published pages. + if($item->elementType == 'folder' && isset($item->folderContent)) + { + if(count($item->folderContent > 0)) + { + $response->getBody()->write(json_encode([ + 'message' => 'This folder contains pages, please delete the pages first.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + } + + # publish content + $content = new Content($urlinfo['baseurl']); + $content->deletePage($item); + + # refresh navigation + $navigation->clearNavigation(); + $draftNavigation = $navigation->getDraftNavigation($urlinfo, $this->settings['langattr']); + + # check if it is a subfile or subfolder and set the redirect-url to the parent item + $url = $urlinfo['baseurl'] . '/tm/content/' . $this->settings['editor']; + if(count($item->keyPathArray) > 1) + { + array_pop($item->keyPathArray); + + $parentItem = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + if($parentItem) + { + # an active file has been moved to another folder + $url .= $parentItem->urlRelWoF; + } + } + + $response->getBody()->write(json_encode([ + 'url' => $url + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } } \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiAuthorBlock.php b/system/typemill/Controllers/ControllerApiAuthorBlock.php new file mode 100644 index 0000000..3d9f3f7 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiAuthorBlock.php @@ -0,0 +1,413 @@ +validateRights($request->getAttribute('c_userrole'), 'content', 'update'); + if(!$validRights) + { + $response->getBody()->write(json_encode([ + 'message' => 'You do not have enough rights.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->blockInput($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $navigation = new Navigation(); + $urlinfo = $this->c->get('urlinfo'); + $item = $this->getItem($navigation, $params['url'], $urlinfo); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => 'page not found', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $content = new Content($urlinfo['baseurl']); + $draftMarkdown = $content->getDraftMarkdown($item); + + # if it is a new content-block + if($params['block_id'] > 9999) + { + # set the id of the markdown-block (it will be one more than the actual array, so count is perfect) + $id = count($draftMarkdown); + + # add the new markdown block to the page content + $draftMarkdown[] = $params['markdown']; + } + elseif(($params['block_id'] == 0) OR !isset($draftMarkdown[$params['block_id']])) + { + $response->getBody()->write(json_encode([ + 'message' => 'Block-id not found.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + else + { + # insert new markdown block + array_splice( $draftMarkdown, $params['block_id'], 0, $params['markdown'] ); + $id = $params['block_id']; + } + + $store = $content->saveDraftMarkdown($item, $draftMarkdown); + if($store !== true) + { + $response->getBody()->write(json_encode([ + 'message' => 'We could not store the content: ' . $store, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $draftMarkdownHtml = $content->addDraftHtml($draftMarkdown); + + # if it was published before, then we need to refresh the navigation + if($item->status == 'published') + { + $navigation->clearNavigation(); + + $draftNavigation = $navigation->getDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItems($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => $draftNavigation, + 'item' => $item, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => false, + 'item' => false, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function moveBlock(Request $request, Response $response, $args) + { + $validRights = $this->validateRights($request->getAttribute('c_userrole'), 'content', 'update'); + if(!$validRights) + { + $response->getBody()->write(json_encode([ + 'message' => 'You do not have enough rights.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->blockMove($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $navigation = new Navigation(); + $urlinfo = $this->c->get('urlinfo'); + $item = $this->getItem($navigation, $params['url'], $urlinfo); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => 'page not found', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $content = new Content($urlinfo['baseurl']); + + $draftMarkdown = $content->getDraftMarkdown($item); + + if(!isset($draftMarkdown[$params['index_old']])) + { + $response->getBody()->write(json_encode([ + 'message' => 'Block-id not found', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $extract = array_splice($draftMarkdown, $params['index_old'], 1); + array_splice($draftMarkdown, $params['index_new'], 0, $extract); + + $store = $content->saveDraftMarkdown($item, $draftMarkdown); + if($store !== true) + { + $response->getBody()->write(json_encode([ + 'message' => 'We could not store the content: ' . $store, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $draftMarkdownHtml = $content->addDraftHtml($draftMarkdown); + + # if it was published before, then we need to refresh the navigation + if($item->status == 'published') + { + $navigation->clearNavigation(); + + $draftNavigation = $navigation->getDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItems($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => $draftNavigation, + 'item' => $item, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => false, + 'item' => false, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function updateBlock(Request $request, Response $response, $args) + { + $validRights = $this->validateRights($request->getAttribute('c_userrole'), 'content', 'update'); + if(!$validRights) + { + $response->getBody()->write(json_encode([ + 'message' => 'You do not have enough rights.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->blockInput($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $navigation = new Navigation(); + $urlinfo = $this->c->get('urlinfo'); + $item = $this->getItem($navigation, $params['url'], $urlinfo); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => 'page not found', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $content = new Content($urlinfo['baseurl']); + + $draftMarkdown = $content->getDraftMarkdown($item); + + if(!isset($draftMarkdown[$params['block_id']])) + { + $response->getBody()->write(json_encode([ + 'message' => 'Block-id not found.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + elseif($params['block_id'] == 0) + { + # if it is the title, then delete the "# " if it exists + $blockMarkdown = trim($params['markdown'], "#"); + + # store the markdown-headline in a separate variable + $blockMarkdownTitle = '# ' . trim($blockMarkdown); + + # add the markdown-headline to the page-markdown + $draftMarkdown[0] = $blockMarkdownTitle; + } + else + { + # update the markdown block in the page content + $draftMarkdown[$params['block_id']] = $params['markdown']; + } + + $store = $content->saveDraftMarkdown($item, $draftMarkdown); + if($store !== true) + { + $response->getBody()->write(json_encode([ + 'message' => 'We could not store the content: ' . $store, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $draftMarkdownHtml = $content->addDraftHtml($draftMarkdown); + + # if it was published before, then we need to refresh the navigation + if($item->status == 'published') + { + $navigation->clearNavigation(); + + $draftNavigation = $navigation->getDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItems($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => $draftNavigation, + 'item' => $item, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => false, + 'item' => false, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function deleteBlock(Request $request, Response $response, $args) + { + $validRights = $this->validateRights($request->getAttribute('c_userrole'), 'content', 'update'); + if(!$validRights) + { + $response->getBody()->write(json_encode([ + 'message' => 'You do not have enough rights.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->blockDelete($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $navigation = new Navigation(); + $urlinfo = $this->c->get('urlinfo'); + $item = $this->getItem($navigation, $params['url'], $urlinfo); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => 'page not found', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $content = new Content($urlinfo['baseurl']); + + $draftMarkdown = $content->getDraftMarkdown($item); + + # check if id exists + if(!isset($draftMarkdown[$params['block_id']])) + { + $response->getBody()->write(json_encode([ + 'message' => 'The ID of the content-block is wrong.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $contentBlock = $draftMarkdown[$params['block_id']]; + + # delete the block + unset($draftMarkdown[$params['block_id']]); + $draftMarkdown = array_values($draftMarkdown); + + $store = $content->saveDraftMarkdown($item, $draftMarkdown); + if($store !== true) + { + $response->getBody()->write(json_encode([ + 'message' => 'We could not store the content: ' . $store, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $draftMarkdownHtml = $content->addDraftHtml($draftMarkdown); + + # if it was published before, then we need to refresh the navigation + if($item->status == 'published') + { + $navigation->clearNavigation(); + + $draftNavigation = $navigation->getDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItems($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => $draftNavigation, + 'item' => $item, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => false, + 'item' => false, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiAuthorShortcode.php b/system/typemill/Controllers/ControllerApiAuthorShortcode.php new file mode 100644 index 0000000..b6f80a2 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiAuthorShortcode.php @@ -0,0 +1,31 @@ +c->get('dispatcher')->dispatch(new OnShortcodeFound(['name' => 'registershortcode', 'data' => []]), 'onShortcodeFound')->getData(); + + if(empty($shortcodeData['data'])) + { + $response->getBody()->write(json_encode([ + 'shortcodedata' => false + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'shortcodedata' => $shortcodeData['data'] + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiMediaOld.php b/system/typemill/Controllers/ControllerApiFile.php similarity index 50% rename from system/typemill/Controllers/ControllerApiMediaOld.php rename to system/typemill/Controllers/ControllerApiFile.php index 3189b9e..2afc5af 100644 --- a/system/typemill/Controllers/ControllerApiMediaOld.php +++ b/system/typemill/Controllers/ControllerApiFile.php @@ -4,108 +4,221 @@ namespace Typemill\Controllers; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; -use Typemill\Models\ProcessImage; use Typemill\Models\ProcessFile; -use Typemill\Models\Yaml; -use Typemill\Controllers\ControllerAuthorBlockApi; +use Typemill\Models\StorageWrapper; -class ControllerApiMedia extends ControllerData +class ControllerApiFile extends Controller { - public function createImage(Request $request, Response $response, $args) + public function getFileRestrictions(Request $request, Response $response, $args) { - # get params from call - $params = $request->getParsedBody(); - - $imageProcessor = new ProcessImage($this->settings['images']); + $params = $request->getParsedBody(); - if(!$imageProcessor->checkFolders('images')) + $restriction = 'all'; + + $userroles = $this->c->get('acl')->getRoles(); + + if(isset($params['filename']) && $params['filename'] != '') { - $response->getBody()->write(json_encode([ - 'message' => 'Please check if your media-folder exists and all folders inside are writable.' - ])); + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $restrictions = $storage->getYaml('fileFolder', '', 'filerestrictions.yaml'); - return $response->withHeader('Content-Type', 'application/json')->withStatus(403); - } - - $imageParts = explode(";base64,", $params['image']); - $imageType = explode("/", $imageParts[0]); - - if(!isset($imageType[1])) - { - $response->getBody()->write(json_encode([ - 'message' => 'We did not find an image type, the file might be corrupted.' - ])); - - return $response->withHeader('Content-Type', 'application/json')->withStatus(403); - } - - $acceptedTypes = [ - 'png' => true, - 'jpg' => true, - 'jpeg' => true, - 'gif' => true, - 'webp' => true, - ]; - - if(isset($this->settings['svg']) && $this->settings['svg']) - { - $acceptedTypes['svg+xml'] = true; - } - - if(!isset($acceptedTypes[$imageType[1]])) - { - $response->getBody()->write(json_encode([ - 'message' => 'The image type is not supported.' - ])); - - return $response->withHeader('Content-Type', 'application/json')->withStatus(403); - } - - $imageResult = $imageProcessor->createImage($params['image'], $params['name'], $this->settings['images']); - - if($imageResult) - { - if(is_array($imageResult) && isset($imageResult['errors'])) + if(isset($restrictions[$params['filename']])) { - return $response->withJson($imageResult,422); + $restriction = $restrictions[$params['filename']]; } - - # publish image directly, used for example by image field for meta-tabs - if($params['publish']) - { - $imageProcessor->publishImage(); - } - return $response->withJson(['name' => 'media/live/' . $imageProcessor->getFullName(),'errors' => false]); } $response->getBody()->write(json_encode([ - 'message' => 'could not store image to temporary folder.' + 'userroles' => $userroles, + 'restriction' => $restriction ])); - return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + return $response->withHeader('Content-Type', 'application/json'); } - public function getMediaLibImages(Request $request, Response $response, $args) + public function updateFileRestrictions(Request $request, Response $response, $args) { # get params from call - $this->params = $request->getParsedBody(); - $this->uri = $request->getUri()->withUserInfo(''); + $params = $request->getParsedBody(); + $filename = $params['filename'] ?? false; + $role = $params['role'] ?? false; - $imageProcessor = new ProcessImage($this->settings['images']); - if(!$imageProcessor->checkFolders('images')) + if(!$filename OR !$role) { - return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); + $response->getBody()->write(json_encode([ + 'message' => 'Filename or userrole is missing.' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); } - - $imagelist = $imageProcessor->scanMediaFlat(); + + $userroles = $this->c->get("acl")->getRoles(); + + if($role != 'all' AND !in_array($role, $userroles)) + { + $response->getBody()->write(json_encode([ + 'message' => 'Userrole is unknown.' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $restrictions = $storage->getYaml('fileFolder', '', 'filerestrictions.yaml'); + if(!$restrictions) + { + $restrictions = []; + } + + if($role == 'all') + { + unset($restrictions[$filename]); + } + else + { + $restrictions[$filename] = $role; + } + + $storage->updateYaml('fileFolder', '', 'filerestrictions.yaml', $restrictions); $response->getBody()->write(json_encode([ - 'images' => $imagelist + 'restrictions' => $restrictions ])); - return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + return $response->withHeader('Content-Type', 'application/json'); } + + public function uploadFile(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + + if (!isset($params['file'])) + { + $response->getBody()->write(json_encode([ + 'message' => 'No file found.' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $size = (int) (strlen(rtrim($params['file'], '=')) * 3 / 4); + $extension = pathinfo($params['name'], PATHINFO_EXTENSION); + $finfo = finfo_open( FILEINFO_MIME_TYPE ); + $mtype = @finfo_file( $finfo, $params['file'] ); + finfo_close($finfo); + + if ($size === 0) + { + $response->getBody()->write(json_encode([ + 'message' => 'File is empty.' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + + # 20 MB (1 byte * 1024 * 1024 * 20 (for 20 MB)) + if ($size > 20971520) + { + $response->getBody()->write(json_encode([ + 'message' => 'File is bigger than 20MB.' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + + # check extension first + if (!$this->checkAllowedExtensions($extension)) + { + $response->getBody()->write(json_encode([ + 'message' => 'Filetype is not allowed.' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + + # check mimetype and extension if there is a mimetype. + # in some environments the finfo_file does not work with a base64 string. + if($mtype) + { + if(!$this->checkAllowedMimeTypes($mtype, $extension)) + { + $response->getBody()->write(json_encode([ + 'message' => 'The mime-type is missing, not allowed or does not fit to the file extension.' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + } + + $fileProcessor = new ProcessFile(); + + $fileinfo = $fileProcessor->storeFile($params['file'], $params['name']); + + if($fileinfo) + { + # if the previous check of the mtype with the base64 string failed, then do it now again with the temporary file + if(!$mtype) + { + $filePath = str_replace('media/files', 'media/tmp', $fileinfo['url']); + $fullPath = $this->settings['rootPath'] . $filePath; + $finfo = finfo_open( FILEINFO_MIME_TYPE ); + $mtype = @finfo_file( $finfo, $fullPath ); + finfo_close($finfo); + + if(!$mtype OR !$this->checkAllowedMimeTypes($mtype, $extension)) + { + $fileProcessor->clearTempFolder(); + + $response->getBody()->write(json_encode([ + 'message' => 'The mime-type is missing, not allowed or does not fit to the file extension.' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + } + + /* + # publish file directly, used for example by file field for meta-tabs + if(isset($params['publish']) && $params['publish']) + { + $fileProcessor->publishFile(); + } + */ + + $response->getBody()->write(json_encode([ + 'message' => 'File has been stored', + 'fileinfo' => $fileinfo + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'message' => 'Could not store file to temporary folder.' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + + + + + + + + + + + + + + + + + + + public function getMediaLibFiles(Request $request, Response $response, $args) { # get params from call @@ -127,30 +240,6 @@ class ControllerApiMedia extends ControllerData return $response->withHeader('Content-Type', 'application/json')->withStatus(200); } - public function getImage(Request $request, Response $response, $args) - { - # get params from call - $this->params = $request->getParsedBody(); - $this->uri = $request->getUri()->withUserInfo(''); - - $this->setStructureDraft(); - - $imageProcessor = new ProcessImage($this->settings['images']); - if(!$imageProcessor->checkFolders('images')) - { - return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); - } - - $imageDetails = $imageProcessor->getImageDetails($this->params['name'], $this->structureDraft); - - if($imageDetails) - { - return $response->withJson(['image' => $imageDetails]); - } - - return $response->withJson(['errors' => 'Image not found or image name not valid.'], 404); - } - public function getFile(Request $request, Response $response, $args) { # get params from call @@ -175,188 +264,7 @@ class ControllerApiMedia extends ControllerData return $response->withJson(['errors' => 'file not found or file name invalid'],404); } - public function getFileRestrictions(Request $request, Response $response, $args) - { - # get params from call - $this->params = $request->getParams(); - $this->uri = $request->getUri()->withUserInfo(''); - $restriction = 'all'; - $userroles = $this->c->acl->getRoles(); - - if(isset($this->params['filename']) && $this->params['filename'] != '') - { - $writeYaml = new WriteYaml(); - $restrictions = $writeYaml->getYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml'); - if(isset($restrictions[$this->params['filename']])) - { - $restriction = $restrictions[$this->params['filename']]; - } - } - - return $response->withJson(['userroles' => $userroles, 'restriction' => $restriction]); - } - - public function updateFileRestrictions(Request $request, Response $response, $args) - { - # get params from call - $this->params = $request->getParams(); - $this->uri = $request->getUri()->withUserInfo(''); - $filename = isset($this->params['filename']) ? $this->params['filename'] : false; - $role = isset($this->params['role']) ? $this->params['role'] : false; - - if(!$filename OR !$role) - { - return $response->withJson(['errors' => ['message' => 'Filename or userrole is missing.']], 422); - } - - $userroles = $this->c->acl->getRoles(); - - if($role != 'all' AND !in_array($role, $userroles)) - { - return $response->withJson(['errors' => ['message' => 'Userrole is unknown.']], 422); - } - - $writeYaml = new WriteYaml(); - $restrictions = $writeYaml->getYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml'); - if(!$restrictions) - { - $restrictions = []; - } - - if($role == 'all') - { - unset($restrictions[$filename]); - } - else - { - $restrictions[$filename] = $role; - } - - $writeYaml->updateYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml', $restrictions); - - return $response->withJson(['restrictions' => $restrictions]); - } - - - public function uploadFile(Request $request, Response $response, $args) - { - # get params from call - $this->params = $request->getParams(); - $this->uri = $request->getUri()->withUserInfo(''); - - if (!isset($this->params['file'])) - { - return $response->withJson(['errors' => 'No file found.'],404); - } - - $size = (int) (strlen(rtrim($this->params['file'], '=')) * 3 / 4); - $extension = pathinfo($this->params['name'], PATHINFO_EXTENSION); - $finfo = finfo_open( FILEINFO_MIME_TYPE ); - $mtype = @finfo_file( $finfo, $this->params['file'] ); - finfo_close($finfo); - - if ($size === 0) - { - return $response->withJson(['errors' => 'File is empty.'],422); - } - - # 20 MB (1 byte * 1024 * 1024 * 20 (for 20 MB)) - if ($size > 20971520) - { - return $response->withJson(['errors' => 'File is bigger than 20MB.'],422); - } - - # check extension first - if (!$this->checkAllowedExtensions($extension)) - { - return $response->withJson(['errors' => 'File is not allowed.'],422); - } - - # check mimetype and extension if there is a mimetype. - # in some environments the finfo_file does not work with a base64 string. - if($mtype) - { - if(!$this->checkAllowedMimeTypes($mtype, $extension)) - { - return $response->withJson(['errors' => 'The mime-type or file extension is not allowed.'],422); - } - } - - $fileProcessor = new ProcessFile(); - - if(!$fileProcessor->checkFolders()) - { - return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); - } - - $fileinfo = $fileProcessor->storeFile($this->params['file'], $this->params['name']); - - if($fileinfo) - { - # if the previous check of the mtype with the base64 string failed, then do it now again with the temporary file - if(!$mtype) - { - $filePath = str_replace('media/files', 'media/tmp', $fileinfo['url']); - $fullPath = $this->settings['rootPath'] . $filePath; - $finfo = finfo_open( FILEINFO_MIME_TYPE ); - $mtype = @finfo_file( $finfo, $fullPath ); - finfo_close($finfo); - - if(!$mtype OR !$this->checkAllowedMimeTypes($mtype, $extension)) - { - $fileProcessor->clearTempFolder(); - - return $response->withJson(['errors' => 'The mime-type is missing, not allowed or does not fit to the file extension.'],422); - } - } - - # publish file directly, used for example by file field for meta-tabs - if(isset($this->params['publish']) && $this->params['publish']) - { - $fileProcessor->publishFile(); - } - - return $response->withJson(['errors' => false, 'info' => $fileinfo]); - } - - return $response->withJson(['errors' => 'could not store file to temporary folder'],500); - } - - public function publishImage(Request $request, Response $response, $args) - { - $params = $request->getParsedBody(); - - $imageProcessor = new ProcessImage($this->settings['images']); - if(!$imageProcessor->checkFolders()) - { - return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); - } - - # check the resize modifier in the image markdown, set it to true and delete it from markdown - $noresize = false; - $markdown = isset($params['markdown']) ? $params['markdown'] : false; - - if($markdown && (strlen($markdown) > 9) && (substr($markdown, -9) == '|noresize') ) - { - $noresize = true; - $params['markdown'] = substr($markdown,0,-9); - } - - if($imageProcessor->publishImage($noresize)) - { - $request = $request->withParsedBody($params); - - $block = new ControllerAuthorBlockApi($this->c); - if($params['new']) - { - return $block->addBlock($request, $response, $args); - } - return $block->updateBlock($request, $response, $args); - } - - return $response->withJson(['errors' => 'could not store image to media folder'],500); - } public function publishFile(Request $request, Response $response, $args) { @@ -383,37 +291,6 @@ class ControllerApiMedia extends ControllerData return $response->withJson(['errors' => 'could not store file to media folder'],500); } - public function deleteImage(Request $request, Response $response, $args) - { - # get params from call - $this->params = $request->getParams(); - $this->uri = $request->getUri()->withUserInfo(''); - - # minimum permission is that user is allowed to delete content - if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'delete')) - { - return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to delete images.'), 403); - } - - if(!isset($this->params['name'])) - { - return $response->withJson(['errors' => 'image name is missing'],500); - } - - $imageProcessor = new ProcessImage($this->settings['images']); - if(!$imageProcessor->checkFolders('images')) - { - return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); - } - - if($imageProcessor->deleteImage($this->params['name'])) - { - return $response->withJson(['errors' => false]); - } - - return $response->withJson(['errors' => 'Oops, looks like we could not delete all sizes of that image.'], 500); - } - public function deleteFile(Request $request, Response $response, $args) { # get params from call @@ -441,83 +318,6 @@ class ControllerApiMedia extends ControllerData return $response->withJson(['errors' => 'could not delete the file'],500); } - public function saveVideoImage(Request $request, Response $response, $args) - { - /* get params from call */ - $this->params = $request->getParams(); - $this->uri = $request->getUri()->withUserInfo(''); - $class = false; - - $imageUrl = $this->params['markdown']; - - if(strpos($imageUrl, 'https://www.youtube.com/watch?v=') !== false) - { - $videoID = str_replace('https://www.youtube.com/watch?v=', '', $imageUrl); - $videoID = strpos($videoID, '&') ? substr($videoID, 0, strpos($videoID, '&')) : $videoID; - $class = 'youtube'; - } - if(strpos($imageUrl, 'https://youtu.be/') !== false) - { - $videoID = str_replace('https://youtu.be/', '', $imageUrl); - $videoID = strpos($videoID, '?') ? substr($videoID, 0, strpos($videoID, '?')) : $videoID; - $class = 'youtube'; - } - - if($class == 'youtube') - { - $videoURLmaxres = 'https://i1.ytimg.com/vi/' . $videoID . '/maxresdefault.jpg'; - $videoURL0 = 'https://i1.ytimg.com/vi/' . $videoID . '/0.jpg'; - } - - $ctx = stream_context_create(array( - 'https' => array( - 'timeout' => 1 - ) - ) - ); - - $imageData = @file_get_contents($videoURLmaxres, 0, $ctx); - if($imageData === false) - { - $imageData = @file_get_contents($videoURL0, 0, $ctx); - if($imageData === false) - { - return $response->withJson(array('errors' => 'could not get the video image')); - } - } - - $imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData); - $desiredSizes = ['live' => ['width' => 560, 'height' => 315]]; - $imageProcessor = new ProcessImage($this->settings['images']); - if(!$imageProcessor->checkFolders()) - { - return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500); - } - - $tmpImage = $imageProcessor->createImage($imageData64, $videoID, $desiredSizes); - - if(!$tmpImage) - { - return $response->withJson(array('errors' => 'could not create temporary image')); - } - - $imageUrl = $imageProcessor->publishImage(); - if($imageUrl) - { - $this->params['markdown'] = '![' . $class . '-video](' . $imageUrl . ' "click to load video"){#' . $videoID. ' .' . $class . '}'; - - $request = $request->withParsedBody($this->params); - $block = new ControllerAuthorBlockApi($this->c); - if($this->params['new']) - { - return $block->addBlock($request, $response, $args); - } - return $block->updateBlock($request, $response, $args); - } - - return $response->withJson(array('errors' => 'could not store the preview image')); - } - # https://www.sitepoint.com/mime-types-complete-list/ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types # https://wiki.selfhtml.org/wiki/MIME-Type/%C3%9Cbersicht diff --git a/system/typemill/Controllers/ControllerApiGlobals.php b/system/typemill/Controllers/ControllerApiGlobals.php index f26ea3d..66f0da4 100644 --- a/system/typemill/Controllers/ControllerApiGlobals.php +++ b/system/typemill/Controllers/ControllerApiGlobals.php @@ -4,14 +4,22 @@ namespace Typemill\Controllers; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; +use Typemill\Models\Navigation; -class ControllerApiGlobals extends ControllerData +class ControllerApiGlobals extends Controller { public function getSystemNavi(Request $request, Response $response) { + $navigation = new Navigation(); + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo') + ); + # won't work because api has no session, instead you have to pass user $response->getBody()->write(json_encode([ - 'systemnavi' => $this->getSystemNavigation('member'), + 'systemnavi' => $systemNavigation ])); return $response->withHeader('Content-Type', 'application/json')->withStatus(200); @@ -19,15 +27,23 @@ class ControllerApiGlobals extends ControllerData public function getMainNavi(Request $request, Response $response) { + $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'] + ); + $response->getBody()->write(json_encode([ - 'mainnavi' => $this->getMainNavigation('member'), + 'mainnavi' => $mainNavigation ])); return $response->withHeader('Content-Type', 'application/json')->withStatus(200); } public function getTranslations(Request $request, Response $response) - { + { $response->getBody()->write(json_encode([ 'translations' => $this->c->get('translations'), ])); diff --git a/system/typemill/Controllers/ControllerApiImage.php b/system/typemill/Controllers/ControllerApiImage.php index dc16154..f11248d 100644 --- a/system/typemill/Controllers/ControllerApiImage.php +++ b/system/typemill/Controllers/ControllerApiImage.php @@ -12,7 +12,7 @@ use Typemill\Models\StorageWrapper; # use Typemill\Models\Yaml; # use Typemill\Controllers\ControllerAuthorBlockApi; -class ControllerApiImage extends ControllerData +class ControllerApiImage extends Controller { # MISSING @@ -88,7 +88,7 @@ class ControllerApiImage extends ControllerData 'fullerrors' => $img->errors, ])); - return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); } # for all other image types, check if they should be transformed to webp @@ -130,55 +130,206 @@ class ControllerApiImage extends ControllerData } - - - - - - - - - - - public function publishImage(Request $request, Response $response, $args) { - $params = $request->getParsedBody(); + $params = $request->getParsedBody(); + if(!isset($params['imgfile']) OR !$params['imgfile']) + { + $response->getBody()->write(json_encode([ + 'message' => 'Image or filename is missing.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $result = $storage->publishImage($params['imgfile']); + + if(!$result) + { + $response->getBody()->write(json_encode([ + 'message' => $storage->getError() + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $response->getBody()->write(json_encode([ + 'message' => 'Image saved successfully', + 'path' => $result, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function saveVideoImage(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + + if(!isset($params['videourl']) OR !$params['videourl']) + { + $response->getBody()->write(json_encode([ + 'message' => 'Markdown is missing.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $videoUrl = $params['videourl']; + $class = false; + if(strpos($videoUrl, 'https://www.youtube.com/watch?v=') !== false) + { + $videoID = str_replace('https://www.youtube.com/watch?v=', '', $videoUrl); + $videoID = strpos($videoID, '&') ? substr($videoID, 0, strpos($videoID, '&')) : $videoID; + $class = 'youtube'; + } + elseif(strpos($videoUrl, 'https://youtu.be/') !== false) + { + $videoID = str_replace('https://youtu.be/', '', $videoUrl); + $videoID = strpos($videoID, '?') ? substr($videoID, 0, strpos($videoID, '?')) : $videoID; + $class = 'youtube'; + } + + if($class == 'youtube') + { + $videoURLmaxres = 'https://i1.ytimg.com/vi/' . $videoID . '/maxresdefault.jpg'; + $videoURL0 = 'https://i1.ytimg.com/vi/' . $videoID . '/0.jpg'; + } + + $ctx = stream_context_create(array( + 'https' => array( + 'timeout' => 1 + ) + ) + ); + + $imageData = @file_get_contents($videoURLmaxres, 0, $ctx); + if($imageData === false) + { + $imageData = @file_get_contents($videoURL0, 0, $ctx); + if($imageData === false) + { + return $response->withJson(array('errors' => 'could not get the video image')); + } + } + + $imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData); + + $img = new ProcessImage(); + + # prepare the image + if(!$img->prepareImage($imageData64, $class . '-' . $videoID . '.jpg')) + { + $response->getBody()->write(json_encode([ + 'message' => $img->errors[0], + 'fullerrors' => $img->errors, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # check if image name already exisits in live folder and create an unique name (do not overwrite existing files) + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $uniqueImageName = $storage->createUniqueImageName($img->getFilename(), $img->getExtension()); + $img->setFilename($uniqueImageName); + + # store the original image + if(!$img->storeOriginalToTmp()) + { + $response->getBody()->write(json_encode([ + 'message' => $img->errors[0], + 'fullerrors' => $img->errors, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # for all other image types, check if they should be transformed to webp + if($this->settingActive('convertwebp')) + { + $img->setExtension('webp'); + } + + # set to youtube size + $sizes = $this->settings['images']; + $sizes['live'] = ['width' => 560, 'height' => 315]; + + if(!$img->storeRenditionsToTmp($sizes)) + { + $response->getBody()->write(json_encode([ + 'message' => $img->errors[0], + 'fullerrors' => $img->errors, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + # now publish directly + $livePath = $storage->publishImage($img->getFullName()); + + if($livePath) + { + $response->getBody()->write(json_encode([ + 'message' => 'Image saved successfully', + 'path' => $livePath, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'message' => $storage->getError(), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + + + + + + + $imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData); + $desiredSizes = ['live' => ['width' => 560, 'height' => 315]]; $imageProcessor = new ProcessImage($this->settings['images']); if(!$imageProcessor->checkFolders()) { - return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); + return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500); + } + + $tmpImage = $imageProcessor->createImage($imageData64, $videoID, $desiredSizes); + + if(!$tmpImage) + { + return $response->withJson(array('errors' => 'could not create temporary image')); } - # check the resize modifier in the image markdown, set it to true and delete it from markdown - $noresize = false; - $markdown = isset($params['markdown']) ? $params['markdown'] : false; - - if($markdown && (strlen($markdown) > 9) && (substr($markdown, -9) == '|noresize') ) - { - $noresize = true; - $params['markdown'] = substr($markdown,0,-9); - } - - if($imageProcessor->publishImage($noresize)) + $imageUrl = $imageProcessor->publishImage(); + if($imageUrl) { - $request = $request->withParsedBody($params); - + $this->params['markdown'] = '![' . $class . '-video](' . $imageUrl . ' "click to load video"){#' . $videoID. ' .' . $class . '}'; + + $request = $request->withParsedBody($this->params); $block = new ControllerAuthorBlockApi($this->c); - if($params['new']) + if($this->params['new']) { return $block->addBlock($request, $response, $args); } return $block->updateBlock($request, $response, $args); } - - return $response->withJson(['errors' => 'could not store image to media folder'],500); + + return $response->withJson(array('errors' => 'could not store the preview image')); } + + + + + public function getMediaLibImages(Request $request, Response $response, $args) { # get params from call @@ -256,80 +407,4 @@ class ControllerApiImage extends ControllerData } - public function saveVideoImage(Request $request, Response $response, $args) - { - /* get params from call */ - $this->params = $request->getParams(); - $this->uri = $request->getUri()->withUserInfo(''); - $class = false; - - $imageUrl = $this->params['markdown']; - - if(strpos($imageUrl, 'https://www.youtube.com/watch?v=') !== false) - { - $videoID = str_replace('https://www.youtube.com/watch?v=', '', $imageUrl); - $videoID = strpos($videoID, '&') ? substr($videoID, 0, strpos($videoID, '&')) : $videoID; - $class = 'youtube'; - } - if(strpos($imageUrl, 'https://youtu.be/') !== false) - { - $videoID = str_replace('https://youtu.be/', '', $imageUrl); - $videoID = strpos($videoID, '?') ? substr($videoID, 0, strpos($videoID, '?')) : $videoID; - $class = 'youtube'; - } - - if($class == 'youtube') - { - $videoURLmaxres = 'https://i1.ytimg.com/vi/' . $videoID . '/maxresdefault.jpg'; - $videoURL0 = 'https://i1.ytimg.com/vi/' . $videoID . '/0.jpg'; - } - - $ctx = stream_context_create(array( - 'https' => array( - 'timeout' => 1 - ) - ) - ); - - $imageData = @file_get_contents($videoURLmaxres, 0, $ctx); - if($imageData === false) - { - $imageData = @file_get_contents($videoURL0, 0, $ctx); - if($imageData === false) - { - return $response->withJson(array('errors' => 'could not get the video image')); - } - } - - $imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData); - $desiredSizes = ['live' => ['width' => 560, 'height' => 315]]; - $imageProcessor = new ProcessImage($this->settings['images']); - if(!$imageProcessor->checkFolders()) - { - return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500); - } - - $tmpImage = $imageProcessor->createImage($imageData64, $videoID, $desiredSizes); - - if(!$tmpImage) - { - return $response->withJson(array('errors' => 'could not create temporary image')); - } - - $imageUrl = $imageProcessor->publishImage(); - if($imageUrl) - { - $this->params['markdown'] = '![' . $class . '-video](' . $imageUrl . ' "click to load video"){#' . $videoID. ' .' . $class . '}'; - - $request = $request->withParsedBody($this->params); - $block = new ControllerAuthorBlockApi($this->c); - if($this->params['new']) - { - return $block->addBlock($request, $response, $args); - } - return $block->updateBlock($request, $response, $args); - } - - return $response->withJson(array('errors' => 'could not store the preview image')); - } } diff --git a/system/typemill/Controllers/ControllerApiSystemExtensions.php b/system/typemill/Controllers/ControllerApiSystemExtensions.php index 17a33d1..b46de04 100644 --- a/system/typemill/Controllers/ControllerApiSystemExtensions.php +++ b/system/typemill/Controllers/ControllerApiSystemExtensions.php @@ -5,11 +5,11 @@ namespace Typemill\Controllers; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use Typemill\Models\Validation; -use Typemill\Models\StorageWrapper; use Typemill\Models\License; +use Typemill\Models\Extension; use Typemill\Static\Settings; -class ControllerApiSystemExtensions extends ControllerData +class ControllerApiSystemExtensions extends Controller { public function activateExtension(Request $request, Response $response) { @@ -38,18 +38,24 @@ class ControllerApiSystemExtensions extends ControllerData return $response->withHeader('Content-Type', 'application/json')->withStatus(400); } - $storage = new StorageWrapper('\Typemill\Models\Storage'); - if($params['checked'] == true) { - $folder = ( $params['type'] == 'plugins' ) ? 'pluginFolder' : 'themeFolder'; + $extension = new Extension(); + + if($params['type'] == 'plugins') + { + $definitions = $extension->getPluginDefinition($params['name']); + } + elseif($params['type'] == 'themes') + { + $definitions = $extension->getThemeDefinition($params['name']); + } - $definitions = $storage->getYaml($folder, $params['name'], $params['name'] . '.yaml'); - if(isset($definitions['license']) && in_array($definitions['license'], ['MAKER', 'BUSINESS'])) { - $license = new License(); - $licenseScope = $license->getLicenseScope($this->c->get('urlinfo')); + $license = new License(); + $licenseScope = $license->getLicenseScope($this->c->get('urlinfo')); + if(!isset($licenseScope[$definitions['license']])) { $response->getBody()->write(json_encode([ diff --git a/system/typemill/Controllers/ControllerApiSystemLicense.php b/system/typemill/Controllers/ControllerApiSystemLicense.php index b77912f..7f9e864 100644 --- a/system/typemill/Controllers/ControllerApiSystemLicense.php +++ b/system/typemill/Controllers/ControllerApiSystemLicense.php @@ -7,7 +7,7 @@ use Psr\Http\Message\ResponseInterface as Response; use Typemill\Models\Validation; use Typemill\Models\License; -class ControllerApiSystemLicense extends ControllerData +class ControllerApiSystemLicense extends Controller { public function createLicense(Request $request, Response $response) { diff --git a/system/typemill/Controllers/ControllerApiSystemPlugins.php b/system/typemill/Controllers/ControllerApiSystemPlugins.php index 359d2b4..b35377b 100644 --- a/system/typemill/Controllers/ControllerApiSystemPlugins.php +++ b/system/typemill/Controllers/ControllerApiSystemPlugins.php @@ -5,25 +5,24 @@ namespace Typemill\Controllers; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use Typemill\Models\Validation; -use Typemill\Models\StorageWrapper; -use Typemill\Models\User; +use Typemill\Models\Extension; use Typemill\Static\Settings; -class ControllerApiSystemPlugins extends ControllerData +class ControllerApiSystemPlugins extends Controller { public function updatePlugin(Request $request, Response $response) { $params = $request->getParsedBody(); $pluginname = $params['plugin']; $plugininput = $params['settings']; - $storage = new StorageWrapper('\Typemill\Models\Storage'); - $formdefinitions = $storage->getYaml('pluginFolder', $pluginname, $pluginname . '.yaml'); + + $extension = new Extension(); + $formdefinitions = $extension->getPluginDefinition($pluginname); $plugindata = []; # validate input $validator = new Validation(); $validatedOutput = $this->recursiveValidation($validator, $formdefinitions['forms']['fields'], $plugininput); - if(!empty($this->errors)) { $response->getBody()->write(json_encode([ diff --git a/system/typemill/Controllers/ControllerApiSystemSettings.php b/system/typemill/Controllers/ControllerApiSystemSettings.php index 0054c75..70c6d1a 100644 --- a/system/typemill/Controllers/ControllerApiSystemSettings.php +++ b/system/typemill/Controllers/ControllerApiSystemSettings.php @@ -5,7 +5,7 @@ namespace Typemill\Controllers; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use Typemill\Models\Validation; -use Typemill\Models\StorageWrapper; +use Typemill\Models\Extension; use Typemill\Models\User; use Typemill\Static\Settings; @@ -13,7 +13,7 @@ use Typemill\Static\Settings; # how to translate results in API call ??? # we should translate in backend instead of twig or vue -class ControllerApiSystemSettings extends ControllerData +class ControllerApiSystemSettings extends Controller { public function getSettings(Request $request, Response $response) { @@ -28,8 +28,8 @@ class ControllerApiSystemSettings extends ControllerData { $params = $request->getParsedBody(); $settingsinput = $params['settings']; - $storage = new StorageWrapper('\Typemill\Models\Storage'); - $formdefinitions = $storage->getYaml('systemSettings', '', 'system.yaml'); + + $formdefinitions = Settings::getSettingsDefinitions(); # validate input $validator = new Validation(); diff --git a/system/typemill/Controllers/ControllerApiSystemThemes.php b/system/typemill/Controllers/ControllerApiSystemThemes.php index d3d7132..ba17545 100644 --- a/system/typemill/Controllers/ControllerApiSystemThemes.php +++ b/system/typemill/Controllers/ControllerApiSystemThemes.php @@ -5,26 +5,24 @@ namespace Typemill\Controllers; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use Typemill\Models\Validation; -use Typemill\Models\StorageWrapper; -use Typemill\Models\User; +use Typemill\Models\Extension; use Typemill\Static\Settings; - -class ControllerApiSystemThemes extends ControllerData +class ControllerApiSystemThemes extends Controller { public function updateTheme(Request $request, Response $response) { $params = $request->getParsedBody(); $themename = $params['theme']; $themeinput = $params['settings']; - $storage = new StorageWrapper('\Typemill\Models\Storage'); - $formdefinitions = $storage->getYaml('themeFolder', $themename, $themename . '.yaml'); + + $extension = new Extension(); + $formdefinitions = $extension->getThemeDefinition($themename); $themedata = []; # validate input $validator = new Validation(); $validatedOutput = $this->recursiveValidation($validator, $formdefinitions['forms']['fields'], $themeinput); - if(!empty($this->errors)) { $response->getBody()->write(json_encode([ diff --git a/system/typemill/Controllers/ControllerApiSystemUsers.php b/system/typemill/Controllers/ControllerApiSystemUsers.php index 7498c56..1374039 100644 --- a/system/typemill/Controllers/ControllerApiSystemUsers.php +++ b/system/typemill/Controllers/ControllerApiSystemUsers.php @@ -7,9 +7,8 @@ use Psr\Http\Message\ResponseInterface as Response; use Typemill\Models\Validation; use Typemill\Models\User; -class ControllerApiSystemUsers extends ControllerData +class ControllerApiSystemUsers extends Controller { - # getCurrentUser # getUserByName @@ -96,13 +95,19 @@ class ControllerApiSystemUsers extends ControllerData $username = $params['userdata']['username'] ?? false; $isAdmin = $this->c->get('acl')->isAllowed($request->getAttribute('c_userrole'), 'user', 'write'); - if(!$userdata OR !$username) + $validate = new Validation(); + + # standard validation for new users + $userroles = $this->c->get('acl')->getRoles(); + $valresult = $validate->existingUser($userdata, $userroles); + if($valresult !== true) { $response->getBody()->write(json_encode([ - 'message' => 'Userdata and username is required.' + 'message' => 'Please correct the errors above.', + 'errors' => $validate->returnFirstValidationErrors($valresult) ])); - return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); } # if a non-admin-user tries to update another account @@ -115,16 +120,6 @@ class ControllerApiSystemUsers extends ControllerData return $response->withHeader('Content-Type', 'application/json')->withStatus(403); } - $user = new User(); - - # make sure you set a user with password when you update, otherwise it will delete the password completely - $user->setUserWithPassword($username); - - $formdefinitions = $this->getUserFields($request->getAttribute('c_userrole')); - - # validate input - $validator = new Validation(); - # cleanup password entry if(isset($userdata['password']) AND $userdata['password'] == '') { @@ -138,7 +133,7 @@ class ControllerApiSystemUsers extends ControllerData # validate passwort changes if valid input if(isset($userdata['password']) OR isset($userdata['newpassword'])) { - $validpass = $validator->newPassword($userdata); + $validpass = $validate->newPassword($userdata); if($validpass === true) { @@ -157,8 +152,13 @@ class ControllerApiSystemUsers extends ControllerData unset($userdata['newpassword']); } - $validatedOutput = $this->recursiveValidation($validator, $formdefinitions, $userdata); + # make sure you set a user with password when you update, otherwise it will delete the password completely + $user = new User(); + $user->setUserWithPassword($username); + $formdefinitions = $user->getUserFields($this->c->get('acl'), $request->getAttribute('c_userrole')); + + $validatedOutput = $this->recursiveValidation($validate, $formdefinitions, $userdata); if(!empty($this->errors)) { $response->getBody()->write(json_encode([ @@ -203,7 +203,8 @@ class ControllerApiSystemUsers extends ControllerData return $response->withHeader('Content-Type', 'application/json')->withStatus(422); } - $userform = $this->getUserFields($userrole,$inspectorrole = $request->getAttribute('c_userrole')); + $user = new User(); + $userform = $user->getUserFields($this->c->get('acl'), $userrole,$inspectorrole = $request->getAttribute('c_userrole')); # fix the standard form $userform['password']['label'] = 'Password'; @@ -250,7 +251,8 @@ class ControllerApiSystemUsers extends ControllerData # additional validation for extra fields and image handling - $formdefinitions = $this->getUserFields($userdata['userrole'],$inspectorrole = $request->getAttribute('c_userrole')); + $user = new User(); + $formdefinitions = $user->getUserFields($this->c->get('acl'), $userdata['userrole'],$inspectorrole = $request->getAttribute('c_userrole')); unset($formdefinitions['username']['readonly']); $validatedOutput = $this->recursiveValidation($validate, $formdefinitions, $userdata); if(!empty($this->errors)) @@ -263,7 +265,6 @@ class ControllerApiSystemUsers extends ControllerData return $response->withHeader('Content-Type', 'application/json')->withStatus(400); } - $user = new User(); if(!$user->createUser($validatedOutput)) { $response->getBody()->write(json_encode([ diff --git a/system/typemill/Controllers/ControllerData.php b/system/typemill/Controllers/ControllerData.php deleted file mode 100644 index 775cf84..0000000 --- a/system/typemill/Controllers/ControllerData.php +++ /dev/null @@ -1,394 +0,0 @@ -getYaml('systemSettings', '', 'mainnavi.yaml'); - - $allowedmainnavi = []; - - $acl = $this->c->get('acl'); - - foreach($mainnavi as $name => $naviitem) - { - if($acl->isAllowed($userrole, $naviitem['aclresource'], $naviitem['aclprivilege'])) - { - # not nice: check if the navi-item is active (e.g if segments like "content" or "system" is in current url) - if($name == 'content' && strpos($this->c->get('urlinfo')['route'], 'tm/content')) - { - $naviitem['active'] = true; - } - elseif($name == 'account' && strpos($this->c->get('urlinfo')['route'], 'tm/account')) - { - $naviitem['active'] = true; - } - elseif($name == 'system') - { - $naviitem['active'] = true; - } - - $allowedmainnavi[$name] = $naviitem; - } - } - - # if system is there, then we do not need the account item - if(isset($allowedmainnavi['system'])) - { - unset($allowedmainnavi['account']); - } - - # set correct editor mode according to user settings - if(isset($allowedmainnavi['content']) && $this->settings['editor'] == 'raw') - { - $allowedmainnavi['content']['routename'] = "content.raw"; - } - - return $allowedmainnavi; - } - - protected function getSystemNavigation($userrole) - { - $storage = new StorageWrapper('\Typemill\Models\Storage'); - - $systemnavi = $storage->getYaml('systemSettings', '', 'systemnavi.yaml'); - $systemnavi = $this->c->get('dispatcher')->dispatch(new OnSystemnaviLoaded($systemnavi), 'onSystemnaviLoaded')->getData(); - - $allowedsystemnavi = []; - - $acl = $this->c->get('acl'); - - 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($this->c->get('urlinfo')['route'], 'tm/' . $name)) - { - $naviitem['active'] = true; - } - - if($acl->isAllowed($userrole, $naviitem['aclresource'], $naviitem['aclprivilege'])) - { - $allowedsystemnavi[$name] = $naviitem; - } - } - - return $allowedsystemnavi; - } - - protected function getThemeDetails() - { - $themes = $this->getThemes(); - - $themeDetails = []; - foreach($themes as $themeName) - { - $themeDetails[$themeName] = $this->getThemeDefinition($themeName); - } - - return $themeDetails; - } - - protected function getThemes() - { - $themeFolder = $this->c->get('settings')['rootPath'] . DIRECTORY_SEPARATOR . $this->c->get('settings')['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; - } - - protected function getThemeDefinition($themeName) - { - $storage = new StorageWrapper('\Typemill\Models\Storage'); - - $themeSettings = $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; - } - - protected function getPluginDetails() - { - $plugins = $this->getPlugins(); - - $pluginDetails = []; - foreach($plugins as $pluginName) - { - $pluginDetails[$pluginName] = $this->getPluginDefinition($pluginName); - } - - return $pluginDetails; - } - - protected function getPlugins() - { - $pluginFolder = $this->c->get('settings')['rootPath'] . DIRECTORY_SEPARATOR . $this->c->get('settings')['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; - } - - protected function getPluginDefinition($pluginName) - { - $storage = new StorageWrapper('\Typemill\Models\Storage'); - - $pluginSettings = $storage->getYaml('pluginFolder', $pluginName, $pluginName . '.yaml'); - - return $pluginSettings; - } - - protected function getUserFields($userrole,$inspectorrole = NULL) - { - 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; - } - - $storage = new StorageWrapper('\Typemill\Models\Storage'); - - $userfields = $storage->getYaml('systemSettings', '', 'user.yaml'); - - # if a plugin with a role has been deactivated, then users with the role throw an error, so set them back to member... - if(!$this->c->get('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($this->c->get('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($this->c->get('acl')->isAllowed($inspectorrole, 'user', 'write')) - { - - # can change userroles - $definedroles = $this->c->get('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; - } - - - -########################################################################################## -# GET STUFF FOR EDITOR AREA -########################################################################################## - - # 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; - } -} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerWebAuthor.php b/system/typemill/Controllers/ControllerWebAuthor.php index 4077225..c0ae408 100644 --- a/system/typemill/Controllers/ControllerWebAuthor.php +++ b/system/typemill/Controllers/ControllerWebAuthor.php @@ -5,14 +5,14 @@ namespace Typemill\Controllers; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use Slim\Routing\RouteContext; +use Typemill\Models\Navigation; +use Typemill\Models\Content; # use Typemill\Models\Folder; # use Typemill\Extensions\ParsedownExtension; # use Typemill\Models\StorageWrapper; # use Typemill\Models\User; # use Typemill\Models\License; -use Typemill\Models\Navigation; - class ControllerWebAuthor extends Controller { public function showBlox(Request $request, Response $response, $args) @@ -24,61 +24,84 @@ class ControllerWebAuthor extends Controller $langattr = $this->settings['langattr']; $navigation = new Navigation(); + $draftNavigation = $navigation->getDraftNavigation($urlinfo, $langattr); + $home = $navigation->getHomepageItem($urlinfo['baseurl']); - $extendedNavigation = $navigation->getExtendedNavigation($urlinfo, $langattr); - $pageinfo = $extendedNavigation[$url] ?? false; - if(!$pageinfo) - { - return $this->c->get('view')->render($response->withStatus(404), '404.twig', [ - 'title' => 'Typemill Author Area', - 'description' => 'Typemill Version 2 wird noch besser als Version 1.' - ]); - } + if($url == '/') + { + $item = $home; + } + else + { + $extendedNavigation = $navigation->getExtendedNavigation($urlinfo, $langattr); - # extend : $request->getAttribute('c_userrole') - $draftNavigation = $navigation->getDraftNavigation($urlinfo, $langattr); + $pageinfo = $extendedNavigation[$url] ?? false; + if(!$pageinfo) + { + return $this->c->get('view')->render($response->withStatus(404), '404.twig', [ + 'title' => 'Typemill Author Area', + 'description' => 'Typemill Version 2 wird noch besser als Version 1.' + ]); + } - $item = $navigation->getItemWithKeyPath($draftNavigation, explode(".", $pageinfo['keyPath'])); + $keyPathArray = explode(".", $pageinfo['keyPath']); - $draftNavigation = $navigation->setActiveNaviItems($draftNavigation, explode(".", $pageinfo['keyPath'])); + # extend : $request->getAttribute('c_userrole') + $draftNavigation = $navigation->getDraftNavigation($urlinfo, $langattr); + + $draftNavigation = $navigation->setActiveNaviItems($draftNavigation, $keyPathArray); + + $item = $navigation->getItemWithKeyPath($draftNavigation, $keyPathArray); + } + + # $item->modified = ($item->published OR $item->drafted) ? filemtime($this->settings['contentFolder'] . $this->path) : false; $mainNavigation = $navigation->getMainNavigation($request->getAttribute('c_userrole'), $this->c->get('acl'), $urlinfo, $this->settings['editor']); + $content = new Content($urlinfo['baseurl']); + + $draftMarkdown = $content->getDraftMarkdown($item); + + $draftMarkdownHtml = $content->addDraftHtml($draftMarkdown); + +/* + if(isset($draftHtml[0])) + { + $title = $draftHtml[0]; + unset($draftHtml[0]); + } + + echo '
';
+		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+
\s+/', '', $markup); + + # create table of contents + if(isset($this->DefinitionData['TableOfContents'])) + { + $TOC = $this->buildTOC($this->headlines); + + $markup = preg_replace('%(]*>\[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 = '
    '; + + foreach($headlines as $key => $headline) + { + $thisLevel = $headline['level']; + $prevLevel = $key > 0 ? $headlines[$key-1]['level'] : 1; + $nextLevel = isset($headlines[$key+1]) ? $headlines[$key+1]['level'] : 0; + + if($thisLevel > $prevLevel) + { + $markup .= '
      '; + } + + $markup .= '
    • ' . $headline['text'] . ''; + + if($thisLevel == $nextLevel ) + { + $markup .= '
    • '; + } + elseif($thisLevel > $nextLevel) + { + while($thisLevel > $nextLevel) + { + $markup .= '
    '; + $thisLevel--; + } + + if($thisLevel > 0) + { + $markup .= ''; + } + } + } + + return $markup; + } + + + # + # Footnotes + protected $spanFootnotes = false; + public $footnoteCount = 0; + + # set spanFootnotes (W3C style) to true + public function withSpanFootnotes() + { + $this->spanFootnotes = true; + } + + # used for ??? + public function getFootnotes() + { + # add footnotes + if (isset($this->DefinitionData['Footnote'])) + { + $Element = $this->buildFootnoteElement(); + + $footnotes = "\n" . $this->element($Element); + } + + return $footnotes; + } + + protected function inlineFootnoteMarker($Excerpt) + { + if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches)) + { + $name = $matches[1]; + + if ( ! isset($this->DefinitionData['Footnote'][$name])) + { + return; + } + + $this->DefinitionData['Footnote'][$name]['count'] ++; + + if ( ! isset($this->DefinitionData['Footnote'][$name]['number'])) + { + $this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » & + } + + # Optionally use w3c inline footnote markup for books + if($this->spanFootnotes) + { + $Element = array( + 'name' => 'span', + 'attributes' => array('class' => 'footnote'), + 'text' => $this->DefinitionData['Footnote'][$name]['text'], + ); + } + else + { + $Element = array( + 'name' => 'sup', + 'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name), + 'element' => array( + 'name' => 'a', + 'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'), + 'text' => $this->DefinitionData['Footnote'][$name]['number'], + ), + ); + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => $Element, + ); + } + } + + # has a fix for visual editor mode and option for spanFootnotes + public function buildFootnoteElement() + { + + # we do not need a footnote element if we use w3c inline style with spans for footnotes + if($this->spanFootnotes) + { + return []; + } + + $Element = array( + 'name' => 'div', + 'attributes' => array('class' => 'footnotes'), + 'elements' => array( + array('name' => 'hr'), + array( + 'name' => 'ol', + 'elements' => array(), + ), + ), + ); + + uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes'); + + foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData) + { + if ( ! isset($DefinitionData['number'])) + { + # fix the footnote logic in visual mode, this might break for more complex footnotes. + if($this->visualMode) + { + $DefinitionData['number'] = $definitionId; + $DefinitionData['count'] = 1; + } + else + { + continue; + } + } + + $text = $DefinitionData['text']; + + $textElements = parent::textElements($text); + + $numbers = range(1, $DefinitionData['count']); + + $backLinkElements = array(); + + foreach ($numbers as $number) + { + $backLinkElements[] = array('text' => ' '); + $backLinkElements[] = array( + 'name' => 'a', + 'attributes' => array( + 'href' => "#fnref$number:$definitionId", + 'rev' => 'footnote', + 'class' => 'footnote-backref', + ), + 'rawHtml' => '↩', + 'allowRawHtmlInSafeMode' => true, + 'autobreak' => false, + ); + } + + unset($backLinkElements[0]); + + $n = count($textElements) -1; + + if ($textElements[$n]['name'] === 'p') + { + $backLinkElements = array_merge( + array( + array( + 'rawHtml' => ' ', + 'allowRawHtmlInSafeMode' => true, + ), + ), + $backLinkElements + ); + + unset($textElements[$n]['name']); + + $textElements[$n] = array( + 'name' => 'p', + 'elements' => array_merge( + array($textElements[$n]), + $backLinkElements + ), + ); + } + else + { + $textElements[] = array( + 'name' => 'p', + 'elements' => $backLinkElements + ); + } + + $Element['elements'][1]['elements'] []= array( + 'name' => 'li', + 'attributes' => array('id' => 'fn:'.$definitionId), + 'elements' => array_merge( + $textElements + ), + ); + } + + return $Element; + } + + # Inline Math + # check https://github.com/BenjaminHoegh/ParsedownMath + # check https://github.com/cben/mathdown/wiki/math-in-markdown + + protected function inlineMath($Excerpt) + { + if(preg_match('/^(? strlen($matches[0]), + 'element' => array( + 'text' => '\(' . $matches[1] . '\)', + ), + ); + } + } + + protected $specialCharacters = array( + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '<', '>', '#', '+', '-', '.', '!', '|', '~', '^', '=' + ); + + // + // Inline Escape + // ------------------------------------------------------------------------- + protected function inlineEscapeSequence($Excerpt) + { + if (isset($Excerpt['text'][1]) + && in_array($Excerpt['text'][1], $this->specialCharacters) + && !preg_match('/(? array( + 'rawHtml' => $Excerpt['text'][1], + ), + 'extent' => 2, + ); + } + } + + # Block Math + protected function blockMath($Line) + { + $Block = array( + 'element' => array( + 'text' => '', + ), + ); + if (preg_match('/^(? 'math'); + + return $Block; + } + elseif ($Block['end'] === '$$' && preg_match('/^(? 'math'); + + return $Block; + } + + $Block['element']['text'] .= "\n" . $Line['body']; + + // ~ + return $Block; + } + + // ~ + protected function blockMathComplete($Block) + { + return $Block; + } + + protected function blockShortcode($Line) + { + if ($this->dispatcher && preg_match('/^\[:.*:\]/', $Line['text'], $matches)) + { + return $this->createShortcodeArray($matches,$block = true); + } + else + { + return; + } + } + + protected function inlineShortcode($Excerpt) + { + $remainder = $Excerpt['text']; + + if ($this->dispatcher && preg_match('/\[:.*:\]/', $remainder, $matches)) + { + return $this->createShortcodeArray($matches, $block = false); + } + else + { + return; + } + } + + protected $allowedShortcodes = false; + + public function setAllowedShortcodes(array $shortcodelist) + { + $this->allowedShortcodes = $shortcodelist; + } + + protected function createShortcodeArray($matches, $block) + { + if(is_array($this->allowedShortcodes) && empty($this->allowedShortcodes)) + { + return array('element' => array()); + } + + $shortcodeString = substr($matches[0], 2, -2); + $shortcodeArray = explode(' ', $shortcodeString, 2); + $shortcode = []; + + $shortcode['name'] = $shortcodeArray[0]; + $shortcode['params'] = false; + + if(is_array($this->allowedShortcodes) && !in_array($shortcode['name'], $this->allowedShortcodes)) + { + return array('element' => array()); + } + + # are there params? + if(isset($shortcodeArray[1])) + { + $shortcode['params'] = []; + + # see: https://www.thetopsites.net/article/58136180.shtml + $pattern = '/(\\w+)\s*=\\s*("[^"]*"|\'[^\']*\'|[^"\'\\s>]*)/'; + preg_match_all($pattern, $shortcodeArray[1], $attributes, PREG_SET_ORDER); + + foreach($attributes as $attribute) + { + if(isset($attribute[1]) && isset($attribute[2])) + { + $shortcode['params'][$attribute[1]] = trim($attribute[2], " \""); + } + } + } + + $html = $this->dispatcher->dispatch('onShortcodeFound', new OnShortcodeFound($shortcode))->getData(); + + # if no shortcode has been processed, add the original string + if(is_array($html) OR is_object($html)) + { + if($block) + { + $html = '

    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 .= '    ' . $url . '' . "\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 %}
    -	
    -	

    {{ translate('Visual Editor') }}

    - -
    + +
    + + {% for block in content %} +
    {{ block.html|raw }}
    + {% endfor %} + +
    + +
    + +
    {% 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: `
    + +
    `, + 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: `
    +
    + + + +
    + + + + + + + + + + + +
    {{value}} +
    +
    add left column
    +
    add right column
    +
    delete column
    +
    +
    {{ value }} + +
    +
    add row above
    +
    add row below
    +
    delete row
    +
    + {{ value }} +
    +
    `, + 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: `
    +
    + + + +
    + + + +
    + + Add definition +
    +
    +
    `, + 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: `
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    `, + 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: `
    + +
    +
    + +

    drag a picture or click to select

    +
    + +
    + +
    + + + +
    +
    + +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + + +
    +
    + +
    + +
    `, + 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', '![]('+ response.data.name +')'); + }) + .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: `
    + +
    +
    + +

    + + + + upload file +

    +
    + +
    + +
    + + + +
    +
    +
    + +
    + + +
    +
    + + +
    +
    +
    `, + 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: `
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + +
    +
    + +
    `, + 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.provider + '-video](' + this.path + ' "click to load video"){#' + 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: ` +
    +
    +
    +

    Choose a content type

    + +
    + +
    +
    + +
    +
    +
    + + + + +
    + +
    + + +
    +
    +
    +
    + `, + 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 '

    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({
    @@ -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',{