1
0
mirror of https://github.com/typemill/typemill.git synced 2025-07-25 00:02:28 +02:00

Cleaned up kixote branch, removed secrets

This commit is contained in:
trendschau
2025-03-16 23:03:30 +01:00
commit 59380f20a6
327 changed files with 67658 additions and 0 deletions

21
.dockerignore Normal file
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

8
content/.yaml Normal file
View File

@@ -0,0 +1,8 @@
meta:
author: Sebastian
created: '2024-09-10'
time: 12-43-18
navtitle: null
modified: '2024-08-01'
title: ''
description: ''

View 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.

View 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: ''

View 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.

View 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'

View 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).

View 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,'

View 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.

View 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:'

View 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.

View 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: ''

View File

@@ -0,0 +1 @@
["# Unpublished Page","This is an unpublished page. Unpublished pages are marked red in the navigation and in the publish bar."]

View File

@@ -0,0 +1,6 @@
meta:
owner: typemill
author: ''
created: '2024-03-19'
time: 18-40-21
navtitle: unpublished

View 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.

View 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."]

View 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'

View 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.

View 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'

View 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).

View 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

View 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).

View 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

View 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.

View 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,'

View 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".

View 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".'

View 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".

View 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".'

View 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".

View 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'

View 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.

View 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

View 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.

View 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

View 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).

View 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

View 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).

View 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

View 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 youre 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).

View 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
View 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.

View 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
View 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
View File

@@ -0,0 +1,3 @@
{
"baseUrl": "http://localhost/EDIT_YOUR_LOCAL_BASE_URL_HERE"
}

View File

@@ -0,0 +1,3 @@
setup: true
language: en
welcome: true

View File

@@ -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: ""

View File

@@ -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

View 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");
});
});

View 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");
});
});
});

View 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");
});
});

View 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");
});
});

View 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);
});
});
});

View 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
View 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;
},
});
};

View 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
View 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')

View 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
View 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

View 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
View File

@@ -0,0 +1,5 @@
<?php
require __DIR__ . '/system/vendor/autoload.php';
require __DIR__ . '/system/typemill/system.php';

7
licence.md Normal file
View 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.

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

1542
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
package.json Normal file
View 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"
}
}

View 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
View File

@@ -0,0 +1,12 @@
<?php
namespace Plugins\Demo;
class Text
{
public function getText()
{
return 'I am the twig function';
}
}

View File

@@ -0,0 +1,3 @@
body{
background: #000;
}

400
plugins/demo/demo.php Normal file
View 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
View File

@@ -0,0 +1 @@
<h1>Hello</h1>

186
plugins/demo/demo.yaml Normal file
View 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

View 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');
},
}
})

View 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 = '';
}
},
})

View File

@@ -0,0 +1 @@
I am twig

1
plugins/search Submodule

Submodule plugins/search added at d97fe18149

Some files were not shown because too many files have changed in this diff Show More