mirror of
https://github.com/typemill/typemill.git
synced 2025-08-01 20:00:37 +02:00
Version 1.4.6: Change content, CSRF-notification, fix blox editor, alert for raw editor, cyanine optimizations
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Write Content
|
||||
|
||||
Typemill is a simple Flat File Content Management System (CMS). We (the community) work hard to provide the best author experience with easy and intuitive authoring tools. But Typemill is still in early development and it is likely that not everything will work perfectly out of the box. If you miss something or if you have ideas for improvements, then post a new issue on [GitHub](https://github.com/typemill/typemill/issues).
|
||||
Typemill provides easy and intuitive authoring tools and we work hard to create a good author experience. With the interactive navigation you can create pages and structure your websites. The visual markdown editor will help you to create content in a wysiwyg mode. The publish bar gives you full control over the status of each page. Watch the following video tutorial to learn all the details.
|
||||
|
||||
{#6I2-uV88GkE .youtube}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Manage Access
|
||||
|
||||
Typemill has a build-in system to restrict access to pages or to the whole websites. You can activate both features in the system settings under the section "access rights". If you activate one of the features, then Typemill will use session cookies on all frontend pages.
|
||||
Typemill has a build-in system to restrict access to pages or to the whole websites. You can activate both features in the system settings under the section "access rights". If you activate one of the features, then Typemill will use session cookies on all frontend pages. Learn all the details in the following video tutorial:
|
||||
|
||||
{#UW_m-4g1kAA .youtube}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Welcome
|
||||
|
||||
Great that you give Typemill a try!! Typemill is a small open source cms and a project in work. You will probably miss some important features, but we are working hard to add everything that is needed for a handy and productive writing-system.
|
||||
Great that you give Typemill a try!! Typemill is a simple Flat File Content Management System (CMS). We (the community) work hard to provide the best author experience with easy and intuitive authoring tools. But Typemill is still in early development and it is likely that not everything will work perfectly out of the box. If you miss something or if you have ideas for improvements, then post a new issue on [GitHub](https://github.com/typemill/typemill/issues).
|
||||
|
||||
|
32
readme.md
32
readme.md
@@ -1,14 +1,14 @@
|
||||
# About TYPEMILL
|
||||
|
||||
TYPEMILL is a small flat file cms created for editors and writers. It provides an author-friendly dashboard and a visual-block-editor for markdown based on vue.js. Use TYPEMILL for manuals, documentations, web-books and similar publications. The website http://typemill.net itself is an example for TYPEMILL.
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
* Website with markdown-files.
|
||||
* Flexible drag & drop navigation.
|
||||
* Visual markdown editor (VUE.js) and raw markdown mode.
|
||||
* Flexible drag & drop navigation.
|
||||
* Markdown extras with
|
||||
* table of contents (TOC)
|
||||
* tables
|
||||
@@ -22,8 +22,15 @@ TYPEMILL is a small flat file cms created for editors and writers. It provides a
|
||||
* System configurations.
|
||||
* User management.
|
||||
* Flexible form management with YAML-files.
|
||||
* Plugins (with symfony event dispatcher).
|
||||
* Flexible access rights.
|
||||
* Themes (with TWIG).
|
||||
* Plugins (with symfony event dispatcher).
|
||||
|
||||
Some plugin highlights are:
|
||||
|
||||
* **Ebooks**: Generate one or many professional pdf books from your typemill website.
|
||||
* **Register**: Let users register to your website and give them access to pro-content.
|
||||
* **Subscribe** (in work): Sell subscriptions for premium content with traditional pdf-invoices.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -62,7 +69,7 @@ You can use your ftp-software for that.
|
||||
|
||||
## Setup
|
||||
|
||||
You will be redirected to the `/setup` page, please create an initial user and configure your system in the author panel.
|
||||
If you visit your website first, then you will be redirected to the `/setup` page. Please create an initial user and configure your system in the author panel.
|
||||
|
||||
## Login
|
||||
|
||||
@@ -91,39 +98,38 @@ TYPEMILL is published under MIT licence. Please check the licence of the include
|
||||
|
||||
## IMPORTANT: How to Contribute
|
||||
|
||||
Typemill is still in an early stage and contributions are highly welcome. Please follow these rules:
|
||||
Contributions are highly welcome. Please follow these rules:
|
||||
|
||||
* If you plan bigger changes, then please create an issue first so we can discuss it.
|
||||
* Fork the "DEVELOP" branch from typemill. Never use the master branch, because it is protected and only contains tested releases.
|
||||
* Do your changes.
|
||||
* Before your request, please pull the recent develop branch again to get latest changes.
|
||||
* Then make a pull request for the DEVELOP branch again.
|
||||
* After that pull the recent develop branch again to get the latest changes.
|
||||
* Then make a pull request for the DEVELOP branch.
|
||||
|
||||
You can check the [roadmap for Typemill](https://github.com/typemill/typemill/issues/35) and scroll through the issues. I will mark issues in future that are easy to start with or where help is highly appreciated.
|
||||
|
||||
Here are some contribution-ideas for non-coder:
|
||||
|
||||
* Share typemill with social media.
|
||||
* Write about typemill.
|
||||
* Share Typemill with social media.
|
||||
* Write about Typemill.
|
||||
* Improve the documentation.
|
||||
* Find bugs and errors (open a new issue on github for it).
|
||||
* Describe some missing features and explain, why they are important for other users.
|
||||
|
||||
Some ideas for devs (please fork this repository make your changes and create a pull request):
|
||||
Some ideas for devs:
|
||||
|
||||
* Fix a bug.
|
||||
* Create or port a theme, especially for documentations, knowlegde bases or web-books.
|
||||
* Create a fancy plugin.
|
||||
* An auto-update functionality for core system, plugins and themes is highly needed.
|
||||
* An auto-update functionality for the core system, for plugins and for themes is highly welcome.
|
||||
* Improve the accessibility of html and css.
|
||||
* Implement user roles and rights with RBAC or ACL.
|
||||
* Write autotests with Cypress.
|
||||
|
||||
For hints, questions, problems and support, please open up a new issue on GitHub.
|
||||
|
||||
## Support
|
||||
|
||||
This is an open source project. I love it and I spend about 20 hours a week on it (starting in 2017). There is no business model right now, but you can support this project with a donation or simply hire me for implementations.
|
||||
This is an open source project. I love it and I spend about 20 hours a week on it (starting in 2017). There is no business model right now, but you can support this project with a donation or simply [hire me](https://trendschau.net) for implementations.
|
||||
|
||||
Donate: https://www.paypal.me/typemill
|
||||
|
||||
|
@@ -76,6 +76,12 @@ class AuthController extends Controller
|
||||
|
||||
public function login(Request $request, Response $response)
|
||||
{
|
||||
if( ( null !== $request->getattribute('csrf_result') ) OR ( $request->getattribute('csrf_result') === false ) )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
/* log user attemps to authenticate */
|
||||
$yaml = new WriteYaml();
|
||||
$logins = $yaml->getYaml('settings/users', '.logins');
|
||||
|
@@ -325,7 +325,6 @@ class BlockApiController extends ContentController
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
|
||||
}
|
||||
|
||||
|
||||
/* parse markdown-file to content-array, if title parse title. */
|
||||
if($this->params['block_id'] == 0)
|
||||
|
@@ -14,12 +14,19 @@ class FormController extends Controller
|
||||
public function savePublicForm($request, $response, $args)
|
||||
{
|
||||
if($request->isPost())
|
||||
{
|
||||
{
|
||||
$params = $request->getParams();
|
||||
reset($params);
|
||||
$pluginName = key($params);
|
||||
$referer = $request->getHeader('HTTP_REFERER');
|
||||
|
||||
# check csrf protection
|
||||
if($request->getattribute('csrf_result') === false )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout. Please try again.');
|
||||
return $response->withRedirect($referer[0]);
|
||||
}
|
||||
|
||||
# simple bot check with honeypot
|
||||
if(isset($params[$pluginName]['personal-mail']))
|
||||
{
|
||||
|
@@ -67,7 +67,14 @@ class SettingsController extends Controller
|
||||
public function saveSettings($request, $response, $args)
|
||||
{
|
||||
if($request->isPost())
|
||||
{
|
||||
{
|
||||
|
||||
if( $request->getattribute('csrf_result') === false )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
|
||||
return $response->withRedirect($this->c->router->pathFor('settings.show'));
|
||||
}
|
||||
|
||||
$settings = \Typemill\Settings::getUserSettings();
|
||||
$defaultSettings = \Typemill\Settings::getDefaultSettings();
|
||||
$params = $request->getParams();
|
||||
@@ -378,7 +385,13 @@ class SettingsController extends Controller
|
||||
public function saveThemes($request, $response, $args)
|
||||
{
|
||||
if($request->isPost())
|
||||
{
|
||||
{
|
||||
if( $request->getattribute('csrf_result') === false )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
|
||||
return $response->withRedirect($this->c->router->pathFor('themes.show'));
|
||||
}
|
||||
|
||||
$userSettings = \Typemill\Settings::getUserSettings();
|
||||
$params = $request->getParams();
|
||||
$themeName = isset($params['theme']) ? $params['theme'] : false;
|
||||
@@ -465,6 +478,13 @@ class SettingsController extends Controller
|
||||
{
|
||||
if($request->isPost())
|
||||
{
|
||||
|
||||
if( $request->getattribute('csrf_result') === false )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
|
||||
return $response->withRedirect($this->c->router->pathFor('plugins.show'));
|
||||
}
|
||||
|
||||
$userSettings = \Typemill\Settings::getUserSettings();
|
||||
$pluginSettings = array();
|
||||
$userInput = $request->getParams();
|
||||
@@ -764,6 +784,12 @@ class SettingsController extends Controller
|
||||
{
|
||||
if($request->isPost())
|
||||
{
|
||||
if( $request->getattribute('csrf_result') === false )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
|
||||
return $response->withRedirect($this->c->router->pathFor('user.new'));
|
||||
}
|
||||
|
||||
$params = $request->getParams();
|
||||
$user = new User();
|
||||
$validate = new Validation();
|
||||
@@ -793,6 +819,12 @@ class SettingsController extends Controller
|
||||
|
||||
if($request->isPost())
|
||||
{
|
||||
if( $request->getattribute('csrf_result') === false )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
|
||||
return $response->withRedirect($this->c->router->pathFor('user.account'));
|
||||
}
|
||||
|
||||
$params = $request->getParams();
|
||||
$userdata = $params['user'];
|
||||
$user = new User();
|
||||
@@ -886,7 +918,13 @@ class SettingsController extends Controller
|
||||
public function deleteUser($request, $response, $args)
|
||||
{
|
||||
if($request->isPost())
|
||||
{
|
||||
{
|
||||
if( $request->getattribute('csrf_result') === false )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
|
||||
return $response->withRedirect($this->c->router->pathFor('user.account'));
|
||||
}
|
||||
|
||||
$params = $request->getParams();
|
||||
$validate = new Validation();
|
||||
$user = new User();
|
||||
|
@@ -62,6 +62,12 @@ class SetupController extends Controller
|
||||
{
|
||||
if($request->isPost())
|
||||
{
|
||||
if( $request->getattribute('csrf_result') === false )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
|
||||
return $response->withRedirect($this->c->router->pathFor('setup.welcome'));
|
||||
}
|
||||
|
||||
$params = $request->getParams();
|
||||
$validate = new Validation();
|
||||
$user = new User();
|
||||
|
@@ -19,8 +19,14 @@ class RestrictApiAccess
|
||||
{
|
||||
if(!isset($_SESSION['login']) || !isset($_SESSION['role']))
|
||||
{
|
||||
return $response->withJson(['errors' => ['access denied']], 403);
|
||||
return $response->withJson(['data' => false, 'errors' => ['message' => 'You are probably logged out. Please login and try again.']], 403);
|
||||
}
|
||||
|
||||
# check csrf protection
|
||||
if( $request->getattribute('csrf_result') === false )
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'The form has a timeout. Please reload the page and try again.']), 403);
|
||||
}
|
||||
return $next($request, $response);
|
||||
}
|
||||
}
|
@@ -226,7 +226,7 @@ h3{ font-size: 1em; margin: 0.6em 0 0.6em; text-transform: uppercase; font-weigh
|
||||
}
|
||||
section{
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 0 4px #ddd;
|
||||
box-shadow: 0 4px 4px #ddd;
|
||||
position: relative;
|
||||
margin-bottom: 40px;
|
||||
padding: 20px 5px 40px;
|
||||
@@ -949,7 +949,7 @@ header a.button{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
margin-top: -35px;
|
||||
z-index: 1;
|
||||
/* z-index: 1; */
|
||||
}
|
||||
.tab-button{
|
||||
font-size: 0.9em;
|
||||
@@ -968,7 +968,7 @@ header a.button{
|
||||
outline: none;
|
||||
}
|
||||
section.tab{
|
||||
z-index: -1;
|
||||
/* z-index: -1; */
|
||||
padding: 40px;
|
||||
}
|
||||
/********************
|
||||
@@ -2358,16 +2358,18 @@ button.format-item.close:hover{
|
||||
display: inline-block;
|
||||
margin-top: 15px;
|
||||
}
|
||||
input.definitionTerm{
|
||||
.definitionTerm{
|
||||
width: 29%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
.definitionDescription{
|
||||
width: 60%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
background: #fff;
|
||||
}
|
||||
textarea.definitionDescription{
|
||||
width: 60%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
.definitionRow input, .definitionRow textarea{
|
||||
background: #fff;
|
||||
}
|
||||
button.addDL,
|
||||
|
@@ -20,29 +20,37 @@
|
||||
{% endif %}
|
||||
{% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %}
|
||||
<a v-if="visual" href="{{ base_url }}/tm/content/raw{{ itemurl }}" class="button--secondary"><span class="desktop">{{ __('raw mode') }}</span><span class="mobile">{{ __('raw') }}</span></a>
|
||||
<a v-if="raw" href="{{ base_url }}/tm/content/visual{{ itemurl }}" class="button--secondary"><span class="desktop">{{ __('visual mode') }}</span><span class="mobile">{{ __('visual') }}</span></a>
|
||||
<a v-if="raw" @click="checkUnsafedContent(event)" href="{{ base_url }}/tm/content/visual{{ itemurl }}" class="button--secondary"><span class="desktop">{{ __('visual mode') }}</span><span class="mobile">{{ __('visual') }}</span></a>
|
||||
{% endif %}
|
||||
<a target="_blank" class="button--secondary" href="{{ item.urlAbs }}"><svg class="icon baseline icon-external-link"><use xlink:href="#icon-external-link"></use></svg></a>
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<div v-if="modalWindow" id="modalWindow" class="modalWindow">
|
||||
<div class="modalInner">
|
||||
<div @click="hideModal" id="closeModal" class="closeModal">X</div>
|
||||
{% if (acl.isAllowed(get_role(), 'content', 'delete')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'delete')) ) %}
|
||||
<div v-if="modalType == 'delete'">
|
||||
<h2>{{ __('Delete page') }}</h2>
|
||||
<p>{{ __('Do you really want to delete this page') }} {{ __('Please confirm') }}</p>
|
||||
<button @click.prevent="deleteArticle" class="large alert" :class="deleteResult" :disabled="deleteDisabled">{{ __('Delete Page') }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %}
|
||||
<div v-if="modalType == 'discard'">
|
||||
<h2>{{ __('Discard Changes') }}</h2>
|
||||
<p>{{ __('Do you want to discard your changes and set the content back to the live version') }}</p>
|
||||
<button @click.prevent="discardDraft" class="large fullwidth" :class="discardResult" :disabled="publishDisabled">{{ __('Discard Changes') }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<div v-if="modalWindow" id="modalWindow" class="modalWindow">
|
||||
<div class="modalInner">
|
||||
<div @click="hideModal" id="closeModal" class="closeModal">X</div>
|
||||
{% if (acl.isAllowed(get_role(), 'content', 'delete')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'delete')) ) %}
|
||||
<div v-if="modalType == 'delete'">
|
||||
<h2>{{ __('Delete page') }}</h2>
|
||||
<p>{{ __('Do you really want to delete this page') }} {{ __('Please confirm') }}</p>
|
||||
<button @click.prevent="deleteArticle" class="large alert" :class="deleteResult" :disabled="deleteDisabled">{{ __('Delete Page') }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %}
|
||||
<div v-if="modalType == 'discard'">
|
||||
<h2>{{ __('Discard Changes') }}</h2>
|
||||
<p>{{ __('Do you want to discard your changes and set the content back to the live version') }}</p>
|
||||
<button @click.prevent="discardDraft" class="large fullwidth" :class="discardResult" :disabled="publishDisabled">{{ __('Discard Changes') }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %}
|
||||
<div v-if="modalType == 'unsafed'">
|
||||
<h2>{{ __('Save Your Changes') }}</h2>
|
||||
<p>{{ __('You have unsaved changes. Go back and save your changes. If you proceed to the visual mode now, then you will loose your changes.') }}</p>
|
||||
<button @click="hideModal" class="large fullwidth">{{ __('Go back and save') }}</button>
|
||||
<a class="w-100 tc ma2 tm-red hover-bg-tm-red hover-white" href="{{ base_url }}/tm/content/visual{{ itemurl }}">{{ __('go to visual mode') }}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
@@ -718,33 +718,60 @@ const codeComponent = Vue.component('code-component', {
|
||||
template: '<div>' +
|
||||
'<input type="hidden" ref="markdown" :value="compmarkdown" :disabled="disabled" @input="updatemarkdown" />' +
|
||||
'<div class="contenttype"><svg class="icon icon-embed"><use xlink:href="#icon-embed"></use></svg></div>' +
|
||||
'<div class="w-100 pa2 white bg-tm-green dib">' +
|
||||
'<label for="language" class="w-10">{{ \'Language\'|translate }}: </label>' +
|
||||
'<input class="pa1 w-90 min-h-100" name="language" type="text" v-model="language" :disabled="disabled" @input="createlanguage">' +
|
||||
'</div>' +
|
||||
'<textarea class="mdcontent" ref="markdown" v-model="codeblock" :disabled="disabled" @input="createmarkdown"></textarea>' +
|
||||
'</div>',
|
||||
data: function(){
|
||||
return {
|
||||
codeblock: ''
|
||||
prefix: '```',
|
||||
language: '',
|
||||
codeblock: '',
|
||||
}
|
||||
},
|
||||
mounted: function(){
|
||||
this.$refs.markdown.focus();
|
||||
if(this.compmarkdown)
|
||||
{
|
||||
var codeblock = this.compmarkdown.replace("````\n", "");
|
||||
codeblock = codeblock.replace("```\n", "");
|
||||
codeblock = codeblock.replace("\n````", "");
|
||||
codeblock = codeblock.replace("\n```", "");
|
||||
codeblock = codeblock.replace("\n\n", "\n");
|
||||
this.codeblock = codeblock;
|
||||
var codelines = this.compmarkdown.split(/\r\n|\n\r|\n|\r/);
|
||||
var linelength = codelines.length;
|
||||
var codeblock = '';
|
||||
|
||||
for(i=0;i<linelength;i++)
|
||||
{
|
||||
if(codelines[i].substring(0,3) == '```')
|
||||
{
|
||||
if(i==0)
|
||||
{
|
||||
var prefixlength = (codelines[i].match(/`/g)).length;
|
||||
this.prefix = codelines[i].slice(0, prefixlength);
|
||||
this.language = codelines[i].replace(/`/g, '');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.codeblock += codelines[i] + "\n";
|
||||
}
|
||||
}
|
||||
this.codeblock = this.codeblock.replace(/^\s+|\s+$/g, '');
|
||||
}
|
||||
this.$nextTick(function () {
|
||||
autosize(document.querySelectorAll('textarea'));
|
||||
this.$parent.setComponentSize();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
createlanguage: function()
|
||||
{
|
||||
var codeblock = this.prefix + this.language + '\n' + this.codeblock + '\n' + this.prefix;
|
||||
this.updatemarkdown(codeblock);
|
||||
},
|
||||
createmarkdown: function(event)
|
||||
{
|
||||
this.codeblock = event.target.value;
|
||||
var codeblock = '````\n' + event.target.value + '\n````';
|
||||
var codeblock = this.prefix + this.language + '\n' + this.codeblock + '\n' + this.prefix;
|
||||
this.updatemarkdown(codeblock);
|
||||
},
|
||||
updatemarkdown: function(codeblock)
|
||||
@@ -1243,43 +1270,76 @@ const definitionComponent = Vue.component('definition-component', {
|
||||
}
|
||||
},
|
||||
template: '<div class="definitionList">' +
|
||||
'<div class="contenttype"><svg class="icon icon-dots-two-vertical"><use xlink:href="#icon-dots-two-vertical"></use></svg></div>' +
|
||||
'<draggable v-model="definitionList" :animation="150" @end="moveDefinition">' +
|
||||
'<div class="definitionRow" v-for="(definition, dindex) in definitionList" :key="definition.id">' +
|
||||
'<svg class="icon icon-arrows-v"><use xlink:href="#icon-arrows-v"></use></svg>' +
|
||||
'<input type="text" class="definitionTerm" v-bind:placeholder="\'term\'|translate" :value="definition.term" :disabled="disabled" @input="updateterm($event,dindex)" @blur="updateMarkdown">' +
|
||||
'<svg class="icon icon-dots-two-vertical"><use xlink:href="#icon-dots-two-vertical"></use></svg>' +
|
||||
'<textarea class="definitionDescription" v-bind:placeholder="\'description\'|translate" v-html="definition.description" :disabled="disabled" @input="updatedescription($event, dindex)" @keydown.13.prevent="enter" @blur="updateMarkdown"></textarea>' +
|
||||
'<button class="delDL" @click.prevent="deleteDefinition(dindex)"><svg class="icon icon-minus"><use xlink:href="#icon-minus"></use></svg></button>' +
|
||||
'</div>' +
|
||||
'</draggable>' +
|
||||
'<button class="addDL" @click.prevent="addDefinition()"><svg class="icon icon-plus"><use xlink:href="#icon-plus"></use></svg> {{ \'add definition\'|translate }}</button>' +
|
||||
'<div v-if="load" class="loadwrapper"><span class="load"></span></div>' +
|
||||
'</div>',
|
||||
'<div class="contenttype"><svg class="icon icon-dots-two-vertical"><use xlink:href="#icon-dots-two-vertical"></use></svg></div>' +
|
||||
'<draggable v-model="definitionList" :animation="150" @end="moveDefinition">' +
|
||||
'<div class="definitionRow relative" v-for="(definition, dtindex) in definitionList" :key="definition.id">' +
|
||||
'<div class="definitionTerm">' +
|
||||
'<svg class="icon icon-arrows-v"><use xlink:href="#icon-arrows-v"></use></svg>' +
|
||||
'<input type="text" class="w-90" v-bind:placeholder="\'term\'|translate" :value="definition.term" :disabled="disabled" @input="updateterm($event,dtindex)" @blur="updateMarkdown">' +
|
||||
'</div>' +
|
||||
'<div class="dib w-60">' +
|
||||
'<div class="flex" v-for="(description,ddindex) in definition.descriptions">' +
|
||||
'<svg class="icon icon-dots-two-vertical"><use xlink:href="#icon-dots-two-vertical"></use></svg>' +
|
||||
'<textarea class="w-90" v-bind:placeholder="\'description\'|translate" v-html="definition.descriptions[ddindex]" :disabled="disabled" @input="updatedescription($event, dtindex, ddindex)" @keydown.13.prevent="enter" @blur="updateMarkdown"></textarea>' +
|
||||
'<button title="delete description" class="ba bw1 b--tm-gray white bg-tm-red w2 h2 f7 lh-solid dim" @click.prevent="deleteItem(dtindex,ddindex)"><svg class="icon icon-minus baseline"><use xlink:href="#icon-minus"></use></svg></button>' +
|
||||
'</div>' +
|
||||
'<button title="add description" class="ml3 ba bw1 b--tm-gray white bg-tm-green w2 h2 f7 lh-solid dim" @click.prevent="addItem(dtindex)"><svg class="icon icon-plus"><use xlink:href="#icon-plus"></use></svg></button>' +
|
||||
'</div>' +
|
||||
'<button title="delete definition" class="absolute top-1 right-2 bottom-1 tc pa2 ba br1 b--tm-red tm-red bg-tm-gray hover-bg-tm-red hover-white" @click.prevent="deleteDefinition(dtindex)">x</button>' +
|
||||
'<hr></div>' +
|
||||
'</draggable>' +
|
||||
'<button class="ml3 mb3 mr1 ba bw1 b--tm-gray white bg-tm-green w2 h2 f7 lh-solid dim" @click.prevent="addDefinition()"><svg class="icon icon-plus"><use xlink:href="#icon-plus"></use></svg></button><span class="f6">{{ \'add definition\'|translate }}</span>' +
|
||||
'<div v-if="load" class="loadwrapper"><span class="load"></span></div>' +
|
||||
'</div>',
|
||||
mounted: function(){
|
||||
if(this.compmarkdown)
|
||||
{
|
||||
var definitionList = this.compmarkdown.replace("\r\n", "\n");
|
||||
definitionList = definitionList.replace("\r", "\n");
|
||||
definitionList = definitionList.split("\n\n");
|
||||
var definitionList = this.compmarkdown.replace("\r\n", "\n");
|
||||
definitionList = definitionList.replace("\r", "\n");
|
||||
definitionList = definitionList.split("\n\n");
|
||||
|
||||
for(var i=0; i < definitionList.length; i++)
|
||||
{
|
||||
var definition = definitionList[i].split("\n");
|
||||
|
||||
var term = definition[0];
|
||||
var description = definition[1];
|
||||
var id = i;
|
||||
var definitionBlock = definitionList[i].split("\n");
|
||||
var term = definitionBlock[0];
|
||||
var descriptions = [];
|
||||
var description = false;
|
||||
|
||||
if(description && description.substring(0, 2) == ": ")
|
||||
/* parse one or more descriptions */
|
||||
for(var y=0; y < definitionBlock.length; y++)
|
||||
{
|
||||
this.definitionList.push({'term': term ,'description': description.substring(2), 'id': id});
|
||||
if(y == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(definitionBlock[y].substring(0, 2) == ": ")
|
||||
{
|
||||
/* if there is another description in the loop, then push that first then start a new one */
|
||||
if(description)
|
||||
{
|
||||
descriptions.push(description);
|
||||
}
|
||||
var cleandefinition = definitionBlock[y].substr(1).trim();
|
||||
var description = cleandefinition;
|
||||
}
|
||||
else
|
||||
{
|
||||
description += "\n" + definitionBlock[y];
|
||||
}
|
||||
}
|
||||
|
||||
if(description)
|
||||
{
|
||||
descriptions.push(description);
|
||||
}
|
||||
this.definitionList.push({'term': term ,'descriptions': descriptions, 'id': i});
|
||||
}
|
||||
console.info(this.definitionList);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.definitionList.push({'term': '', 'description': '', 'id': 0});
|
||||
this.addDefinition();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -1287,23 +1347,31 @@ const definitionComponent = Vue.component('definition-component', {
|
||||
{
|
||||
return false;
|
||||
},
|
||||
updateterm: function(event, dindex)
|
||||
updateterm: function(event, dtindex)
|
||||
{
|
||||
this.definitionList[dindex].term = event.target.value;
|
||||
this.definitionList[dtindex].term = event.target.value;
|
||||
},
|
||||
updatedescription: function(event, dindex)
|
||||
updatedescription: function(event, dtindex, ddindex)
|
||||
{
|
||||
this.definitionList[dindex].description = event.target.value;
|
||||
this.definitionList[dtindex].descriptions[ddindex] = event.target.value;
|
||||
},
|
||||
addDefinition: function()
|
||||
{
|
||||
var id = this.definitionList.length;
|
||||
this.definitionList.push({'term': '', 'description': '', 'id': id});
|
||||
this.definitionList.push({'term': '', 'descriptions': [''], 'id': id});
|
||||
},
|
||||
deleteDefinition: function(dtindex)
|
||||
{
|
||||
this.definitionList.splice(dtindex,1);
|
||||
this.updateMarkdown();
|
||||
},
|
||||
deleteDefinition: function(dindex)
|
||||
addItem: function(dtindex)
|
||||
{
|
||||
this.definitionList.splice(dindex,1);
|
||||
this.definitionList[dtindex].descriptions.push('');
|
||||
},
|
||||
deleteItem: function(dtindex,ddindex)
|
||||
{
|
||||
this.definitionList[dtindex].descriptions.splice(ddindex,1);
|
||||
this.updateMarkdown();
|
||||
},
|
||||
moveDefinition: function(evt)
|
||||
@@ -1312,11 +1380,18 @@ const definitionComponent = Vue.component('definition-component', {
|
||||
},
|
||||
updateMarkdown: function()
|
||||
{
|
||||
var length = this.definitionList.length;
|
||||
var dllength = this.definitionList.length;
|
||||
var markdown = '';
|
||||
for(i = 0; i < length; i++)
|
||||
|
||||
for(i = 0; i < dllength; i++)
|
||||
{
|
||||
markdown = markdown + this.definitionList[i].term + "\n: " + this.definitionList[i].description + "\n\n";
|
||||
markdown = markdown + this.definitionList[i].term;
|
||||
var ddlength = this.definitionList[i].descriptions.length;
|
||||
for(y = 0; y < ddlength; y++)
|
||||
{
|
||||
markdown = markdown + "\n: " + this.definitionList[i].descriptions[y];
|
||||
}
|
||||
markdown = markdown + "\n\n";
|
||||
}
|
||||
this.$emit('updatedMarkdown', markdown);
|
||||
},
|
||||
|
@@ -193,6 +193,14 @@ let publishController = new Vue({
|
||||
self.handleErrors(error);
|
||||
});
|
||||
},
|
||||
checkUnsafedContent(event){
|
||||
console.info(this.draftDisabled);
|
||||
if(!this.draftDisabled)
|
||||
{
|
||||
event.preventDefault();
|
||||
this.showModal('unsafed');
|
||||
}
|
||||
},
|
||||
showModal: function(type){
|
||||
this.modalType = type;
|
||||
this.modalWindow = true;
|
||||
|
@@ -210,7 +210,12 @@ foreach($session_segments as $segment)
|
||||
{
|
||||
$guard = new \Slim\Csrf\Guard();
|
||||
$guard->setPersistentTokenMode(true);
|
||||
|
||||
$guard->setfailurecallable(function ($request, $response, $next)
|
||||
{
|
||||
$request = $request->withattribute("csrf_result", false);
|
||||
return $next($request, $response);
|
||||
});
|
||||
|
||||
return $guard;
|
||||
};
|
||||
|
||||
|
@@ -124,8 +124,10 @@ article a:hover, article a:focus, article a:active,
|
||||
footer a:hover, footer a:focus, footer a:active{
|
||||
text-decoration: none;
|
||||
}
|
||||
/* Fix background for hightlight plugin */
|
||||
|
||||
/* Fix background for hightlight plugin
|
||||
code.hljs{ background: transparent; }
|
||||
*/
|
||||
|
||||
/* For definition list */
|
||||
dt::after{
|
||||
|
@@ -1,5 +1,5 @@
|
||||
name: Cyanine Theme
|
||||
version: 1.1.3
|
||||
version: 1.1.4
|
||||
description: Cyanine is a modern and flexible multi-purpose theme and the standard theme for typemill.
|
||||
author: Trendschau
|
||||
homepage: https://trendschau.net
|
||||
@@ -243,18 +243,34 @@ forms:
|
||||
label: Text/label for edit link
|
||||
placeholder: edit on github
|
||||
|
||||
fieldsetChapters:
|
||||
fieldsetNavigations:
|
||||
type: fieldset
|
||||
legend: Navigations and Chapters
|
||||
fields:
|
||||
collapseNav:
|
||||
type: checkbox
|
||||
label: Collapse navigation
|
||||
checkboxlabel: Collapse and expand navigation?
|
||||
chapnum:
|
||||
type: checkbox
|
||||
label: Chapter Numbers
|
||||
checkboxlabel: Show chapter numbers in navigation?
|
||||
collapseNav:
|
||||
type: checkbox
|
||||
label: Collapse navigation
|
||||
checkboxlabel: Collapse and expand navigation?
|
||||
expand:
|
||||
type: text
|
||||
label: Label for expand button
|
||||
placeholder: expand navigation
|
||||
collapse:
|
||||
type: text
|
||||
label: Label for collapse button
|
||||
placeholder: collapse navigation
|
||||
next:
|
||||
type: text
|
||||
label: Label for next link
|
||||
placeholder: next
|
||||
previous:
|
||||
type: text
|
||||
label: Label for previous link
|
||||
placeholder: previous
|
||||
|
||||
fieldsetfooter:
|
||||
type: fieldset
|
||||
|
@@ -2,7 +2,9 @@
|
||||
|
||||
<div class="mw7 pb7 ph3 center">
|
||||
<header class="mt6">
|
||||
{% if settings.themes.cyanine.introTitle %}
|
||||
{% if logo %}
|
||||
<img src="{{ base_url }}/{{ logo }}" class="logo-image"/>
|
||||
{% elseif settings.themes.cyanine.introTitle %}
|
||||
<h1 class="f-large f-headline-ns lh-title mv2 pt5-ns">{{ settings.themes.cyanine.introTitle }}</h1>
|
||||
{% else %}
|
||||
<h1 class="f-large f-headline-ns lh-title mv2 pt5-ns">{{ title }}</h1>
|
||||
|
@@ -242,7 +242,10 @@
|
||||
|
||||
{% if settings.themes.cyanine.collapseNav %}
|
||||
<script>
|
||||
var expandButton = document.getElementById("expander");
|
||||
var expandButton = document.getElementById("expander");
|
||||
var expandLabel = expandButton.dataset.expandlabel;
|
||||
var collapseLabel = expandButton.dataset.collapselabel;
|
||||
|
||||
if(expandButton)
|
||||
{
|
||||
expandButton.addEventListener('click', function(e)
|
||||
@@ -253,11 +256,11 @@
|
||||
|
||||
if(contentnav.classList.contains("collapse"))
|
||||
{
|
||||
expandButton.innerHTML = "expand navigation";
|
||||
expandButton.innerHTML = expandLabel;
|
||||
}
|
||||
else
|
||||
{
|
||||
expandButton.innerHTML = "collapse navigation";
|
||||
expandButton.innerHTML = collapseLabel;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -14,10 +14,10 @@
|
||||
</div>
|
||||
<div class="dn dib-l">
|
||||
{% if item.prevItem %}
|
||||
<a class="link pr1 f-link underline-hover" href="{{ item.prevItem.urlRel }}">‹ {{ __('previous') }}</a>
|
||||
<a class="link pr1 f-link underline-hover" href="{{ item.prevItem.urlRel }}">‹ {{ settings.themes.cyanine.previous|default('previous') }}</a>
|
||||
{% endif %}
|
||||
{% if item.nextItem %}
|
||||
<a class="link pl1 f-link underline-hover" href="{{ item.nextItem.urlRel }}">{{ __('next') }} ›</a>
|
||||
<a class="link pl1 f-link underline-hover" href="{{ item.nextItem.urlRel }}">{{ settings.themes.cyanine.next|default('next') }} ›</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
@@ -1,7 +1,7 @@
|
||||
{% set nowYear = "now"|date("Y") %}
|
||||
|
||||
<footer class="w-100 bl br bb lh-copy">
|
||||
<div class="mw8 center pv3 flex-l">
|
||||
<div class="grid-container center pv3 flex-l">
|
||||
{% for column,key in settings.themes.cyanine.footercolumns %}
|
||||
{% if settings.themes.cyanine[column] %}
|
||||
<div class="w-100 pv3 ph3 ph4-l">
|
||||
@@ -10,7 +10,7 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="mw8 center pv3 tc-l w100">
|
||||
<div class="center pv3 tc-l w100">
|
||||
{% if settings.year is empty or settings.year == nowYear %}
|
||||
{% set copyrightYears = nowYear %}
|
||||
{% else %}
|
||||
|
@@ -31,7 +31,7 @@
|
||||
{% import _self as macros %}
|
||||
|
||||
{% if settings.themes.cyanine.collapseNav %}
|
||||
<button id="expander" class="expander dim dn db-l link pointer w-100 ba pa2 mb3 f6">expand navigation</button>
|
||||
<button id="expander" data-expandlabel="{{ settings.themes.cyanine.expand|default('expand navigation') }}" data-collapselabel="{{ settings.themes.cyanine.collapse|default('collapse navigation') }}" class="expander dim dn db-l link pointer w-100 ba pa2 mb3 f6">{{ settings.themes.cyanine.expand|default('expand navigation') }}</button>
|
||||
{% endif %}
|
||||
|
||||
<ul class="cy-nav mainnavigation list pa0 menu">
|
||||
|
Reference in New Issue
Block a user