Cleaned up kixote branch, removed secrets
21
.dockerignore
Normal file
@@ -0,0 +1,21 @@
|
||||
# Ignore everything
|
||||
**
|
||||
|
||||
# Allow system files and directories
|
||||
!docker-utils/
|
||||
!system/
|
||||
!.htaccess
|
||||
!composer*
|
||||
!index.php
|
||||
|
||||
# Allow content files and directories
|
||||
!cache/
|
||||
!content/
|
||||
!data/
|
||||
!media/
|
||||
!settings/
|
||||
!themes/
|
||||
|
||||
# Ignore unnecessary files inside allowed directories below
|
||||
# This should go after the allowed directories
|
||||
# e.g. docker-utils/test.php
|
25
.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
cache/sitemap.xml
|
||||
content/index.yaml
|
||||
content/00-welcome/index.yaml
|
||||
content/00-welcome/00-setup-your-website.yaml
|
||||
content/00-welcome/01-write-content.yaml
|
||||
content/00-welcome/02-manage-access.yaml
|
||||
content/00-welcome/03-get-help.yaml
|
||||
content/00-welcome/04-markdown-test.yaml
|
||||
content/01-cyanine-theme/index.yaml
|
||||
content/01-cyanine-theme/00-landingpage.yaml
|
||||
content/01-cyanine-theme/01-colors-and-fonts.yaml
|
||||
content/01-cyanine-theme/02-footer.yaml
|
||||
content/01-cyanine-theme/03-content-elements.yaml
|
||||
system/vendor
|
||||
cypress
|
||||
data/navigation
|
||||
data/css
|
||||
node_modules
|
||||
plugins/search
|
||||
settings/settings.yaml
|
||||
settings/secrets.yaml
|
||||
settings/license.yaml
|
||||
settings/users
|
||||
zips
|
||||
cypress.json
|
74
.htaccess
Normal file
@@ -0,0 +1,74 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# If your homepage is http://yourdomain.com/yoursite
|
||||
# Set the RewriteBase to:
|
||||
# RewriteBase /yoursite
|
||||
|
||||
# In some environements, an empty RewriteBase is required:
|
||||
# RewriteBase /
|
||||
|
||||
# Use this to redirect HTTP to HTTPS on apache servers
|
||||
# RewriteCond %{HTTPS} off
|
||||
# RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
|
||||
|
||||
# Use this to redirect www to non-wwww on apache servers
|
||||
# RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
|
||||
# RewriteRule ^(.*)$ http://%1/$1 [R=301,L]
|
||||
|
||||
# Use this to redirect slash/ to url without slash on apache servers
|
||||
# RewriteCond %{REQUEST_FILENAME} !-d
|
||||
# RewriteRule ^(.*)/$ /$1 [R=301,L]
|
||||
|
||||
# Removes index.php
|
||||
RewriteCond %{THE_REQUEST} ^GET.*index\.php [NC]
|
||||
RewriteRule (.*?)index\.php/*(.*) /$1$2 [R=301,NE,L]
|
||||
|
||||
# REWRITE TO INDEX
|
||||
|
||||
# If the requested path and file not /index.php
|
||||
RewriteCond %{REQUEST_URI} !^/index\.php
|
||||
|
||||
# if requested doesn't match a physical file
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
|
||||
# if requested doesn't match a physical folder
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
|
||||
# then rewrite the request to the index.php script
|
||||
RewriteRule ^ index.php [QSA,L]
|
||||
|
||||
|
||||
# FILE/FOLDER PROTECTION
|
||||
|
||||
# Deny access to these file types generally
|
||||
RewriteRule ^(.*)?\.yml$ - [F,L]
|
||||
Rewriterule ^(.*)?\.yaml$ - [F,L]
|
||||
RewriteRule ^(.*)?\.txt$ - [F,L]
|
||||
RewriteRule ^(.*)?\.example$ - [F,L]
|
||||
RewriteRule ^(.*)?\.git+ - [F,L]
|
||||
RewriteRule ^(.*)?\.md - [F,L]
|
||||
RewriteCond %{REQUEST_URI} !/index\.php
|
||||
RewriteRule ^(.*)?\.ph - [F,L]
|
||||
RewriteRule ^(.*)?\.twig - [F,L]
|
||||
RewriteRule ^(media\/tmp\/) - [F,L]
|
||||
|
||||
# Block access to specific files in the root folder
|
||||
RewriteRule ^(composer\.lock|composer\.json|\.htaccess)$ error [F,L]
|
||||
|
||||
# block files and folders starting with a dot except for the .well-known folder (Let's Encrypt)
|
||||
RewriteRule (^|/)\.(?!well-known\/) index.php [L]
|
||||
|
||||
# Allow access to frontend files in author folder
|
||||
RewriteRule ^(system\/typemill\/author\/css\/) - [L]
|
||||
RewriteRule ^(system\/typemill\/author\/img\/) - [L]
|
||||
RewriteRule ^(system\/typemill\/author\/js\/) - [L]
|
||||
|
||||
# redirect all other direct requests to the following physical folders to the index.php so pages with same name work
|
||||
RewriteRule ^(system|content|data|settings|(media\/files\/)) index.php [QSA,L]
|
||||
|
||||
# disallow browsing other folders generally
|
||||
Options -Indexes
|
||||
|
||||
</IfModule>
|
41
Dockerfile
Normal file
@@ -0,0 +1,41 @@
|
||||
FROM php:8.0-apache
|
||||
|
||||
# Install OS dependencies required
|
||||
RUN apt-get update && apt-get upgrade -y && apt-get install git unzip zlib1g-dev libpng-dev -y
|
||||
|
||||
# Adapt apache config
|
||||
RUN a2enmod rewrite \
|
||||
&& echo "ServerName 127.0.0.1" >> /etc/apache2/apache2.conf
|
||||
|
||||
# Install PHP ext-gd
|
||||
RUN docker-php-ext-install gd
|
||||
|
||||
# Copy app content
|
||||
# Use the .dockerignore file to control what ends up inside the image!
|
||||
WORKDIR /var/www/html
|
||||
COPY . .
|
||||
|
||||
# Install server dependencies
|
||||
RUN chmod +x /var/www/html/docker-utils/install-composer && \
|
||||
/var/www/html/docker-utils/install-composer && \
|
||||
./composer.phar update && \
|
||||
chmod +x /var/www/html/docker-utils/init-server
|
||||
|
||||
# Expose useful volumes (see documentation)
|
||||
VOLUME /var/www/html/settings
|
||||
VOLUME /var/www/html/media
|
||||
VOLUME /var/www/html/cache
|
||||
VOLUME /var/www/html/plugins
|
||||
VOLUME /var/www/html/data
|
||||
|
||||
# Create a default copy of content and theme in case of empty directories binding
|
||||
RUN mkdir -p /var/www/html/content.default/ && \
|
||||
cp -R /var/www/html/content/* /var/www/html/content.default/ && \
|
||||
mkdir -p /var/www/html/themes.default/ && \
|
||||
cp -R /var/www/html/themes/* /var/www/html/themes.default/
|
||||
|
||||
VOLUME /var/www/html/content
|
||||
VOLUME /var/www/html/themes
|
||||
|
||||
# Inject default values if content and themes are mounted with empty directories, adjust rights and start the server
|
||||
CMD ["/var/www/html/docker-utils/init-server"]
|
205
cache/cyanine-custom.css
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
.demolink{
|
||||
display:block;
|
||||
font-size: .8em;
|
||||
text-decoration:none;
|
||||
margin-top: 20px;
|
||||
}
|
||||
/*
|
||||
.landingpageintro h1{
|
||||
display: inline-block;
|
||||
background: white;
|
||||
padding: 4px 10px 10px;
|
||||
margin: 15px;
|
||||
}
|
||||
*/
|
||||
.landingpageintro .dim:focus, .landingpageintro .dim:hover {
|
||||
opacity: .8;
|
||||
}
|
||||
.landingpageintro figure {
|
||||
display: block;
|
||||
margin: 0em auto;
|
||||
padding: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
.landingpageintro figure img {
|
||||
display: inline;
|
||||
vertical-align: baseline;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
|
||||
.landingpageintro h1{
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
.landingpageintro p{
|
||||
/* display: inline; */
|
||||
/* background: white; */
|
||||
/* line-height: 2.1rem;
|
||||
padding: 6px 4px 4px; */
|
||||
|
||||
font-weight: 500;
|
||||
}
|
||||
.landingpageintro p a{
|
||||
color: white;
|
||||
font-size: .8em;
|
||||
}
|
||||
|
||||
.introbg.pa4{
|
||||
padding: 0;
|
||||
}
|
||||
@media screen and (min-width:60em) {
|
||||
/*
|
||||
.landingpageintro h1{
|
||||
font-size: 4.5rem;
|
||||
}
|
||||
*/
|
||||
.introbg.pa4{
|
||||
padding: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.landingpageinfo ol{
|
||||
text-align: left;
|
||||
list-style: none;
|
||||
counter-reset: item;
|
||||
}
|
||||
.landingpageinfo ol li{
|
||||
padding:.5rem 0;
|
||||
counter-increment: item;
|
||||
}
|
||||
.landingpageinfo ol li:before {
|
||||
content: counter(item);
|
||||
margin-right: 10px;
|
||||
background: lightseagreen;
|
||||
border-radius: 100%;
|
||||
color: white;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
margin-left: -40px;
|
||||
font-size: .8em;
|
||||
line-height: 30px;
|
||||
width: 30px;
|
||||
font-weight: 400;
|
||||
}
|
||||
.landingpageinfo ol a{
|
||||
color:lightseagreen;
|
||||
}
|
||||
|
||||
#mlb2-10705438.ml-form-embedContainer .ml-form-embedWrapper .ml-form-embedBody .ml-block-form .ml-form-embedPermissions,
|
||||
#mlb2-10705438.ml-form-embedContainer .ml-form-embedWrapper .ml-form-embedBody .ml-block-form .ml-form-embedSubmit{
|
||||
float:none;
|
||||
}
|
||||
.ml-form-embedBody{
|
||||
width:100%;
|
||||
background-color:white;
|
||||
}
|
||||
#mlb2-10705438.ml-form-embedContainer .ml-form-embedWrapper .ml-form-embedBody, #mlb2-10705438.ml-form-embedContainer .ml-form-embedWrapper .ml-form-successBody{
|
||||
padding: 40px 40px 40px 40px!important;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 50em) {
|
||||
.ml-form-embedBody{
|
||||
width:60%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*paddle*/
|
||||
.mt-auto{
|
||||
margin-top:auto;
|
||||
}
|
||||
.paddlebox h2{
|
||||
margin-top:1em;
|
||||
}
|
||||
.paddlebox a,
|
||||
.paddlebox a:link,
|
||||
.paddlebox a:hover,
|
||||
.paddlebox a:focus,
|
||||
.paddlebox a:active,
|
||||
.paddlebox a:visited{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.paddlebox strong{
|
||||
position: relative;
|
||||
background-color: RGBA(32, 178, 170, .2);
|
||||
font-size: 1.2em;
|
||||
padding: 1px;
|
||||
}
|
||||
.paddlebox strong:before{
|
||||
content:"";
|
||||
left:0em;
|
||||
top:0em;
|
||||
border-width:3px;
|
||||
border-style:solid;
|
||||
border-color:lightseagreen;
|
||||
position:absolute;
|
||||
border-right-color:transparent;
|
||||
width:100%;
|
||||
height:1.1em;
|
||||
transform:rotate(2deg);
|
||||
opacity:0.5;
|
||||
border-radius:0.25em;
|
||||
}
|
||||
|
||||
.paddlebox strong:after{
|
||||
content:"";
|
||||
left:0em;
|
||||
top:0em;
|
||||
border-width:4px;
|
||||
border-style:solid;
|
||||
border-color:lightseagreen;
|
||||
border-left-color:transparent;
|
||||
border-top-color:transparent;
|
||||
position:absolute;
|
||||
width:100%;
|
||||
height:1em;
|
||||
transform:rotate(-1deg);
|
||||
opacity:0.5;
|
||||
border-radius:0.25em;
|
||||
}
|
||||
.paddlebox ul{
|
||||
list-style-type: "✓ ";
|
||||
padding-left:1em;
|
||||
}
|
||||
.paddle_button{
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.myclassbox p{
|
||||
background:lightseagreen;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
20% {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
40% {
|
||||
transform: translateY(-200%);
|
||||
}
|
||||
60% {
|
||||
transform: translateY(-300%);
|
||||
}
|
||||
80% {
|
||||
transform: translateY(-400%);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.slide-up {
|
||||
/* animation: slideUp 1s ease-in-out; */
|
||||
animation: slideUp 8s infinite;
|
||||
will-change: transform;
|
||||
animation-delay: 3s;
|
||||
}
|
||||
|
||||
.figureborder figure {
|
||||
border: 1px solid lightgray;
|
||||
}
|
44
composer.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "typemill/typemill",
|
||||
"type": "project",
|
||||
"description": "A lightweight flat-file-cms for advanced publishing. Create websites, handbooks, documentations, and transform them into ebooks.",
|
||||
"keywords": ["cms", "vue", "markdown", "slim", "php", "flat-file", "publishing", "documentations", "manuals", "handbooks", "pdf", "epub", "paged media", "paged.js"],
|
||||
"homepage": "https://typemill.net",
|
||||
"license": "MIT",
|
||||
"config": {
|
||||
"vendor-dir": "system/vendor",
|
||||
"allow-plugins": {
|
||||
"composer/installers": true
|
||||
},
|
||||
"platform": {
|
||||
"php": "8.0.0"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.0",
|
||||
"slim/slim": "4.*",
|
||||
"slim/psr7": "^1.5",
|
||||
"php-di/php-di": "^6.3",
|
||||
"php-di/slim-bridge": "^3.2",
|
||||
"slim/twig-view": "^3.3",
|
||||
"slim/flash": "^0.4.0",
|
||||
"slim/csrf": "^1.2",
|
||||
"vlucas/valitron": "^1.4",
|
||||
"symfony/yaml": "^5.4",
|
||||
"symfony/event-dispatcher": "^5.4",
|
||||
"erusev/parsedown": "dev-master",
|
||||
"erusev/parsedown-extra": "dev-master",
|
||||
"jbroadway/urlify": "1.1.3",
|
||||
"laminas/laminas-permissions-acl": "^2.10",
|
||||
"akrabat/proxy-detection-middleware": "^1.0.0",
|
||||
"gregwar/captcha": "master"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Typemill\\": "system/typemill/",
|
||||
"Plugins\\": "plugins/"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
2362
composer.lock
generated
Normal file
8
content/.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
meta:
|
||||
author: Sebastian
|
||||
created: '2024-09-10'
|
||||
time: 12-43-18
|
||||
navtitle: null
|
||||
modified: '2024-08-01'
|
||||
title: ''
|
||||
description: ''
|
15
content/00-getting-started/00-create-your-first-page.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Create Your First Page
|
||||
|
||||
To create a new page in Typemill, follow these simple steps:
|
||||
|
||||
* Use the **interactive navigation** located on the left side of the screen.
|
||||
* **Enter a page title** for your new page into one of the grey input fields.
|
||||
* **Click the page icon** to create a new page. You can also create a folder if you want to add sub-pages later.
|
||||
|
||||
Here's a quick breakdown of the options.
|
||||
|
||||
* **Folders**: Use folders to organize your website hierarchically. Folders can contain subfolders or pages. Unlike a traditional file system, folders in Typemill can also have content, similar to pages.
|
||||
* **Pages**: Utilize pages for simple content without needing a deeper structure.
|
||||
|
||||
Additionally, you can rearrange your pages by dragging and dropping them within the navigation panel.
|
||||
|
12
content/00-getting-started/00-create-your-first-page.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
meta:
|
||||
navtitle: 'create your first page'
|
||||
title: 'Create Your First Page'
|
||||
description: "To create a new page in Typemill, follow these simple steps: \nUse the interactive navigation located on the left side of the screen. \nEnter a page title for"
|
||||
owner: trendschau
|
||||
author: 'Sebastian Schürmanns'
|
||||
modified: '2024-04-27'
|
||||
created: '2024-04-25'
|
||||
time: 17-24-48
|
||||
hide: false
|
||||
noindex: false
|
||||
template: ''
|
14
content/00-getting-started/01-edit-your-page.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Edit Your Page
|
||||
|
||||
Typemill uses [Markdown](https://typemill.net/writers/markdown) to create content. But don't worry: The visual editor of Typemill will help you write content in a WYSIWYG-style (what you see is what you get). You can also switch to the [raw-markdown-mode](/tm/content/raw/getting-started/edit-your-page) with the sticky 'raw' button located at the bottom right corner of the editor interface.
|
||||
|
||||
The content is organized in blocks, and you can move each content block up and down with drag & drop.
|
||||
|
||||
- **To edit** a content block, simply click on it.
|
||||
- **To add** a new block at the end of the page, use the edit buttons below.
|
||||
- **To add** a new block above this block, use the "add" button that appears when you hover over this block.
|
||||
|
||||
You can add all kinds of content like tables, quotes, images, files, an automatic table of contents (TOC), or YouTube videos. There are also plugins to embed media from other platforms or to use selected HTML tags in content.
|
||||
|
||||
If you are a developer, you can write plugins and integrate nearly everything into the editor with `{::]` shortcodes.
|
||||
|
9
content/00-getting-started/01-edit-your-page.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
meta:
|
||||
owner: trendschau
|
||||
author: 'Sebastian Schürmanns'
|
||||
created: '2024-04-25'
|
||||
time: 17-37-50
|
||||
navtitle: 'edit your page'
|
||||
modified: '2024-04-27'
|
||||
title: 'Edit Your Page'
|
||||
description: 'Typemill uses Markdown to create content. But don''t worry: The visual editor of Typemill will help you write content in a WYSIWYG-style (what you see is what'
|
13
content/00-getting-started/02-edit-the-page-meta.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Edit the Page Meta
|
||||
|
||||
You can edit detailed meta-information by switching to the meta-tab at the top of the page. While most of the meta-information should be self-explanatory, let's delve into some details:
|
||||
|
||||
- **Slug**: This is the URL. Changing the slug will alter the URL, potentially affecting indexing in search engines like Google.
|
||||
- **Navigation Title**: Change the title for the navigation.
|
||||
- **Meta Content**: Modify standard meta-tags like title and description. The image will be used when pages are shared on social media platforms and may also appear in the frontend, depending on your theme.
|
||||
- **Author**: The author will appear in the frontend (depending on the theme); the owner has the rights to edit the page.
|
||||
- **Access Rights**: You can restrict access to the page in the frontend. See the example for an [restricted page here](/tm/content/visual/publish-status/restricted).
|
||||
- **Date**: The date of creation, last publication, and a manually set date.
|
||||
- **Reference**: You can create different references for a page, such as a redirect 301 or 302 to another page, copying another page, or linking the page to an external page. These features are demonstrated in the [pages status examples](tm/content/visual/publish-status).
|
||||
- **Visibility**: Hide a page from the navigation and exclude it from the index with a noindex-tag. These features are demonstrated in the [pages status examples](tm/content/visual/publish-status).
|
||||
|
9
content/00-getting-started/02-edit-the-page-meta.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
meta:
|
||||
owner: trendschau
|
||||
author: 'Sebastian Schürmanns'
|
||||
created: '2024-04-25'
|
||||
time: 17-48-32
|
||||
navtitle: 'edit the page meta'
|
||||
modified: '2024-04-27'
|
||||
title: 'Edit the Page Meta'
|
||||
description: 'You can edit detailed meta-information by switching to the meta-tab at the top of the page. While most of the meta-information should be self-explanatory,'
|
17
content/00-getting-started/03-publish-your-page.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Publish Your Page
|
||||
|
||||
Is your page ready to go live? Let's publish it! In Typemill, a page can have three different statuses:
|
||||
|
||||
- **Unpublished** (red): The page is not published and not visible in the frontend yet.
|
||||
- **Published** (green): The page is published and visible in the frontend.
|
||||
- **Modified** (orange): The page is published and has changes that have not been published yet.
|
||||
|
||||
You can view and manage the status of the page in the sticky publish bar at the bottom. There, you can:
|
||||
|
||||
- **Publish** a page or publish modifications.
|
||||
- **Discard** modifications and revert content to the published version.
|
||||
- **Unpublish** a published page.
|
||||
- **Delete** a page.
|
||||
|
||||
You can also see the status of all pages in the content navigation on the left side, indicated by the colors red, orange, and green.
|
||||
|
9
content/00-getting-started/03-publish-your-page.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
meta:
|
||||
owner: trendschau
|
||||
author: 'Sebastian Schürmanns'
|
||||
created: '2024-04-25'
|
||||
time: 18-56-38
|
||||
navtitle: 'publish your page'
|
||||
modified: '2024-04-27'
|
||||
title: 'Publish Your Page'
|
||||
description: 'Is your page ready to go live? Let''s publish it! In Typemill, a page can have three different statuses:'
|
8
content/00-getting-started/index.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Getting Started with Typemill
|
||||
|
||||
Use this demo-content to familiarize yourself with Typemill.
|
||||
|
||||
Not sure where to start?
|
||||
|
||||
Simply select a topic in the navigation; it will briefly explain what you can do with Typemill.
|
||||
|
13
content/00-getting-started/index.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
meta:
|
||||
navtitle: 'getting started'
|
||||
title: 'Getting Started with Typemill'
|
||||
description: 'Use this demo-content to familiarize yourself with Typemill. Not sure where to start?'
|
||||
owner: trendschau
|
||||
author: 'Sebastian Schürmanns'
|
||||
modified: '2024-05-17'
|
||||
created: '2024-04-25'
|
||||
time: 13-16-58
|
||||
hide: false
|
||||
noindex: false
|
||||
contains: pages
|
||||
template: ''
|
1
content/01-publish-status/00-unpublished.txt
Normal file
@@ -0,0 +1 @@
|
||||
["# Unpublished Page","This is an unpublished page. Unpublished pages are marked red in the navigation and in the publish bar."]
|
6
content/01-publish-status/00-unpublished.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
meta:
|
||||
owner: typemill
|
||||
author: ''
|
||||
created: '2024-03-19'
|
||||
time: 18-40-21
|
||||
navtitle: unpublished
|
4
content/01-publish-status/01-modified.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Modified Page
|
||||
|
||||
This is a modified page. Modified pages are marked orange in the navigation and in the publish bar. Modified pages are published pages that have been modified in the editor. The published page is still online in the original version. You can publish the mofidifications to make them visible in the frontend, or you can discard the changes to restore the published version. If you discard your changes, then your changes are lost and cannot be restored.
|
||||
|
1
content/01-publish-status/01-modified.txt
Normal file
@@ -0,0 +1 @@
|
||||
["# Modified Page","This is a modified page. Modified pages are marked orange in the navigation and in the publish bar. Modified pages are published pages that have been modified in the editor. The published page is still online in the original version. You can publish the mofidifications to make them visible in the frontend, or you can discard the changes to restore the published version. If you discard your changes, then your changes are lost and cannot be restored.","A page becomes modified if you either:","* Save a block in the visual editor or\n* Save a draft in the raw editor."]
|
9
content/01-publish-status/01-modified.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
meta:
|
||||
owner: typemill
|
||||
author: ''
|
||||
created: '2024-03-19'
|
||||
time: 18-40-27
|
||||
navtitle: modified
|
||||
modified: '2024-03-19'
|
||||
title: 'Modified Page'
|
||||
description: 'This is a modified page. Modified pages are marked orange in the navigation and in the publish bar. Modified pages are published pages that have been modified'
|
4
content/01-publish-status/02-published.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Published Page
|
||||
|
||||
This is a published page. Published pages are marked green in the navigation and in the publish bar. Published pages are visible in the frontend unless they are not restricted. Published pages are visible in the navigation unless they are not hidden. Published pages are added to the xml-sitemap and alowed to crawl unless they are not marked as noindex.
|
||||
|
9
content/01-publish-status/02-published.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
meta:
|
||||
owner: typemill
|
||||
author: ''
|
||||
created: '2024-03-19'
|
||||
time: 18-40-40
|
||||
navtitle: published
|
||||
modified: '2024-03-19'
|
||||
title: 'Published Page'
|
||||
description: 'This is a published page. Published pages are marked green in the navigation and in the publish bar. Published pages are visible in the frontend unless they'
|
6
content/01-publish-status/03-hidden.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Hidden Page
|
||||
|
||||
This is a hidden page. Hidden pages are published pages that are excluded from all navigation elements. You need to know the url to visit a hidden page. Hidden pages are great if you want to check your page in the frontend without beeing visible for visitors directly. Hidden pages are not excluded from the xml-sitemap and they do not have a noindex attribute.
|
||||
|
||||
You can hide pages with a checkbox in the meta-tab (sroll to the bottom).
|
||||
|
11
content/01-publish-status/03-hidden.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
meta:
|
||||
navtitle: hidden
|
||||
title: 'Hidden Page'
|
||||
description: 'This is a hidden page. Hidden pages are published pages that are excluded from all navigation elements. You need to know the url to visit a hidden page. Hidden'
|
||||
owner: typemill
|
||||
author: ''
|
||||
modified: '2024-03-19'
|
||||
created: '2024-03-19'
|
||||
time: 18-49-34
|
||||
hide: true
|
||||
noindex: false
|
4
content/01-publish-status/04-noindex.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Noindex Page
|
||||
|
||||
This page is excluded from the xml-sitemap (see url of the xml-sitemap in the system settings) and the page has a noindex-attribute to stop robots from crawling and indexing. You can activate the noindex in the meta-tab (scroll to the bottom).
|
||||
|
11
content/01-publish-status/04-noindex.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
meta:
|
||||
navtitle: noindex
|
||||
title: 'Noindex Page'
|
||||
description: 'This page is excluded from the xml-sitemap (see url of the xml-sitemap in the system settings) and the page has a noindex-attribute to stop robots from'
|
||||
owner: typemill
|
||||
author: ''
|
||||
modified: '2024-03-19'
|
||||
created: '2024-03-19'
|
||||
time: 18-54-12
|
||||
noindex: true
|
||||
hide: false
|
11
content/01-publish-status/05-restricted.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Restricted Page
|
||||
|
||||
This is a restricted page (to activate the feature please read the paragraph below). Restricted pages are published pages that require an authentication to see the content. You can cut the content with a hr-line, everything below this line is not visible in frontend for unauthenticated users.
|
||||
|
||||
---
|
||||
|
||||
You can configure page restrictions in the system settings. Open the tab "restrictions" and choose the options. You can restrict the whole website or you can restrict certain pages. To restrict certain pages, go to the meta-tab of a page and scroll down to the section "Access & Rights". There you have two options:
|
||||
|
||||
* You can choose a userrole that should have access to the content.
|
||||
* Or you can add one or several usernames who should have access to the content.
|
||||
|
12
content/01-publish-status/05-restricted.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
meta:
|
||||
navtitle: restricted
|
||||
owner: typemill
|
||||
author: ''
|
||||
allowedrole: member
|
||||
created: '2024-03-19'
|
||||
time: 18-56-07
|
||||
hide: false
|
||||
noindex: false
|
||||
modified: '2024-03-19'
|
||||
title: 'Restricted Page'
|
||||
description: 'This is a restricted page. Restricted pages are published pages that require an authentication to see the content. You can cut the content with a hr-line,'
|
4
content/01-publish-status/06-redirect-301.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Redirect 301
|
||||
|
||||
This page redirects to the page "published" with a 301 permanent redirect. You can choose this option in the meta-tab, section "References".
|
||||
|
13
content/01-publish-status/06-redirect-301.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
meta:
|
||||
navtitle: 'redirect 301'
|
||||
owner: typemill
|
||||
author: ''
|
||||
created: '2024-03-19'
|
||||
time: 19-00-58
|
||||
reference: /publish-status/published
|
||||
referencetype: redirect301
|
||||
hide: false
|
||||
noindex: false
|
||||
modified: '2024-03-19'
|
||||
title: 'Redirect 301'
|
||||
description: 'This page redirects to another page with a 301 permanent redirect. You can choose this option in the meta-tab, section "References".'
|
4
content/01-publish-status/07-redirect-302.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Redirect 302
|
||||
|
||||
This page redirects to the page "published" with a 302 temporary redirect. You can choose this option in the meta-tab, section "References".
|
||||
|
13
content/01-publish-status/07-redirect-302.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
meta:
|
||||
navtitle: 'redirect 302'
|
||||
owner: typemill
|
||||
author: ''
|
||||
created: '2024-03-19'
|
||||
time: 19-02-36
|
||||
reference: /publish-status/published
|
||||
referencetype: redirect302
|
||||
hide: false
|
||||
noindex: false
|
||||
modified: '2024-03-19'
|
||||
title: 'Redirect 302'
|
||||
description: 'This page redirects to the published page with a 302 temporary redirect. You can choose this option in the meta-tab, section "References".'
|
4
content/01-publish-status/08-copy.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Copy
|
||||
|
||||
You can copy the content of another page to this page. This is helpful in some cases if you want to reference the content in several other navigation points on the website. You can edit the referenced page and all referenced sites will show that content. You can copy the page of another page in the meta-tab, section "references".
|
||||
|
13
content/01-publish-status/08-copy.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
meta:
|
||||
navtitle: copy
|
||||
owner: typemill
|
||||
author: ''
|
||||
created: '2024-03-19'
|
||||
time: 19-03-54
|
||||
reference: /publish-status/published
|
||||
referencetype: copy
|
||||
hide: false
|
||||
noindex: false
|
||||
modified: '2024-03-19'
|
||||
title: Copy
|
||||
description: 'You can copy the content of another page to this page. This is helpful in some cases if you want to reference the content in several other navigation points on'
|
4
content/01-publish-status/09-link.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# External link
|
||||
|
||||
You can link to an external page in the navigation with the reference feature in the meta-tab. Just choose "link" and add the url to the external page.
|
||||
|
13
content/01-publish-status/09-link.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
meta:
|
||||
navtitle: link
|
||||
title: 'External link'
|
||||
description: 'You can link to an external page in the navigation with the reference feature in the meta-tab. Just choose "link" and add the url to the external page.'
|
||||
owner: typemill
|
||||
author: ''
|
||||
modified: '2024-03-19'
|
||||
created: '2024-03-19'
|
||||
time: 19-07-05
|
||||
reference: 'https://typemill.net'
|
||||
referencetype: outlink
|
||||
hide: false
|
||||
noindex: false
|
14
content/01-publish-status/index.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Publish Status
|
||||
|
||||
In Typemill, you can save drafts, publish pages, unpublish pages, and delete pages using a sticky publish panel at the bottom of each page. For published pages, you can also save modifications, discard modifications, and publish modifications.
|
||||
|
||||
The current status of the page is indicated with colors in the publish panel and in the navigation. This way, an author can always see the status of each page and the current state of the whole website.
|
||||
|
||||
- **Save a draft**: The page is not published and not accessible on the frontend. The status color is red.
|
||||
- **Publish a page**: The page is published and accessible on the frontend. The status color is green.
|
||||
- **Unpublish a page**: The page is not accessible on the frontend and only accessible in the author interface. The status color is red again.
|
||||
- **Delete a page**: The page is completely deleted from the frontend and the author interface.
|
||||
- **Save modifications**: The page is published and accessible on the frontend. The modified draft is only visible in the author interface. The status color is orange.
|
||||
- **Discard modifications**: The modifications are deleted, and the live version is restored in the author interface. The status color returns to green.
|
||||
- **Publish modifications**: The modifications are published to the live version. The status color returns to green.
|
||||
|
12
content/01-publish-status/index.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
meta:
|
||||
navtitle: 'page status'
|
||||
title: 'Publish Status'
|
||||
description: 'In Typemill you can save drafts, publish pages, unpublish pages, and delete pages with a sticky publish panel at the bottom of each page. For published pages,'
|
||||
owner: typemill
|
||||
author: ''
|
||||
modified: '2024-03-19'
|
||||
created: '2024-03-19'
|
||||
time: 18-40-10
|
||||
contains: pages
|
||||
hide: false
|
||||
noindex: false
|
22
content/02-news/202405171834-fast-websites.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Fast Websites
|
||||
|
||||
Typemill is a lightweight, flat-file CMS specifically designed for efficient web publishing. It excels in creating documentation, manuals, knowledge bases, wikis, and eBooks. Its performance-driven architecture ensures rapid page loading and a streamlined user experience.
|
||||
|
||||
## Performance Features
|
||||
|
||||
1. **Full Points for Google Page Speed**: Typemill websites typically achieve perfect scores on Google PageSpeed Insights without additional optimizations. This is due to its optimized code, lightweight architecture, and refined frontend code.
|
||||
2. **SEO Benefits**: The excellent performance makes Typemill a great choice for SEO. Additionally, it offers an SEO plugin that integrates data from Google Search Console, providing valuable insights for optimizing content.
|
||||
3. **Efficient Workflow**: With its simple approach and excellent user experience, website managers and authors can accomplish tasks quickly and efficiently.
|
||||
|
||||
## Benefits
|
||||
|
||||
- **Quick Setup and Maintenance**: Typemill's straightforward installation and lack of a database simplify setup and maintenance.
|
||||
- **Scalability**: The flat-file approach ensures consistent performance even as the site grows.
|
||||
- **SEO Friendly**: Fast loading times and SEO tools contribute to better search engine rankings, improving visibility.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Typemill is an ideal choice for small to medium-sized websites focused on documentation and knowledge management. Its flat-file structure, lightweight design, and optimized performance make it a robust solution for fast and reliable web publishing.
|
||||
|
||||
For more information, visit [Typemill](https://typemill.net).
|
||||
|
12
content/02-news/202405171834-fast-websites.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
meta:
|
||||
navtitle: 'Fast Websites'
|
||||
title: 'Fast Websites'
|
||||
description: 'Typemill is a lightweight, flat-file CMS specifically designed for efficient web publishing. It excels in creating documentation, manuals, knowledge bases,'
|
||||
heroimage: ''
|
||||
owner: trendschau
|
||||
author: 'Sebastian Schürmanns'
|
||||
modified: '2024-05-17'
|
||||
created: '2024-05-17'
|
||||
time: 18-34-46
|
||||
hide: false
|
||||
noindex: false
|
33
content/02-news/202405171843-reports-and-handbooks.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Reports and Handbooks
|
||||
|
||||
Small organizations often face the challenge of producing and publishing annual reports, impact reports, and other essential documents efficiently. Typemill offers an excellent solution by enabling users to create content once and publish it both as a website and a PDF. This not only saves time but also reduces costs, making it an ideal choice for small organizations.
|
||||
|
||||
## Save Time: Write Once, Publish Twice
|
||||
|
||||
One of the standout features of Typemill is its ability to streamline the reporting process. By creating your report in Typemill, you can effortlessly generate both a web-based version and a PDF version from the same source. This dual-publishing capability is particularly beneficial for:
|
||||
|
||||
- **Annual Reports**: Showcase your organization's yearly achievements online and provide a downloadable PDF for stakeholders.
|
||||
- **Impact Reports**: Highlight the impact of your projects with an engaging web presentation and a formal PDF document.
|
||||
|
||||
## Cost-Effective Solution
|
||||
|
||||
Using Typemill helps small organizations save money by minimizing the need for separate tools and platforms for web and print publishing. The simple and intuitive interface ensures that staff can quickly learn and use the system, further reducing training and operational costs.
|
||||
|
||||
## Versatile Use Cases
|
||||
|
||||
Beyond reporting, Typemill is perfect for creating various handbooks and company documents. Some additional use cases include:
|
||||
|
||||
- **Employee Handbooks**: Develop comprehensive, web-based employee manuals that are easy to update and accessible to all staff members.
|
||||
- **Company Policies and Procedures**: Maintain a centralized repository of policies and procedures that can be viewed online and downloaded as needed.
|
||||
- **Training Manuals**: Create detailed training materials that are available both as interactive web pages and printable PDFs.
|
||||
|
||||
## Templates for Speed
|
||||
|
||||
Typemill allows you to load pre-made reporting templates and store your own templates to further speed up the reporting process. This feature ensures consistency and saves time, enabling you to focus more on content creation and less on formatting.
|
||||
|
||||
## Conclusion
|
||||
|
||||
For small organizations and companies looking to optimize their reporting and documentation processes, Typemill offers an efficient, cost-effective solution. Its ability to produce both web and PDF versions of reports from a single source not only saves time but also ensures consistency across formats. Additionally, its versatility in creating various handbooks and company documents makes it an invaluable tool for any small organization.
|
||||
|
||||
Explore more about Typemill and how it can benefit your organization at [Typemill](https://typemill.net).
|
||||
|
12
content/02-news/202405171843-reports-and-handbooks.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
meta:
|
||||
navtitle: 'Reports and Handbooks'
|
||||
title: 'Reports and Handbooks'
|
||||
description: 'Small organizations often face the challenge of producing and publishing annual reports, impact reports, and other essential documents efficiently. Typemill'
|
||||
heroimage: ''
|
||||
owner: trendschau
|
||||
author: 'Sebastian Schürmanns'
|
||||
modified: '2024-05-17'
|
||||
created: '2024-05-17'
|
||||
time: 18-43-57
|
||||
hide: false
|
||||
noindex: false
|
18
content/02-news/202405171855-documentations-and-manuals.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Documentations and Manuals
|
||||
|
||||
Small companies often need to create comprehensive documentation and manuals for both digital and physical products. Typemill offers an ideal solution, allowing you to effortlessly produce manuals as both PDFs and websites.
|
||||
|
||||
## Intuitive Authoring Experience
|
||||
|
||||
Typemill is designed with non-technical writers in mind, featuring an intuitive author interface and a WYSIWYG-style Markdown editor. This makes it easy for anyone to create and maintain high-quality documentation.
|
||||
|
||||
## Versatility and Efficiency
|
||||
|
||||
Whether you’re documenting a software application or a physical product, Typemill enables you to manage your content efficiently and publish it in multiple formats. This versatility ensures that your documentation is always accessible and up-to-date.
|
||||
|
||||
### Conclusion
|
||||
|
||||
Typemill stands out among many tools for documentation and manuals due to its ease of use and powerful features. It's the perfect choice for small companies looking to streamline their documentation process.
|
||||
|
||||
Explore more about Typemill and how it can benefit your organization at [Typemill](https://typemill.net).
|
||||
|
12
content/02-news/202405171855-documentations-and-manuals.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
meta:
|
||||
navtitle: 'Documentations and Manuals'
|
||||
title: 'Documentations and Manuals'
|
||||
description: 'Small companies often need to create comprehensive documentation and manuals for both digital and physical products. Typemill offers an ideal solution,'
|
||||
heroimage: media/live/favicon-180x180.png
|
||||
owner: trendschau
|
||||
author: 'Sebastian Schürmanns'
|
||||
modified: '2024-05-17'
|
||||
created: '2024-05-17'
|
||||
time: 18-55-26
|
||||
hide: false
|
||||
noindex: false
|
4
content/02-news/index.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# News or Blog
|
||||
|
||||
You can add one or many news- or blog-sections to your website. Simply create a new folder and change the content of the folder from "pages" to "posts" in the meta-tab. You can also transform a folder with pages into a folder with posts and vice versa. The only difference between posts and pages is the sorting: Pages are sorted alphabetically, while posts are sorted by the publish date. Posts are visible as a list. They are not included in the navigation on the left side.
|
||||
|
12
content/02-news/index.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
meta:
|
||||
navtitle: news
|
||||
title: 'Blog or News'
|
||||
description: 'You can add one or many blog- or news-sections to your website. Just create a new folder and change the content of the folder from "pages" to "posts" in the'
|
||||
owner: typemill
|
||||
author: ''
|
||||
modified: '2024-03-19'
|
||||
created: '2024-03-19'
|
||||
time: 19-12-31
|
||||
hide: false
|
||||
noindex: false
|
||||
contains: posts
|
6
content/index.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Typemill
|
||||
|
||||
Typemill is a lightweight, flat-file CMS designed for simple, fast, and flexible website and eBook creation using Markdown. Create handbooks, documentation, manuals, reports, traditional websites, online novels, and more.
|
||||
|
||||
Stay in the loop and subscribe to the [Typemill newsletter](https://typemill.net/news)!
|
||||
|
3
cypress.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"baseUrl": "http://localhost/EDIT_YOUR_LOCAL_BASE_URL_HERE"
|
||||
}
|
3
cypress/fixtures/01_setup/default-settings.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
setup: true
|
||||
language: en
|
||||
welcome: true
|
@@ -0,0 +1,51 @@
|
||||
setup: false
|
||||
language: en
|
||||
welcome: false
|
||||
title: TYPEMILL
|
||||
author: Unknown
|
||||
copyright: ©
|
||||
year: "2022"
|
||||
langattr: en
|
||||
editor: visual
|
||||
formats:
|
||||
- markdown
|
||||
- headline
|
||||
- ulist
|
||||
- olist
|
||||
- table
|
||||
- quote
|
||||
- notice
|
||||
- image
|
||||
- video
|
||||
- file
|
||||
- toc
|
||||
- hr
|
||||
- definition
|
||||
- code
|
||||
- shortcode
|
||||
access: null
|
||||
pageaccess: null
|
||||
hrdelimiter: null
|
||||
restrictionnotice: ""
|
||||
wraprestrictionnotice: null
|
||||
headlineanchors: null
|
||||
displayErrorDetails: null
|
||||
twigcache: null
|
||||
proxy: null
|
||||
trustedproxies: ""
|
||||
headersoff: null
|
||||
urlschemes: ""
|
||||
svg: null
|
||||
recoverpw: null
|
||||
recoverfrom: ""
|
||||
recoversubject: ""
|
||||
recovermessage: ""
|
||||
securitylog: null
|
||||
oldslug: null
|
||||
refreshcache: null
|
||||
pingsitemap: null
|
||||
images:
|
||||
live:
|
||||
width: "820"
|
||||
logo: ""
|
||||
favicon: ""
|
@@ -0,0 +1,9 @@
|
||||
username: trendschau
|
||||
email: trendschau@gmail.com
|
||||
userrole: administrator
|
||||
password: $2y$10$SMeVwJeo/5vyjeI68uFi.OJZhSR.3KQTVb5zcKF5D65eZ8CDpX29.
|
||||
lastlogin: 1646578390
|
||||
image: ''
|
||||
description: ''
|
||||
firstname: Sebastian
|
||||
lastname: Schürmanns
|
165
cypress/integration/01_setup/01-system-setup-signup.spec.js
Normal file
@@ -0,0 +1,165 @@
|
||||
describe("Typemill Setup with Signup", function () {
|
||||
before(function () {
|
||||
// reset users and settings
|
||||
cy.task("resetSetup");
|
||||
});
|
||||
|
||||
it("validates form input", function () {
|
||||
// visit setup form
|
||||
cy.visit("/setup");
|
||||
// cy.visit('/setup',{ onBeforeLoad: (_contentWindow) => { Object.defineProperty(navigator, 'language', { value: 'fr-FR' }) } })
|
||||
cy.url().should("include", "/setup");
|
||||
|
||||
// add data and check attributes
|
||||
cy.get('input[name="username"]')
|
||||
.type("?1")
|
||||
.should("have.value", "?1")
|
||||
.and("have.attr", "required");
|
||||
|
||||
cy.get('input[name="email"]')
|
||||
.type("trendschau.net")
|
||||
.should("have.value", "trendschau.net")
|
||||
.and("have.attr", "required");
|
||||
|
||||
cy.get('input[name="password"]')
|
||||
.type("pass")
|
||||
.should("have.value", "pass")
|
||||
.and("have.attr", "required");
|
||||
|
||||
// submit and get validation errors
|
||||
cy.get("form").submit();
|
||||
cy.get("#flash-message").should(
|
||||
"contain",
|
||||
"Please check your input and try again"
|
||||
);
|
||||
cy.get(".error").should("contain", "invalid characters");
|
||||
cy.get(".error").should("contain", "e-mail is invalid");
|
||||
cy.get(".error").should("contain", "Length between 5 - 20");
|
||||
});
|
||||
|
||||
/*
|
||||
it('fails without CSRF-token', function ()
|
||||
{
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: '/setup', // baseUrl is prepended to url
|
||||
form: true, // indicates the body should be form urlencoded and sets Content-Type: application/x-www-form-urlencoded headers
|
||||
failOnStatusCode: false,
|
||||
body: {
|
||||
username: 'trendschau',
|
||||
email: 'trendschau@gmail.com',
|
||||
password: 'password'
|
||||
}
|
||||
})
|
||||
.its('body')
|
||||
.should('include', 'The form has a timeout')
|
||||
})
|
||||
*/
|
||||
it("fails without CSRF-token", function () {
|
||||
cy.visit("/setup");
|
||||
|
||||
// enter correct data
|
||||
cy.get('input[name="username"]').clear().type("trendschau");
|
||||
cy.get('input[name="email"]').clear().type("trendschau@gmail.com");
|
||||
cy.get('input[name="password"]').clear().type("password");
|
||||
cy.get("#csrf_value").then((elem) => {
|
||||
elem.val("wrongvalue");
|
||||
});
|
||||
|
||||
// submit and get validation errors
|
||||
cy.get("form").submit();
|
||||
cy.get("#flash-message").should("contain", "form has a timeout");
|
||||
});
|
||||
|
||||
it("submits valid form data and visit welcome and settings page", function () {
|
||||
cy.visit("/setup");
|
||||
|
||||
// enter correct data
|
||||
cy.get('input[name="username"]').clear().type("trendschau");
|
||||
cy.get('input[name="email"]').clear().type("trendschau@gmail.com");
|
||||
cy.get('input[name="password"]').clear().type("password");
|
||||
|
||||
// submits valid form
|
||||
cy.get("form").submit();
|
||||
cy.url().should("include", "/welcome");
|
||||
cy.getCookie("typemill-session").should("exist");
|
||||
Cypress.Cookies.preserveOnce("typemill-session");
|
||||
|
||||
// clicks link on welcome page to settings page
|
||||
// cy.get('.button').should('contain', 'Configure your website')
|
||||
cy.get(".button").click();
|
||||
cy.url().should("include", "/tm/settings");
|
||||
});
|
||||
|
||||
it("creates default settings data", function () {
|
||||
cy.get('input[name="settings[title]"]')
|
||||
.should("have.value", "TYPEMILL")
|
||||
.and("have.attr", "required");
|
||||
cy.get('input[name="settings[author]"]');
|
||||
cy.get('select[name="settings[copyright]"]');
|
||||
cy.get('input[name="settings[year]"]').should("have.attr", "required");
|
||||
cy.get('select[name="settings[language]"]');
|
||||
// cy.get('select[name="settings[langattr]"]')
|
||||
cy.get('input[name="settings[sitemap]"]')
|
||||
.should("have.value", `${Cypress.config().baseUrl}/cache/sitemap.xml`)
|
||||
.and("have.attr", "readonly");
|
||||
cy.get('input[name="settings[logo]"]');
|
||||
cy.get('input[name="settings[deletelogo]"]');
|
||||
cy.get('input[name="settings[favicon]"]');
|
||||
cy.get('input[name="settings[deletefav]"]');
|
||||
cy.get('input[name="settings[headlineanchors]"]');
|
||||
cy.get('input[name="settings[editor]"]');
|
||||
|
||||
cy.get('select[name="settings[language]"]')
|
||||
.select("en")
|
||||
.should("have.value", "en");
|
||||
|
||||
cy.get("form").submit();
|
||||
cy.get("#flash-message").should("contain", "Settings are stored");
|
||||
|
||||
Cypress.Cookies.preserveOnce("typemill-session");
|
||||
});
|
||||
|
||||
it("creates default user data", function () {
|
||||
cy.visit("/tm/user/trendschau");
|
||||
cy.url().should("include", "/tm/user/trendschau");
|
||||
cy.get('input[name="user[username]"]').should("have.value", "trendschau");
|
||||
cy.get('input[name="user[firstname]"]')
|
||||
.clear()
|
||||
.type("Sebastian")
|
||||
.should("have.value", "Sebastian");
|
||||
cy.get('input[name="user[lastname]"]')
|
||||
.clear()
|
||||
.type("Schürmanns")
|
||||
.should("have.value", "Schürmanns");
|
||||
cy.get('input[name="user[email]"]').should(
|
||||
"have.value",
|
||||
"trendschau@gmail.com"
|
||||
);
|
||||
cy.get('select[name="user[userrole]"]').should(
|
||||
"have.value",
|
||||
"administrator"
|
||||
);
|
||||
cy.get('input[name="user[password]"]').should("have.value", "");
|
||||
cy.get('input[name="user[newpassword]"]').should("have.value", "");
|
||||
|
||||
cy.get("#userform").submit();
|
||||
cy.get("#flash-message").should("contain", "Saved all changes");
|
||||
});
|
||||
|
||||
it("logouts out", function () {
|
||||
// visits logout link
|
||||
cy.visit("/tm/logout");
|
||||
cy.url().should("include", "/tm/login");
|
||||
|
||||
// tries to open setup form again and gets redirected to login
|
||||
cy.visit("/setup");
|
||||
cy.url().should("include", "/login");
|
||||
});
|
||||
|
||||
it("redirects when tries to setup again", function () {
|
||||
// tries to open setup form again and gets redirected to login
|
||||
cy.visit("/setup");
|
||||
cy.url().should("include", "/login");
|
||||
});
|
||||
});
|
155
cypress/integration/01_setup/02-initial-frontend.spec.js
Normal file
@@ -0,0 +1,155 @@
|
||||
describe("Typemill Initial Frontend", function () {
|
||||
before(function () {
|
||||
cy.task("prepopulateSetup");
|
||||
});
|
||||
it("has startpage with navigation", function () {
|
||||
/* visit homepage */
|
||||
cy.visit("/");
|
||||
|
||||
/* has startpage with headline */
|
||||
cy.get("h1").contains("Typemill");
|
||||
|
||||
/* has start and setup button */
|
||||
cy.get("nav")
|
||||
.find("a")
|
||||
.should(($a) => {
|
||||
expect($a).to.have.length(11);
|
||||
expect($a[0].href).to.match(/welcome/);
|
||||
expect($a[1].href).to.match(/welcome\/setup-your-website/);
|
||||
expect($a[2].href).to.match(/welcome\/manage-access/);
|
||||
expect($a[3].href).to.match(/welcome\/write-content/);
|
||||
expect($a[4].href).to.match(/welcome\/get-help/);
|
||||
expect($a[5].href).to.match(/welcome\/markdown-test/);
|
||||
expect($a[6].href).to.match(/cyanine-theme/);
|
||||
expect($a[7].href).to.match(/cyanine-theme\/landingpage/);
|
||||
expect($a[8].href).to.match(/cyanine-theme\/colors-and-fonts/);
|
||||
expect($a[9].href).to.match(/cyanine-theme\/footer/);
|
||||
expect($a[10].href).to.match(/cyanine-theme\/content-elements/);
|
||||
});
|
||||
});
|
||||
|
||||
it("has error page", function () {
|
||||
cy.request({
|
||||
url: "/error",
|
||||
failOnStatusCode: false,
|
||||
}).then((resp) => {
|
||||
/* should return 404 not found */
|
||||
expect(resp.status).to.eq(404);
|
||||
});
|
||||
|
||||
cy.visit("/error", { failOnStatusCode: false });
|
||||
cy.url().should("include", "/error");
|
||||
|
||||
cy.get("h1").contains("Not Found");
|
||||
});
|
||||
|
||||
it("has no access to cache files", function () {
|
||||
cy.request({
|
||||
url: "/cache/structure.txt",
|
||||
failOnStatusCode: false,
|
||||
}).then((resp) => {
|
||||
// redirect status code is 302
|
||||
expect(resp.status).to.eq(403);
|
||||
});
|
||||
});
|
||||
|
||||
it("has no access to dashboard", function () {
|
||||
cy.visit("/tm/settings");
|
||||
cy.url().should("include", "/tm/login");
|
||||
});
|
||||
|
||||
it("has proper markdown test page", function () {
|
||||
cy.visit("/welcome/markdown-test");
|
||||
cy.url().should("include", "/welcome/markdown-test");
|
||||
|
||||
/* has navigation element */
|
||||
cy.get("nav").should("exist");
|
||||
|
||||
/* check if toc exists */
|
||||
cy.get(".TOC").within(($toc) => {
|
||||
/* check if a certain link in toc exists */
|
||||
cy.get("a").eq(2).should("have.attr", "href", "#h-headlines");
|
||||
});
|
||||
|
||||
/* check if corresponding anchor exists */
|
||||
cy.get("#h-headlines").should("exist");
|
||||
|
||||
/* soft linebreaks */
|
||||
cy.get("br").should("exist");
|
||||
|
||||
/* emphasis */
|
||||
cy.get("em").should("exist");
|
||||
|
||||
/* strong */
|
||||
cy.get("strong").should("exist");
|
||||
|
||||
/* ordered list */
|
||||
cy.get("ol").should("exist");
|
||||
|
||||
/* linebreak */
|
||||
cy.get("hr").should("exist");
|
||||
|
||||
/* links exists? hard to test, any idea? We need to wrap it in a div... */
|
||||
|
||||
/* images */
|
||||
cy.get("img").eq(0).should("have.attr", "alt", "alt");
|
||||
cy.get("img")
|
||||
.eq(0)
|
||||
.should("have.attr", "src")
|
||||
.should("include", "media/files/markdown.png");
|
||||
cy.get("figure")
|
||||
.eq(2)
|
||||
.should("have.id", "myid")
|
||||
.and("have.class", "otherclass");
|
||||
cy.get("img")
|
||||
.eq(2)
|
||||
.should("have.attr", "alt", "alt-text")
|
||||
.and("have.attr", "title", "my title")
|
||||
.and("have.attr", "width", "150px");
|
||||
|
||||
/* blockquote */
|
||||
cy.get("blockquote").should("exist");
|
||||
|
||||
/* has navigation element */
|
||||
cy.get(".notice1").should("exist");
|
||||
cy.get(".notice2").should("exist");
|
||||
cy.get(".notice3").should("exist");
|
||||
|
||||
/* footnote */
|
||||
cy.get("sup").eq(0).should("have.id", "fnref1:1");
|
||||
cy.get("sup")
|
||||
.eq(0)
|
||||
.within(($sup) => {
|
||||
cy.get("a")
|
||||
.eq(0)
|
||||
.should("have.attr", "href", "#fn%3A1")
|
||||
.and("have.class", "footnote-ref");
|
||||
});
|
||||
|
||||
/* abbreviation */
|
||||
cy.get("abbr").should("exist");
|
||||
|
||||
/* definition list */
|
||||
cy.get("dl").should("exist");
|
||||
|
||||
/* table */
|
||||
cy.get("table").should("exist");
|
||||
|
||||
/* code */
|
||||
cy.get("pre").should("exist");
|
||||
cy.get("code").should("exist");
|
||||
|
||||
/* math */
|
||||
cy.get(".math").should("exist");
|
||||
|
||||
/* footnote end */
|
||||
cy.get(".footnotes").within(($footnotes) => {
|
||||
cy.get("li").eq(0).should("have.id", "fn:1");
|
||||
cy.get("a")
|
||||
.eq(0)
|
||||
.should("have.class", "footnote-backref")
|
||||
.and("have.attr", "href", "#fnref1%3A1")
|
||||
.and("have.attr", "rev", "footnote");
|
||||
});
|
||||
});
|
||||
});
|
97
cypress/integration/03-system-settings.spec.js
Normal file
@@ -0,0 +1,97 @@
|
||||
describe("Typemill System Settings", function () {
|
||||
before(function () {
|
||||
cy.loginTypemill();
|
||||
|
||||
cy.visit("/tm/settings");
|
||||
cy.url().should("include", "/tm/settings");
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
Cypress.Cookies.preserveOnce("typemill-session");
|
||||
});
|
||||
|
||||
after(function () {
|
||||
cy.logoutTypemill();
|
||||
});
|
||||
|
||||
it("validates the form", function () {
|
||||
// fill out valid data
|
||||
cy.get('input[name="settings[title]"]')
|
||||
.clear()
|
||||
.type("Cypress<?")
|
||||
.should("have.value", "Cypress<?")
|
||||
.and("have.attr", "required");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="settings[author]"]')
|
||||
.clear()
|
||||
.type("trendschau")
|
||||
.should("have.value", "trendschau");
|
||||
|
||||
// fill out copyright data
|
||||
cy.get('select[name="settings[copyright]"]')
|
||||
.select("CC-BY")
|
||||
.should("have.value", "CC-BY");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="settings[year]"]')
|
||||
.clear()
|
||||
.type("2017")
|
||||
.should("have.value", "2017")
|
||||
.and("have.attr", "required");
|
||||
|
||||
// fill out copyright data
|
||||
cy.get('select[name="settings[language]"]')
|
||||
.select("German")
|
||||
.should("have.value", "de");
|
||||
|
||||
// submit form
|
||||
cy.get("form").submit();
|
||||
cy.get("#flash-message").should("contain", "Please correct the errors");
|
||||
});
|
||||
|
||||
it("changes default values", function () {
|
||||
// fill out valid data
|
||||
cy.get('input[name="settings[title]"]')
|
||||
.clear()
|
||||
.type("Cypress")
|
||||
.should("have.value", "Cypress")
|
||||
.and("have.attr", "required");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="settings[author]"]')
|
||||
.clear()
|
||||
.type("robot")
|
||||
.should("have.value", "robot");
|
||||
|
||||
cy.get('select[name="settings[copyright]"]')
|
||||
.select("CC-BY-ND")
|
||||
.should("have.value", "CC-BY-ND");
|
||||
|
||||
// fill out copyright data
|
||||
cy.get('select[name="settings[language]"]')
|
||||
.select("English")
|
||||
.should("have.value", "en");
|
||||
|
||||
cy.get("form").submit();
|
||||
cy.get("#flash-message").should("contain", "Settings are stored");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="settings[title]"]').should("have.value", "Cypress");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="settings[author]"]').should("have.value", "robot");
|
||||
|
||||
// fill out copyright data
|
||||
cy.get('select[name="settings[copyright]"]').should(
|
||||
"have.value",
|
||||
"CC-BY-ND"
|
||||
);
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="settings[year]"]').should("have.value", "2017");
|
||||
|
||||
// fill out copyright data
|
||||
cy.get('select[name="settings[language]"]').should("have.value", "en");
|
||||
});
|
||||
});
|
141
cypress/integration/04-theme-settings.spec.js
Normal file
@@ -0,0 +1,141 @@
|
||||
describe("Typemill Theme Settings", function () {
|
||||
before(function () {
|
||||
cy.clearCookies();
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
cy.loginTypemill();
|
||||
|
||||
cy.visit("/tm/themes");
|
||||
cy.url().should("include", "/tm/themes");
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
cy.logoutTypemill();
|
||||
});
|
||||
|
||||
it("changes default values", function () {
|
||||
// open the form
|
||||
cy.get("#cyanine-toggle").should("contain", "Settings").click();
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="cyanine[introButtonLink]"]')
|
||||
.clear()
|
||||
.type("https://typemill.net")
|
||||
.should("have.value", "https://typemill.net");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="cyanine[introButtonLabel]"]')
|
||||
.clear()
|
||||
.type("Typemill")
|
||||
.should("have.value", "Typemill");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="cyanine[chapnum]"]')
|
||||
.should("not.be.checked")
|
||||
.and("not.be.visible")
|
||||
.check({ force: true })
|
||||
.should("be.checked");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="cyanine[authorPosition][top]"]')
|
||||
.should("not.be.checked")
|
||||
.and("not.be.visible")
|
||||
.check({ force: true })
|
||||
.should("be.checked");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="cyanine[authorIntro]"]')
|
||||
.clear()
|
||||
.type("Writer")
|
||||
.should("have.value", "Writer");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="cyanine[datePosition][bottom]"]')
|
||||
.should("not.be.checked")
|
||||
.and("not.be.visible")
|
||||
.check({ force: true })
|
||||
.should("be.checked");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="cyanine[dateIntro]"]')
|
||||
.clear()
|
||||
.type("Final update")
|
||||
.should("have.value", "Final update");
|
||||
|
||||
cy.get('select[name="cyanine[dateFormat]"]')
|
||||
.should("have.value", "m/d/Y")
|
||||
.select("m/d/Y")
|
||||
.should("have.value", "m/d/Y");
|
||||
|
||||
cy.get('input[name="cyanine[gitPosition][top]"]')
|
||||
.should("not.be.checked")
|
||||
.and("not.be.visible")
|
||||
.check({ force: true })
|
||||
.should("be.checked");
|
||||
|
||||
cy.get('input[name="cyanine[gitLink]"]')
|
||||
.clear()
|
||||
.type("https://github.com/typemill/docs")
|
||||
.should("have.value", "https://github.com/typemill/docs");
|
||||
|
||||
cy.get("#theme-cyanine").submit();
|
||||
cy.get("#flash-message").should("contain", "Settings are stored");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="cyanine[introButtonLink]"]').should(
|
||||
"have.value",
|
||||
"https://typemill.net"
|
||||
);
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="cyanine[introButtonLabel]"]').should(
|
||||
"have.value",
|
||||
"Typemill"
|
||||
);
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="cyanine[chapnum]"]').should("be.checked");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="cyanine[authorPosition][top]"]').should("be.checked");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="cyanine[authorIntro]"]').should("have.value", "Writer");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="cyanine[datePosition][bottom]"]').should("be.checked");
|
||||
|
||||
// fill out valid data
|
||||
cy.get('input[name="cyanine[dateIntro]"]').should(
|
||||
"have.value",
|
||||
"Final update"
|
||||
);
|
||||
|
||||
cy.get('select[name="cyanine[dateFormat]"]').should("have.value", "m/d/Y");
|
||||
|
||||
cy.get('input[name="cyanine[gitPosition][top]"]').should("be.checked");
|
||||
|
||||
cy.get('input[name="cyanine[gitLink]"]').should(
|
||||
"have.value",
|
||||
"https://github.com/typemill/docs"
|
||||
);
|
||||
});
|
||||
|
||||
it("validates input", function () {
|
||||
// open the form
|
||||
cy.get("#cyanine-toggle").should("contain", "Settings").click();
|
||||
|
||||
// fill out invalid data
|
||||
cy.get('input[name="cyanine[introButtonLabel]"]')
|
||||
.should("have.value", "Typemill")
|
||||
.clear()
|
||||
.type("Kapitel<?")
|
||||
.should("have.value", "Kapitel<?");
|
||||
|
||||
// submit form
|
||||
cy.get("#theme-cyanine").submit();
|
||||
|
||||
cy.get("#flash-message").should("contain", "Please correct the errors");
|
||||
});
|
||||
});
|
259
cypress/integration/05_visual-editor/05-blox-editor.spec.js
Normal file
@@ -0,0 +1,259 @@
|
||||
describe("Blox Editor", function () {
|
||||
before(function () {
|
||||
cy.loginTypemill();
|
||||
cy.visit("/tm/content/visual");
|
||||
cy.url().should("include", "/tm/content/visual");
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
Cypress.Cookies.preserveOnce("typemill-session");
|
||||
});
|
||||
|
||||
it("creates new page", function () {
|
||||
// click on add element
|
||||
cy.get(".addNaviItem > a").eq(0).click();
|
||||
|
||||
/* Check dublicates cannot be made */
|
||||
|
||||
/* Check new page can be created */
|
||||
cy.get(".addNaviForm").within((naviform) => {
|
||||
/* add Testpage into input */
|
||||
cy.get("input").clear().type("Testpage").should("have.value", "Testpage");
|
||||
|
||||
cy.get(".b-left").click();
|
||||
});
|
||||
|
||||
/* get Navilist */
|
||||
cy.get(".navi-list")
|
||||
.should("contain", "Testpage")
|
||||
.eq(2)
|
||||
.find("a")
|
||||
.should((a) => {
|
||||
expect(a).to.have.length(6);
|
||||
expect(a[5].href).to.include("/welcome/testpage");
|
||||
});
|
||||
});
|
||||
|
||||
it("edits default content", function () {
|
||||
cy.visit("/tm/content/visual/welcome/testpage");
|
||||
cy.url().should("include", "/tm/content/visual/welcome/testpage");
|
||||
|
||||
cy.get("#blox").within((blox) => {
|
||||
/* Change Title */
|
||||
cy.get("#blox-0").click();
|
||||
cy.get("input").clear().type("This is my Testpage");
|
||||
|
||||
cy.get(".edit").click();
|
||||
cy.get("#blox-0").should("contain", "This is my Testpage");
|
||||
|
||||
/* Change Text */
|
||||
cy.get("#blox-1").click();
|
||||
cy.get("textarea")
|
||||
.clear()
|
||||
.type("This is the new paragraph for the first line with some text.");
|
||||
|
||||
cy.get(".edit").click();
|
||||
cy.get("#blox-1").should("contain", "new paragraph");
|
||||
});
|
||||
});
|
||||
|
||||
it("edits paragraph", function () {
|
||||
cy.get("#blox").within((blox) => {
|
||||
/* Get Format Bar */
|
||||
cy.get(".format-bar").within((formats) => {
|
||||
/* Edit Table */
|
||||
cy.get("button").eq(0).click();
|
||||
cy.get("textarea").type("This is a second paragraph.");
|
||||
|
||||
/* save table */
|
||||
cy.get(".edit").click();
|
||||
cy.get(".cancel").click();
|
||||
});
|
||||
|
||||
cy.get("#blox-2").should("contain", "second paragraph");
|
||||
});
|
||||
});
|
||||
|
||||
it("edits headline", function () {
|
||||
cy.get("#blox").within((blox) => {
|
||||
/* Get Format Bar */
|
||||
cy.get(".format-bar").within((formats) => {
|
||||
/* Edit Table */
|
||||
cy.get("button").eq(1).click();
|
||||
cy.get("input").type("Second Level Headline");
|
||||
|
||||
/* save block */
|
||||
cy.get(".edit").click();
|
||||
|
||||
/* close new standard textarea */
|
||||
cy.get(".cancel").click();
|
||||
});
|
||||
|
||||
cy.get("#blox-3").should("contain", "Second Level Headline");
|
||||
});
|
||||
});
|
||||
|
||||
it("edits unordered list", function () {
|
||||
cy.get("#blox").within((blox) => {
|
||||
/* Get Format Bar */
|
||||
cy.get(".format-bar").within((formats) => {
|
||||
/* Edit Table */
|
||||
cy.get("button").eq(2).click();
|
||||
cy.get("textarea").type("first list item{enter}second list item");
|
||||
|
||||
/* save block */
|
||||
cy.get(".edit").click();
|
||||
|
||||
/* close new standard textarea */
|
||||
cy.get(".cancel").click();
|
||||
});
|
||||
|
||||
cy.get("#blox-4").within((block) => {
|
||||
cy.get("li").should((lis) => {
|
||||
expect(lis).to.have.length(2);
|
||||
expect(lis.eq(0)).to.contain("first list item");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("edits ordered list", function () {
|
||||
cy.get("#blox").within((blox) => {
|
||||
/* Get Format Bar */
|
||||
cy.get(".format-bar").within((formats) => {
|
||||
/* Edit Table */
|
||||
cy.get("button").eq(3).click();
|
||||
cy.get("textarea").type("first ordered item{enter}second ordered item");
|
||||
|
||||
/* save block */
|
||||
cy.get(".edit").click();
|
||||
|
||||
/* close new standard textarea */
|
||||
cy.get(".cancel").click();
|
||||
});
|
||||
|
||||
cy.get("#blox-5").within((block) => {
|
||||
cy.get("li").should((lis) => {
|
||||
expect(lis).to.have.length(2);
|
||||
expect(lis.eq(0)).to.contain("first ordered item");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("edits table", function () {
|
||||
cy.get("#blox").within((blox) => {
|
||||
/* Get Format Bar */
|
||||
cy.get(".format-bar").within((formats) => {
|
||||
/* Edit Table */
|
||||
cy.get("button").eq(4).click();
|
||||
cy.get("table").within((table) => {
|
||||
/* edit table head */
|
||||
cy.get("tr")
|
||||
.eq(1)
|
||||
.within((row) => {
|
||||
cy.get("th").eq(1).click().clear().type("first Headline");
|
||||
cy.get("th").eq(2).click().clear().type("Second Headline");
|
||||
});
|
||||
|
||||
/* edit first content row */
|
||||
cy.get("tr")
|
||||
.eq(2)
|
||||
.within((row) => {
|
||||
cy.get("td").eq(1).click().clear().type("Some");
|
||||
cy.get("td").eq(2).click().clear().type("More");
|
||||
});
|
||||
|
||||
/* edit second content row */
|
||||
cy.get("tr")
|
||||
.eq(3)
|
||||
.within((row) => {
|
||||
cy.get("td").eq(1).click().clear().type("Beautiful");
|
||||
cy.get("td").eq(2).click().clear().type("Content");
|
||||
});
|
||||
|
||||
/* add new column on the right */
|
||||
cy.get("tr")
|
||||
.eq(0)
|
||||
.within((row) => {
|
||||
cy.get("td").eq(2).click();
|
||||
cy.get(".actionline").eq(0).click();
|
||||
});
|
||||
});
|
||||
|
||||
cy.get("table").within((table) => {
|
||||
/* edit second new column head */
|
||||
cy.get("tr")
|
||||
.eq(1)
|
||||
.within((row) => {
|
||||
cy.get("th").eq(3).click().clear().type("New Head");
|
||||
});
|
||||
|
||||
/* edit second new column head */
|
||||
cy.get("tr")
|
||||
.eq(2)
|
||||
.within((row) => {
|
||||
cy.get("td").eq(3).click().clear().type("With");
|
||||
});
|
||||
|
||||
/* edit second new column head */
|
||||
cy.get("tr")
|
||||
.eq(3)
|
||||
.within((row) => {
|
||||
cy.get("td").eq(3).click().clear().type("new Content");
|
||||
});
|
||||
});
|
||||
|
||||
/* save table */
|
||||
cy.get(".edit").click();
|
||||
});
|
||||
|
||||
cy.get("#blox-6").should("contain", "Beautiful").click();
|
||||
|
||||
cy.get(".editactive").within((activeblock) => {
|
||||
cy.get(".component").should("contain", "Beautiful");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("Publishes new page", function () {
|
||||
cy.visit("/tm/content/visual/welcome/testpage");
|
||||
cy.url().should("include", "/tm/content/visual/welcome/testpage");
|
||||
|
||||
cy.get("#publish").click().wait(500);
|
||||
|
||||
cy.visit("/welcome/testpage");
|
||||
cy.url().should("include", "/welcome/testpage");
|
||||
|
||||
cy.get(".cy-nav").should("contain", "Testpage");
|
||||
});
|
||||
|
||||
it("has sitemap xml", function () {
|
||||
cy.request({
|
||||
url: "/cache/sitemap.xml",
|
||||
}).then((resp) => {
|
||||
/* should return xml-format */
|
||||
expect(resp.headers).to.have.property("content-type", "application/xml");
|
||||
});
|
||||
});
|
||||
|
||||
it("Deletes new page", function () {
|
||||
cy.visit("/tm/content/visual/welcome/testpage");
|
||||
cy.url().should("include", "/tm/content/visual/welcome/testpage");
|
||||
|
||||
cy.get(".danger").click();
|
||||
|
||||
cy.get("#modalWindow").within((modal) => {
|
||||
cy.get("button").click();
|
||||
});
|
||||
|
||||
cy.visit("/tm/content/visual/welcome");
|
||||
cy.get(".navi-list")
|
||||
.not("contain", "Testpage")
|
||||
.eq(2)
|
||||
.find("a")
|
||||
.should((a) => {
|
||||
expect(a).to.have.length(5);
|
||||
});
|
||||
});
|
||||
});
|
67
cypress/integration/99-login.spec.js
Normal file
@@ -0,0 +1,67 @@
|
||||
describe("Typemill Login", function() {
|
||||
before(function() {
|
||||
cy.clearCookies();
|
||||
});
|
||||
it("redirects if visits dashboard without login", function() {
|
||||
cy.visit("/tm/content");
|
||||
cy.url().should("include", "/tm/login");
|
||||
});
|
||||
|
||||
it("submits a valid form and logout", function() {
|
||||
// visits login page and adds valid input
|
||||
cy.visit("/tm/login");
|
||||
cy.url().should("include", "/tm/login");
|
||||
|
||||
cy.get('input[name="username"]')
|
||||
.type("trendschau")
|
||||
.should("have.value", "trendschau")
|
||||
.and("have.attr", "required");
|
||||
|
||||
cy.get('input[name="password"]')
|
||||
.type("password")
|
||||
.should("have.value", "password")
|
||||
.and("have.attr", "required");
|
||||
|
||||
// can login
|
||||
cy.get("form").submit();
|
||||
cy.url().should("include", "/tm/content");
|
||||
cy.getCookie("typemill-session").should("exist");
|
||||
|
||||
Cypress.Cookies.preserveOnce("typemill-session");
|
||||
});
|
||||
|
||||
it("redirects if visits login form when logged in", function() {
|
||||
cy.visit("/tm/login");
|
||||
cy.url().should("include", "/tm/content");
|
||||
|
||||
Cypress.Cookies.preserveOnce("typemill-session");
|
||||
});
|
||||
|
||||
it("logs out", function() {
|
||||
cy.contains("Logout").click();
|
||||
cy.url().should("include", "/tm/login");
|
||||
});
|
||||
|
||||
it("captcha after 1 fail", function() {
|
||||
cy.visit("/tm/login");
|
||||
|
||||
// validation fails first
|
||||
cy.get('input[name="username"]').clear().type("wrong");
|
||||
cy.get('input[name="password"]').clear().type("pass");
|
||||
cy.get("form").submit();
|
||||
cy.get("#flash-message").should("contain", "wrong password or username");
|
||||
cy.get('input[name="username"]').should("have.value", "wrong");
|
||||
cy.get('input[name="password"]').should("have.value", "");
|
||||
cy.get('input[name="captcha"]').should("have.value", "");
|
||||
|
||||
// captcha fails first
|
||||
cy.get('input[name="username"]').clear().type("trendschau");
|
||||
cy.get('input[name="password"]').clear().type("password");
|
||||
cy.get('input[name="captcha"]').clear().type("wrong");
|
||||
cy.get("form").submit();
|
||||
cy.get("#flash-message").should("contain", "Captcha is wrong");
|
||||
cy.get('input[name="username"]').should("have.value", "trendschau");
|
||||
cy.get('input[name="password"]').should("have.value", "");
|
||||
cy.get('input[name="captcha"]').should("have.value", "");
|
||||
});
|
||||
});
|
48
cypress/plugins/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const fs = require("fs-extra");
|
||||
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
module.exports = (on, config) => {
|
||||
on("task", {
|
||||
resetSetup() {
|
||||
const users = "settings/users";
|
||||
const settings = "settings/settings.yaml";
|
||||
// of course files need to exist in order to perform a delete
|
||||
if (fs.existsSync(settings) && fs.existsSync(users)) {
|
||||
fs.rmSync(users, { recursive: true, force: true });
|
||||
fs.rmSync(settings);
|
||||
fs.copyFileSync(
|
||||
"cypress/fixtures/01_setup/default-settings.yaml",
|
||||
"settings/settings.yaml"
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
prepopulateSetup() {
|
||||
const settings = "settings";
|
||||
const settingsFixture =
|
||||
"cypress/fixtures/01_setup/prepulate_settings_seed/settings";
|
||||
// of course files need to exist in order to perform a delete
|
||||
fs.copySync(settingsFixture, settings);
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
};
|
41
cypress/support/commands.js
Normal file
@@ -0,0 +1,41 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
|
||||
Cypress.Commands.add("loginTypemill", () => {
|
||||
cy.visit("/tm/login");
|
||||
cy.url().should("include", "/tm/login");
|
||||
|
||||
cy.get('input[name="username"]').type("trendschau");
|
||||
cy.get('input[name="password"]').type("password");
|
||||
|
||||
cy.get("form").submit();
|
||||
cy.url().should("include", "/tm/content");
|
||||
cy.getCookie("typemill-session").should("exist");
|
||||
});
|
||||
|
||||
Cypress.Commands.add("logoutTypemill", () => {
|
||||
cy.visit("/tm/logout");
|
||||
});
|
20
cypress/support/index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
10
data/security/securitylog.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
127.0.0.1;2024-03-25 21:48:49;login: wrong password
|
||||
127.0.0.1;2024-04-20 12:51:39;login: wrong password
|
||||
127.0.0.1;2024-04-21 19:24:11;login: invalid data
|
||||
127.0.0.1;2024-04-22 14:38:20;loginlink: loginlink for user member is not activated.
|
||||
127.0.0.1;2024-04-23 11:16:24;loginlink: invalid data
|
||||
127.0.0.1;2024-09-01 13:59:35;login: invalid data
|
||||
127.0.0.1;2025-02-27 19:22:45;login: wrong password
|
||||
127.0.0.1;2025-02-27 19:23:07;login: wrong password
|
||||
127.0.0.1;2025-02-27 19:25:24;login: invalid data
|
||||
127.0.0.1;2025-02-27 20:14:02;login: wrong password
|
5
docker-utils/init-server
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
find /var/www/html/content -type d -empty -exec cp -R /var/www/html/content.default/* /var/www/html/content \;
|
||||
find /var/www/html/themes -type d -empty -exec cp -R /var/www/html/themes.default/* /var/www/html/themes \;
|
||||
chown -R www-data:www-data /var/www/html/
|
||||
apache2-foreground
|
17
docker-utils/install-composer
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')"
|
||||
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
|
||||
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
|
||||
|
||||
if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]
|
||||
then
|
||||
>&2 echo 'ERROR: Invalid installer checksum'
|
||||
rm composer-setup.php
|
||||
exit 1
|
||||
fi
|
||||
|
||||
php composer-setup.php --quiet
|
||||
RESULT=$?
|
||||
rm composer-setup.php
|
||||
exit $RESULT
|
5
index.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/system/vendor/autoload.php';
|
||||
|
||||
require __DIR__ . '/system/typemill/system.php';
|
7
licence.md
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright (c) 2017 Sebastian Sch<63>rmanns
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
4
media/files/filerestrictions.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
media/files/typemill-v2-navigation.gif: member
|
||||
media/files/markdown.png: member
|
||||
media/files/wordpress-test.txt: contributor
|
||||
media/files/publii-test.txt: editor
|
BIN
media/files/markdown.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
media/live/youtube-6i2-uv88gke.jpeg
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
media/live/youtube-7yvlwxjl9dc-1.jpg
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
media/live/youtube-7yvlwxjl9dc-2.jpg
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
media/live/youtube-7yvlwxjl9dc.jpeg
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
media/live/youtube-7yvlwxjl9dc.jpg
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
media/live/youtube-uw-m-4g1kaa.jpeg
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
media/original/youtube-6i2-uv88gke.jpeg
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
media/original/youtube-7yvlwxjl9dc-1.jpg
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
media/original/youtube-7yvlwxjl9dc-2.jpg
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
media/original/youtube-7yvlwxjl9dc.jpeg
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
media/original/youtube-7yvlwxjl9dc.jpg
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
media/original/youtube-uw-m-4g1kaa.jpeg
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
media/thumbs/youtube-6i2-uv88gke.jpeg
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
media/thumbs/youtube-7yvlwxjl9dc-1.jpg
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
media/thumbs/youtube-7yvlwxjl9dc-2.jpg
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
media/thumbs/youtube-7yvlwxjl9dc.jpeg
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
media/thumbs/youtube-7yvlwxjl9dc.jpg
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
media/thumbs/youtube-uw-m-4g1kaa.jpeg
Normal file
After Width: | Height: | Size: 3.6 KiB |
1542
package-lock.json
generated
Normal file
24
package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "typemill",
|
||||
"version": "1.0.0",
|
||||
"description": "TYPEMILL is a lightweight flat file cms for micro-publishers. You can use it for documentations, manuals, special interest websites, and any other information-driven web-project. You can also enhance Typemill with plugins and generate professional e-books in pdf-format with it. The website http://typemill.net runs with Typemill.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"e2e": "cypress open --project .",
|
||||
"install-and-e2e": "npm i && composer update && cypress open --project ."
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/typemill/typemill.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/typemill/typemill/issues"
|
||||
},
|
||||
"homepage": "https://github.com/typemill/typemill#readme",
|
||||
"devDependencies": {
|
||||
"fs-extra": "^10.0.1",
|
||||
"tailwindcss": "^3.1.6"
|
||||
}
|
||||
}
|
17
plugins/demo/DemoController.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Plugins\demo;
|
||||
|
||||
class demoController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return die('I am the admin controller');
|
||||
}
|
||||
|
||||
public function wrong()
|
||||
{
|
||||
return die('I am the admin controller');
|
||||
}
|
||||
|
||||
}
|
12
plugins/demo/Text.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Plugins\Demo;
|
||||
|
||||
class Text
|
||||
{
|
||||
public function getText()
|
||||
{
|
||||
return 'I am the twig function';
|
||||
}
|
||||
|
||||
}
|
3
plugins/demo/css/demo.css
Normal file
@@ -0,0 +1,3 @@
|
||||
body{
|
||||
background: #000;
|
||||
}
|
400
plugins/demo/demo.php
Normal file
@@ -0,0 +1,400 @@
|
||||
<?php
|
||||
|
||||
namespace Plugins\demo;
|
||||
|
||||
use Typemill\Plugin;
|
||||
use Typemill\Models\Validation;
|
||||
use Plugins\demo\demoController;
|
||||
use Plugins\Demo\Text;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
|
||||
class demo extends Plugin
|
||||
{
|
||||
# you can add a licence check here
|
||||
public static function setPremiumLicense()
|
||||
{
|
||||
# return false;
|
||||
return 'MAKER';
|
||||
# return 'BUSINESS';
|
||||
}
|
||||
|
||||
# you can subscribe to the following events
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
|
||||
# all pages fired from system file
|
||||
'onSettingsLoaded' => ['onSettingsLoaded', 0],
|
||||
'onPluginsLoaded' => ['onPluginsLoaded', 0],
|
||||
'onSessionSegmentsLoaded' => ['onSessionSegmentsLoaded', 0],
|
||||
'onRolesPermissionsLoaded' => ['onRolesPermissionsLoaded', 0],
|
||||
'onResourcesLoaded' => ['onResourcesLoaded', 0],
|
||||
|
||||
# admin area fired from navigation model
|
||||
'onSystemnaviLoaded' => ['onSystemnaviLoaded', 0],
|
||||
|
||||
# all pages fired from controller
|
||||
'onTwigLoaded' => ['onTwigLoaded', 0],
|
||||
|
||||
# only content pages fired from parsedown extension and controllerApiShortcode????
|
||||
'onShortcodeFound' => ['onShortcodeFound', 0],
|
||||
|
||||
# frontend pages fired from ControllerWebFrontend
|
||||
'onPagetreeLoaded' => ['onPagetreeLoaded', 0],
|
||||
'onBreadcrumbLoaded' => ['onBreadcrumbLoaded', 0],
|
||||
'onItemLoaded' => ['onItemLoaded', 0],
|
||||
'onMarkdownLoaded' => ['onMarkdownLoaded', 0],
|
||||
'onMetaLoaded' => ['onMetaLoaded', 0],
|
||||
'onRestrictionsLoaded' => ['onRestrictionsLoaded', 0],
|
||||
'onContentArrayLoaded' => ['onContentArrayLoaded', 0],
|
||||
'onHtmlLoaded' => ['onHtmlLoaded', 0],
|
||||
'onPageReady' => ['onPageReady', 0]
|
||||
];
|
||||
}
|
||||
|
||||
# you can add new routes for public, api, or admin-area
|
||||
public static function addNewRoutes()
|
||||
{
|
||||
return [
|
||||
|
||||
# add a frontend route with a form
|
||||
[
|
||||
'httpMethod' => 'get',
|
||||
'route' => '/demo',
|
||||
'name' => 'demo.frontend',
|
||||
'class' => 'Plugins\demo\DemoController:index',
|
||||
# optionallly restrict page:
|
||||
# 'resource' => 'account',
|
||||
# 'privilege' => 'view'
|
||||
],
|
||||
|
||||
# add a frontend route to receive form data
|
||||
[
|
||||
'httpMethod' => 'post',
|
||||
'route' => '/demo',
|
||||
'name' => 'demo.send',
|
||||
'class' => 'Plugins\demo\DemoController:formdata',
|
||||
# optionallly restrict page:
|
||||
# 'resource' => 'account',
|
||||
# 'privilege' => 'view'
|
||||
],
|
||||
|
||||
# add an admin route
|
||||
[
|
||||
'httpMethod' => 'get',
|
||||
'route' => '/tm/demo',
|
||||
'name' => 'demo.admin',
|
||||
'class' => 'Typemill\Controllers\ControllerWebSystem:blankSystemPage',
|
||||
'resource' => 'system',
|
||||
'privilege' => 'view'
|
||||
],
|
||||
|
||||
# add an api route
|
||||
[
|
||||
'httpMethod' => 'get',
|
||||
'route' => '/api/v1/demo',
|
||||
'name' => 'demo.api',
|
||||
'class' => 'Plugins\demo\demo:getDemoData',
|
||||
'resource' => 'system',
|
||||
'privilege' => 'view'
|
||||
],
|
||||
|
||||
# add an api route
|
||||
[
|
||||
'httpMethod' => 'post',
|
||||
'route' => '/api/v1/demo',
|
||||
'name' => 'demo.api',
|
||||
'class' => 'Plugins\demo\demo:storeDemoData',
|
||||
'resource' => 'system',
|
||||
'privilege' => 'view'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
# you can add new middleware function, for example
|
||||
public static function addNewMiddleware()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
# settings are read only, you do not need to return anything
|
||||
public function onSettingsLoaded($settings)
|
||||
{
|
||||
$data = $settings->getData();
|
||||
|
||||
# you also have access to settings from container through
|
||||
# $this->getSettings()
|
||||
|
||||
# or access to the plugin settings (optionally with pluginname)
|
||||
# $this->getPluginSettings()
|
||||
|
||||
}
|
||||
|
||||
|
||||
# use this if you have any dependencies with other plugins and want to check if they are active
|
||||
public function onPluginsLoaded($plugins)
|
||||
{
|
||||
$pluginnames = $plugins->getData();
|
||||
|
||||
$plugins->setData($pluginnames);
|
||||
}
|
||||
|
||||
|
||||
# you can add a new session segment in frontend, for example if you add frontend fomrs
|
||||
public function onSessionSegmentsLoaded($segments)
|
||||
{
|
||||
$arrayOfSegments = $segments->getData();
|
||||
|
||||
$segments->setData($arrayOfSegments);
|
||||
}
|
||||
|
||||
|
||||
# add new roles and permission
|
||||
public function onRolesPermissionsLoaded($rolespermissions)
|
||||
{
|
||||
$data = $rolespermissions->getData();
|
||||
|
||||
$rolespermissions->setData($data);
|
||||
}
|
||||
|
||||
|
||||
# add new resources for roles and permissions
|
||||
public function onResourcesLoaded($resources)
|
||||
{
|
||||
$data = $resources->getData();
|
||||
|
||||
$resources->setData($data);
|
||||
}
|
||||
|
||||
|
||||
# add new navi-items into the system area
|
||||
public function onSystemnaviLoaded($navidata)
|
||||
{
|
||||
$this->addSvgSymbol('<symbol id="icon-download" viewBox="0 0 24 24"><path d="M20 15v4c0 0.276-0.111 0.525-0.293 0.707s-0.431 0.293-0.707 0.293h-14c-0.276 0-0.525-0.111-0.707-0.293s-0.293-0.431-0.293-0.707v-4c0-0.552-0.448-1-1-1s-1 0.448-1 1v4c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879h14c0.828 0 1.58-0.337 2.121-0.879s0.879-1.293 0.879-2.121v-4c0-0.552-0.448-1-1-1s-1 0.448-1 1zM13 12.586v-9.586c0-0.552-0.448-1-1-1s-1 0.448-1 1v9.586l-3.293-3.293c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414l5 5c0.092 0.092 0.202 0.166 0.324 0.217s0.253 0.076 0.383 0.076c0.256 0 0.512-0.098 0.707-0.293l5-5c0.391-0.391 0.391-1.024 0-1.414s-1.024-0.391-1.414 0z"></path></symbol>');
|
||||
|
||||
$navi = $navidata->getData();
|
||||
|
||||
$navi['Demo'] = ['title' => 'Demo','routename' => 'demo.admin', 'icon' => 'icon-download', 'aclresource' => 'system', 'aclprivilege' => 'view'];
|
||||
|
||||
# if the use visits the system page of the plugin
|
||||
if(trim($this->route,"/") == 'tm/demo')
|
||||
{
|
||||
# set the navigation item active
|
||||
$navi['Demo']['active'] = true;
|
||||
|
||||
# add the system application
|
||||
$this->addJS('/demo/js/systemdemo.js');
|
||||
}
|
||||
|
||||
$navidata->setData($navi);
|
||||
}
|
||||
|
||||
|
||||
# the twig function is for everything you want to add and render in frontend
|
||||
public function onTwigLoaded()
|
||||
{
|
||||
if($this->editorroute)
|
||||
{
|
||||
$this->addJS('/demo/js/editordemo.js');
|
||||
}
|
||||
|
||||
# get the twig-object
|
||||
# $twig = $this->getTwig();
|
||||
|
||||
# get the twig-template-loader
|
||||
# $loader = $twig->getLoader();
|
||||
# $loader->addPath(__DIR__ . '/templates');
|
||||
|
||||
# return $twig->render($this->container['response'], '/demo.twig', ['data' => 'data']);
|
||||
|
||||
# you can add assets to all twig-views
|
||||
# $this->addInlineJS("console.info('my inline script;')");
|
||||
# $this->addJS('/demo/js/script.js');
|
||||
|
||||
# you can add styles to all twig-views
|
||||
# $this->addInlineCSS('h1{color:red;}');
|
||||
# $this->addCSS('/demo/css/demo.css');
|
||||
|
||||
# you can add your own global variables to all twig-views.
|
||||
# $this->addTwigGlobal('text', new Text());
|
||||
|
||||
# you can add your own filter function to twig.
|
||||
# $this->addTwigFilter('rot13', function ($string) {
|
||||
# return str_rot13($string);
|
||||
# });
|
||||
|
||||
# you can add your own function to a twig-views *
|
||||
# $this->addTwigFunction('myName', function(){
|
||||
# return 'My name is ';
|
||||
# });
|
||||
}
|
||||
|
||||
|
||||
# add a shortcode function to enhance the content area with new features
|
||||
public function onShortcodeFound($shortcode)
|
||||
{
|
||||
# read the data of the shortcode
|
||||
$shortcodeArray = $shortcode->getData();
|
||||
|
||||
# register your shortcode
|
||||
if(is_array($shortcodeArray) && $shortcodeArray['name'] == 'registershortcode')
|
||||
{
|
||||
$shortcodeArray['data']['contactform'] = [];
|
||||
|
||||
$shortcode->setData($shortcodeArray);
|
||||
}
|
||||
|
||||
# check if it is the shortcode name that we where looking for
|
||||
if(is_array($shortcodeArray) && $shortcodeArray['name'] == 'contactform')
|
||||
{
|
||||
# we found our shortcode, so stop firing the event to other plugins
|
||||
$shortcode->stopPropagation();
|
||||
|
||||
# get the public forms for the plugin
|
||||
$contactform = $this->generateForm('demo.send');
|
||||
# add to a page
|
||||
# add as shortcode
|
||||
# create new page
|
||||
|
||||
# and return a html-snippet that replaces the shortcode on the page.
|
||||
$shortcode->setData($contactform);
|
||||
}
|
||||
}
|
||||
|
||||
# returns an array of item-objects, that represents the neavigation
|
||||
public function onPagetreeLoaded($pagetree)
|
||||
{
|
||||
$data = $pagetree->getData();
|
||||
|
||||
$pagetree->setData($data);
|
||||
}
|
||||
|
||||
# returns array of item objects that represent the breadcrumb
|
||||
public function onBreadcrumbLoaded($breadcrumb)
|
||||
{
|
||||
$data = $breadcrumb->getData();
|
||||
|
||||
$breadcrumb->setData($data);
|
||||
}
|
||||
|
||||
# returns the item of the current page
|
||||
public function onItemLoaded($item)
|
||||
{
|
||||
$data = $item->getData();
|
||||
|
||||
$item->setData($data);
|
||||
}
|
||||
|
||||
# returns the markdown of the current page
|
||||
public function onMarkdownLoaded($markdown)
|
||||
{
|
||||
$data = $markdown->getData();
|
||||
|
||||
$markdown->setData($data);
|
||||
}
|
||||
|
||||
# returns the metadata (array) of the current page
|
||||
public function onMetaLoaded($meta)
|
||||
{
|
||||
$data = $meta->getData();
|
||||
|
||||
$meta->setData($data);
|
||||
}
|
||||
|
||||
# returns array with restriced role, defaultcontent and full markdown array
|
||||
public function onRestrictionsLoaded($restrictions)
|
||||
{
|
||||
$data = $restrictions->getData();
|
||||
|
||||
$restrictions->setData($data);
|
||||
}
|
||||
|
||||
# returns the full content with ormats as an array
|
||||
public function onContentArrayLoaded($contentArray)
|
||||
{
|
||||
$data = $contentArray->getData();
|
||||
|
||||
$contentArray->setData($data);
|
||||
}
|
||||
|
||||
# returns the full content as html
|
||||
public function onHtmlLoaded($html)
|
||||
{
|
||||
$data = $html->getData();
|
||||
|
||||
$html->setData($data);
|
||||
}
|
||||
|
||||
|
||||
# add a new page into the system area
|
||||
public function onPageReady($data)
|
||||
{
|
||||
/*
|
||||
# admin stuff
|
||||
if($this->adminroute && $this->route == 'tm/demo')
|
||||
{
|
||||
$this->addJS('/ebookproducts/js/vue-ebookproducts.js');
|
||||
|
||||
$pagedata = $data->getData();
|
||||
|
||||
$twig = $this->getTwig();
|
||||
$loader = $twig->getLoader();
|
||||
$loader->addPath(__DIR__ . '/templates');
|
||||
|
||||
# fetch the template and render it with twig
|
||||
$content = $twig->fetch('/ebookproducts.twig', []);
|
||||
|
||||
$pagedata['content'] = $content;
|
||||
|
||||
$data->setData($pagedata);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
#########################################
|
||||
# Add methods for new routes #
|
||||
#########################################
|
||||
|
||||
# gets the centrally stored ebook-data for ebook-plugin in settings-area
|
||||
public function getDemoData(Request $request, Response $response, $args)
|
||||
{
|
||||
# gets file from /data/demo automatically, use getPluginData or getPluginYamlData
|
||||
$formdata = $this->getPluginYamlData('demotest.yaml');
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'formdata' => $formdata
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
|
||||
# gets the centrally stored ebook-data for ebook-plugin in settings-area
|
||||
public function storeDemoData(Request $request, Response $response, $args)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
|
||||
# gets file from /data/demo automatically, use getPluginData or getPluginYamlData
|
||||
$result = $this->storePluginYamlData('demotest.yaml', $params['formdata']);
|
||||
|
||||
if($result !== true)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'errors' => $result,
|
||||
'message' => 'please correct the errors in the form.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'data' => $result,
|
||||
'message' => 'data stored successfully.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
}
|
1
plugins/demo/demo.twig
Normal file
@@ -0,0 +1 @@
|
||||
<h1>Hello</h1>
|
186
plugins/demo/demo.yaml
Normal file
@@ -0,0 +1,186 @@
|
||||
name: Demo Plugin
|
||||
version: 1.0.0
|
||||
description: Demonstrates the power of Typemill Plugins
|
||||
author: Sebastian Schürmanns
|
||||
homepage: http://typemill.net
|
||||
license: MAKER
|
||||
dependencies:
|
||||
- register
|
||||
- mail
|
||||
|
||||
settings:
|
||||
theme: 'edgeless'
|
||||
message: 'You can enter a message here.'
|
||||
website: 'http://typemill.net'
|
||||
background: '#ffffff'
|
||||
|
||||
forms:
|
||||
fields:
|
||||
|
||||
text:
|
||||
type: text
|
||||
label: Text Input
|
||||
|
||||
message:
|
||||
type: textarea
|
||||
label: Message
|
||||
placeholder: 'Message for cookie-popup'
|
||||
required: true
|
||||
|
||||
code:
|
||||
type: codearea
|
||||
label: Code your stuff
|
||||
spellcheck: false
|
||||
|
||||
theme:
|
||||
type: select
|
||||
label: Select
|
||||
placeholder: 'Add name of theme'
|
||||
required: true
|
||||
options:
|
||||
edgeless: Edgeless
|
||||
block: Block
|
||||
classic: Classic
|
||||
mail: PHP Mail
|
||||
|
||||
userrole:
|
||||
type: select
|
||||
label: Which role should a new user get?
|
||||
dataset: userroles
|
||||
description: The standard userrole is "member". A member can only edit his account. Be careful if you select other roles.
|
||||
|
||||
number:
|
||||
type: number
|
||||
label: Number
|
||||
|
||||
date:
|
||||
type: date
|
||||
label: Date
|
||||
|
||||
email:
|
||||
type: email
|
||||
label: Email
|
||||
description: Please help me here.
|
||||
|
||||
tel:
|
||||
type: tel
|
||||
label: Phone number
|
||||
description: Please help me here.
|
||||
|
||||
pass:
|
||||
type: password
|
||||
label: Password
|
||||
description: Please help me here.
|
||||
|
||||
website:
|
||||
type: url
|
||||
label: Add valid url
|
||||
placeholder: 'Add valid URL'
|
||||
help: Please help me here or make me cry. I don't think that this is a good Idea, but we will see. Otherwise we will get this done.
|
||||
required: true
|
||||
|
||||
background:
|
||||
type: color
|
||||
label: Color
|
||||
placeholder: 'Add hex color value like #ffffff'
|
||||
required: true
|
||||
|
||||
singlecheckbox:
|
||||
type: checkbox
|
||||
label: Simple checkbox
|
||||
checkboxlabel: Please check me
|
||||
|
||||
multiplecheckbox:
|
||||
type: checkboxlist
|
||||
label: Multiple Checkboxes
|
||||
options:
|
||||
first: First
|
||||
second: Second
|
||||
third: Third
|
||||
fourth: Fourth
|
||||
|
||||
radio:
|
||||
type: radio
|
||||
label: Radio
|
||||
options:
|
||||
red: Red
|
||||
green: Green
|
||||
blue: Blue
|
||||
yellow: Yellow
|
||||
|
||||
mediaimage:
|
||||
type: image
|
||||
label: Upload image
|
||||
description: Please only upload some stuff you like.
|
||||
|
||||
metatabs:
|
||||
demo:
|
||||
fields:
|
||||
demoimage:
|
||||
type: image
|
||||
label: Image Field Demo
|
||||
description: Maximum size for an image is 5 MB. Hero images are not supported by all themes.
|
||||
demoimagealt:
|
||||
type: text
|
||||
label: Alt-Text for Hero-Image
|
||||
democheckbox:
|
||||
type: checkboxlist
|
||||
label: Multiple Checkboxes
|
||||
options:
|
||||
first: First
|
||||
second: Second
|
||||
third: Third
|
||||
fourth: Fourth
|
||||
democustomfield:
|
||||
type: customfields
|
||||
label: try it out
|
||||
data: array
|
||||
|
||||
system:
|
||||
fields:
|
||||
title:
|
||||
type: text
|
||||
label: Title of your eBook
|
||||
subtitle:
|
||||
type: text
|
||||
label: Subtitle of your eBook
|
||||
author:
|
||||
type: text
|
||||
label: Author
|
||||
edition:
|
||||
type: text
|
||||
label: Edition
|
||||
flytitle:
|
||||
type: checkbox
|
||||
label: Fly title
|
||||
checkboxlabel: Add a fly title after the cover.
|
||||
|
||||
public:
|
||||
fields:
|
||||
|
||||
name:
|
||||
type: text
|
||||
label: name_label
|
||||
required: true
|
||||
class: 'tm-input'
|
||||
|
||||
email:
|
||||
type: email
|
||||
label: email_label
|
||||
required: true
|
||||
class: 'tm-input'
|
||||
|
||||
subject:
|
||||
type: text
|
||||
label: subject_label
|
||||
required: true
|
||||
class: 'tm-input'
|
||||
|
||||
message:
|
||||
type: textarea
|
||||
label: message_label
|
||||
required: true
|
||||
class: 'tm-textarea'
|
||||
|
||||
legalnotice:
|
||||
type: paragraph
|
48
plugins/demo/js/editordemo.js
Normal file
@@ -0,0 +1,48 @@
|
||||
app.component('tab-demo', {
|
||||
props: ['item', 'formData', 'formDefinitions', 'saved', 'errors', 'message', 'messageClass'],
|
||||
template: `<section class="dark:bg-stone-700 dark:text-stone-200">
|
||||
<form>
|
||||
<div v-for="(fieldDefinition, fieldname) in formDefinitions.fields">
|
||||
<fieldset class="flex flex-wrap justify-between border-2 border-stone-200 p-4 my-8" v-if="fieldDefinition.type == 'fieldset'">
|
||||
<legend class="text-lg font-medium">{{ fieldDefinition.legend }}</legend>
|
||||
<component v-for="(subfieldDefinition, subfieldname) in fieldDefinition.fields"
|
||||
:key="subfieldname"
|
||||
:is="selectComponent(subfieldDefinition.type)"
|
||||
:errors="errors"
|
||||
:name="subfieldname"
|
||||
:userroles="userroles"
|
||||
:value="formData[subfieldname]"
|
||||
v-bind="subfieldDefinition">
|
||||
</component>
|
||||
</fieldset>
|
||||
<component v-else
|
||||
:key="fieldname"
|
||||
:is="selectComponent(fieldDefinition.type)"
|
||||
:errors="errors"
|
||||
:name="fieldname"
|
||||
:userroles="userroles"
|
||||
:value="formData[fieldname]"
|
||||
v-bind="fieldDefinition">
|
||||
</component>
|
||||
</div>
|
||||
<div class="my-5">
|
||||
<div class="block w-full h-8 my-1">
|
||||
<transition name="fade">
|
||||
<div v-if="message" :class="messageClass" class="text-white px-3 py-1 transition duration-100">{{ $filters.translate(message) }}</div>
|
||||
</transition>
|
||||
</div>
|
||||
<input type="submit" @click.prevent="saveInput()" :value="$filters.translate('save')" class="w-full p-3 my-1 bg-stone-700 dark:bg-stone-600 hover:bg-stone-900 hover:dark:bg-stone-900 text-white cursor-pointer transition duration-100">
|
||||
</div>
|
||||
</form>
|
||||
</section>`,
|
||||
methods: {
|
||||
selectComponent: function(type)
|
||||
{
|
||||
return 'component-' + type;
|
||||
},
|
||||
saveInput: function()
|
||||
{
|
||||
this.$emit('saveform');
|
||||
},
|
||||
}
|
||||
})
|
112
plugins/demo/js/systemdemo.js
Normal file
@@ -0,0 +1,112 @@
|
||||
const app = Vue.createApp({
|
||||
template: `<Transition name="initial" appear>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold mb-4">{{ plugin.name }}</h1>
|
||||
<form class="w-full my-8">
|
||||
<div v-for="(fieldDefinition, fieldname) in plugin.system.fields">
|
||||
<fieldset class="flex flex-wrap justify-between border-2 border-stone-200 p-4 my-8" v-if="fieldDefinition.type == 'fieldset'">
|
||||
<legend class="text-lg font-medium">{{ fieldDefinition.legend }}</legend>
|
||||
<component v-for="(subfieldDefinition, subfieldname) in fieldDefinition.fields"
|
||||
:key="subfieldname"
|
||||
:is="selectComponent(subfieldDefinition.type)"
|
||||
:errors="errors"
|
||||
:name="subfieldname"
|
||||
:userroles="userroles"
|
||||
:value="formData[subfieldname]"
|
||||
v-bind="subfieldDefinition">
|
||||
</component>
|
||||
</fieldset>
|
||||
<component v-else
|
||||
:key="fieldname"
|
||||
:is="selectComponent(fieldDefinition.type)"
|
||||
:errors="errors"
|
||||
:name="fieldname"
|
||||
:userroles="userroles"
|
||||
:value="formData[fieldname]"
|
||||
v-bind="fieldDefinition">
|
||||
</component>
|
||||
</div>
|
||||
<div class="my-5">
|
||||
<div :class="messageClass" class="block w-full h-8 px-3 py-1 my-1 text-white transition duration-100">{{ $filters.translate(message) }}</div>
|
||||
<input type="submit" @click.prevent="save()" :value="$filters.translate('save')" class="w-full p-3 my-1 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Transition>`,
|
||||
data() {
|
||||
return {
|
||||
plugin: data.plugin,
|
||||
formData: {'title': 'bla'},
|
||||
message: false,
|
||||
messageClass: '',
|
||||
errors: {},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
eventBus.$on('forminput', formdata => {
|
||||
this.formData[formdata.name] = formdata.value;
|
||||
});
|
||||
|
||||
var self = this;
|
||||
|
||||
tmaxios.get('/api/v1/demo',{
|
||||
params: {
|
||||
'url': data.urlinfo.route,
|
||||
}
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
if(response.data.formdata)
|
||||
{
|
||||
self.formData = response.data.formdata;
|
||||
}
|
||||
})
|
||||
.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;
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
methods: {
|
||||
selectComponent: function(type)
|
||||
{
|
||||
return 'component-'+type;
|
||||
},
|
||||
save: function()
|
||||
{
|
||||
this.reset();
|
||||
|
||||
var self = this;
|
||||
|
||||
tmaxios.post('/api/v1/demo',{
|
||||
'formdata': 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;
|
||||
}
|
||||
});
|
||||
},
|
||||
reset: function()
|
||||
{
|
||||
this.errors = {};
|
||||
this.message = '';
|
||||
this.messageClass = '';
|
||||
}
|
||||
},
|
||||
})
|
1
plugins/demo/templates/demo.twig
Normal file
@@ -0,0 +1 @@
|
||||
I am twig
|