1
0
mirror of https://github.com/typemill/typemill.git synced 2025-08-05 05:37:45 +02:00

Version 1.2.17: Format inline elements

This commit is contained in:
trendschau
2019-11-03 15:47:40 +01:00
parent a52dc8a166
commit 7dccfdbfcb
14 changed files with 278 additions and 58 deletions

2
cache/lastCache.txt vendored
View File

@@ -1 +1 @@
1571726903
1572791939

View File

@@ -1,9 +1,9 @@
{
"name": "trendschau/typemill",
"name": "typemill/typemill",
"type": "project",
"description": "A crazy simple tool to create web-documentations and online manuals with markdown files.",
"keywords": ["documentations","manuals","flat-file","Markdown","php"],
"homepage": "http://typemill.net",
"homepage": "https://typemill.net",
"license": "MIT",
"config": {
"vendor-dir": "system/vendor"

View File

@@ -299,8 +299,6 @@ $$
x = \int_{0^1}^1(-b \pm \sqrt{b^2-4ac})/(2a)
$$
Das war es dann aber auch.
[^1]: Thank you for scrolling.
[^2]: This is the end of the page.

View File

@@ -18,6 +18,7 @@ TYPEMILL is a small flat file cms created for editors and writers. It provides a
* Allows super easy backend and frontend forms with simple YAML-files.
* Ships with a fully responsive standard theme
* Ships with plugins for
* Search
* MathJax and KaTeX.
* Code highlighting.
* Matomo/Piwik and Google Analytics.
@@ -60,7 +61,7 @@ You can use your ftp-software for that.
## Setup
Please go to `your-typemill-website.com/setup`, create an initial user and then setup your system in the author panel.
Please go to `your-typemill-website.com/setup`, create an initial user and configure your system in the author panel.
## Login
@@ -88,17 +89,20 @@ Typemill is still in an early stage and contributions are highly welcome. Here a
Some ideas for devs (please fork this repository make your changes and create a pull request):
* Fix a bug.
* Create a nice theme.
* Create a new plugin.
* Improve the CSS-code with BEM or utility-css (e.g. Tailwind) and make it modular.
* Rebuild the theme with the new css-grid feature.
* Create a theme.
* Create a plugin.
* Auto-update functionality for core system plugins and themes.
* Create a plugin and theme download page.
* Improve the accessibility of html and css.
* Help to establish autotests with selenium or cypress.
* Write unit-tests.
* Write an auto-update functionality.
* Implement an ACL for user roles and rights.
For hints, questions, problems and support, please open up a new issue on GitHub.
## Licence
TYPEMILL is published under MIT licence. Please check the licence of the included libraries, too.
TYPEMILL is published under MIT licence. Please check the licence of the included libraries, too.
## Community & Supporters
* [Eziquel Bruni](https://github.com/EzequielBruni) edits the typemill documentation.
* [vodaris](https://www.vodaris.de) sponsored the development of the search plugin.

View File

@@ -87,7 +87,7 @@ class ContentApiController extends ContentController
}
public function unpublishArticle(Request $request, Response $response, $args)
{
{
# get params from call
$this->params = $request->getParams();
$this->uri = $request->getUri();
@@ -129,6 +129,19 @@ class ContentApiController extends ContentController
}
}
# check if it is a folder and if the folder has published pages.
$message = false;
if($this->item->elementType == 'folder')
{
foreach($this->item->folderContent as $folderContent)
{
if($folderContent->status == 'published')
{
$message = 'There are published pages within this folder. The pages are not visible on your website anymore.';
}
}
}
# update the file
$delete = $this->deleteContentFiles(['md']);
@@ -143,7 +156,7 @@ class ContentApiController extends ContentController
# dispatch event
$this->c->dispatcher->dispatch('onPageUnpublished', new OnPageUnpublished($this->item));
return $response->withJson(['success'], 200);
return $response->withJson(['success' => ['message' => $message]], 200);
}
else
{

View File

@@ -45,7 +45,7 @@ class SettingsController extends Controller
$params = $request->getParams();
$newSettings = isset($params['settings']) ? $params['settings'] : false;
$validate = new Validation();
if($newSettings)
{
/* make sure only allowed fields are stored */
@@ -54,6 +54,7 @@ class SettingsController extends Controller
'author' => $newSettings['author'],
'copyright' => $newSettings['copyright'],
'year' => $newSettings['year'],
'language' => $newSettings['language'],
'startpage' => isset($newSettings['startpage']) ? true : false,
'editor' => $newSettings['editor'],
);
@@ -562,6 +563,13 @@ class SettingsController extends Controller
if($validate->username($params['username']))
{
$user->deleteUser($params['username']);
# if user deleted his own account
if($_SESSION['user'] == $params['username'])
{
session_destroy();
return $response->withRedirect($this->c->router->pathFor('auth.show'));
}
$this->c->flash->addMessage('info', 'Say goodbye, the user is gone!');
return $response->withRedirect($this->c->router->pathFor('user.list'));

View File

@@ -15,16 +15,36 @@ class SetupController extends Controller
$checkFolder = new Write();
$systemcheck = array();
# check folders and create them if possible
try{ $checkFolder->checkPath('settings'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); }
try{ $checkFolder->checkPath('settings/users'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); }
try{ $checkFolder->checkPath('content'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); }
try{ $checkFolder->checkPath('cache'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); }
try{ $checkFolder->checkPath('media'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); }
# check php-version
if (version_compare(phpversion(), '7.0.0', '<')) {
$systemcheck['error'][] = 'The PHP-version of your server is ' . phpversion() . ' and Typemill needs at least 7.0.0';
}
# check if mod rewrite is enabled
$modules = apache_get_modules();
if(!in_array('mod_rewrite', $modules))
{
$systemcheck['error'][] = 'The apache module "mod_rewrite" is not enabled.';
}
# check if GD extension is enabled
if(!extension_loaded('gd')){
$systemcheck['error'][] = 'The php-extension GD for image manipulation is not enabled.';
}
$setuperrors = empty($systemcheck) ? false : 'Some system requirements for Typemill are missing.';
$systemcheck = empty($systemcheck) ? false : $systemcheck;
return $this->render($response, 'auth/setup.twig', array( 'messages' => $systemcheck ));
return $this->render($response, 'auth/setup.twig', array( 'messages' => $setuperror, 'systemcheck' => $systemcheck ));
}
public function create($request, $response, $args)

View File

@@ -4,9 +4,19 @@
{% block content %}
<div class="setupWrapper">
{% if systemcheck %}
<h2>Missing Requirements</h2>
<ul style="color:red;padding: 0 14px">
{% for systemerror in systemcheck %}
<li style="margin: 5px 0">{{ systemerror }}</li>
{% endfor %}
</ul>
{% endif %}
<div class="authformWrapper">
<form method="POST" action="{{ path_for('setup.create') }}" autocomplete="off">
<fieldset class="auth">
<div class="formElement{{ errors.username ? ' errors' : '' }}">
<label for="username">Username <abbr title="required">*</abbr></label>

View File

@@ -11,7 +11,7 @@
<h1>Hurra!</h1>
<p>Your account has been created and you are logged in now.</p>
<p><strong>Next step:</strong> Visit the author panel and setup your new website. You can configure the system, choose themes and add plugins.</p>
<p><strong>New:</strong> Typemill ships with a new search plugin now. Just activate the plugin and enjoy!!</p>
<p><strong>New:</strong>Not sure how to add strong, emphasis and inline-code with Markdown? We have buttons for that now!!</p>
<p><strong>Get help:</strong> If you have any questions, please consult the <a target="_blank" href="https://typemill.net/typemill"><i class="icon-link-ext"></i> docs</a> or open a new issue on <a target="_blank" href="https://github.com/typemill/typemill"><i class="icon-link-ext"></i> github</a>.</p>
</div>
<a class="button" href="{{ path_for('settings.show') }}">Configure your website</a>

View File

@@ -1660,6 +1660,49 @@ button.format-item.close:hover{
border: 1px solid #cc4146;
}
/************************
** INLINE FORMATG BAR **
************************/
/* format menu */
.inlineFormatBar {
height: 30px;
padding: 5px 10px;
background: #333;
border-radius: 3px;
position: absolute;
top: 0;
left: 0;
transform: translate(-50%, -100%);
transition: 0.2s all;
display: flex;
justify-content: center;
align-items: center;
}
/* Triangle below format popup */
.inlineFormatBar:after {
content: '';
position: absolute;
left: 50%;
bottom: -5px;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid #333;
}
.inlineFormatItem {
color: #FFF;
cursor: pointer;
}
.inlineFormatItem:hover {
color: #1199ff;
}
.inlineFormatItem + .inlineFormatItem {
margin-left: 10px;
}
/************************
** BLOX EDITOR CONTENT **
************************/

View File

@@ -78,7 +78,7 @@ const contentComponent = Vue.component('content-block', {
this.compmarkdown = $event;
this.$nextTick(function () {
this.$refs.preview.style.minHeight = this.$refs.component.offsetHeight + 'px';
});
});
},
switchToEditMode: function()
{
@@ -86,7 +86,7 @@ const contentComponent = Vue.component('content-block', {
eventBus.$emit('closeComponents');
self = this;
self.$root.$data.freeze = true; /* freeze the data */
self.$root.$data.sortdisabled = true; /* disable sorting */
self.$root.$data.sortdisabled = true; /* disable sorting */
this.preview = 'hidden'; /* hide the html-preview */
this.edit = true; /* show the edit-mode */
this.compmarkdown = self.$root.$data.blockMarkdown; /* get markdown data */
@@ -392,6 +392,123 @@ const contentComponent = Vue.component('content-block', {
},
})
const inlineFormatsComponent = Vue.component('inline-formats', {
template: '<div><div :style="{ left: `${x}px`, top: `${y}px` }" @mousedown.prevent="" v-show="showInlineFormat" id="formatBar" class="inlineFormatBar">' +
'<span class="inlineFormatItem" @mousedown.prevent="formatBold"><i class="icon-bold"></i></span>' +
'<span class="inlineFormatItem" @mousedown.prevent="formatItalic"><i class="icon-italic"></i></span>' +
'<span class="inlineFormatItem" @mousedown.prevent="formatCode"><i class="icon-code"></i></span>' +
'</div><slot></slot></div>',
data: function(){
return {
formatBar: false,
startX: 0,
startY: 0,
x: 0,
y: 0,
textComponent: '',
selectedText: '',
startPos: false,
endPos: false,
showInlineFormat: false,
}
},
mounted: function() {
this.formatBar = document.getElementById('formatBar');
window.addEventListener('mouseup', this.onMouseup),
window.addEventListener('mousedown', this.onMousedown)
},
beforeDestroy: function() {
window.removeEventListener('mouseup', this.onMouseup),
window.removeEventListener('mousedown', this.onMousedown)
},
computed: {
highlightableEl () {
return this.$slots.default[0].elm
}
},
methods: {
onMousedown: function(event) {
this.startX = event.offsetX;
this.startY = event.offsetY;
},
onMouseup: function(event) {
/* if click is on format popup */
if(this.formatBar.contains(event.target))
{
return;
}
/* if click is outside the textarea */
if(!this.highlightableEl.contains(event.target))
{
this.showInlineFormat = false;
return;
}
this.textComponent = document.getElementsByClassName("mdcontent")[0];
/* grab the selected text */
if (document.selection != undefined)
{
this.textComponent.focus();
var sel = document.selection.createRange();
selectedText = sel.text;
}
/* Mozilla version */
else if (this.textComponent.selectionStart != undefined)
{
this.startPos = this.textComponent.selectionStart;
this.endPos = this.textComponent.selectionEnd;
selectedText = this.textComponent.value.substring(this.startPos, this.endPos)
}
var trimmedSelection = selectedText.replace(/\s/g, '');
if(trimmedSelection.length == 0)
{
this.showInlineFormat = false;
return;
}
/* determine the width of selection to position the format bar */
if(event.offsetX > this.startX)
{
var width = event.offsetX - this.startX;
this.x = event.offsetX - (width/2);
}
else
{
var width = this.startX - event.offsetX;
this.x = event.offsetX + (width/2);
}
this.y = event.offsetY - 15;
this.showInlineFormat = true;
this.selectedText = selectedText;
},
formatBold()
{
content = this.textComponent.value;
content = content.substring(0, this.startPos) + '**' + this.selectedText + '**' + content.substring(this.endPos, content.length);
this.$parent.updatemarkdown(content);
},
formatItalic()
{
content = this.textComponent.value;
content = content.substring(0, this.startPos) + '_' + this.selectedText + '_' + content.substring(this.endPos, content.length);
this.$parent.updatemarkdown(content);
},
formatCode()
{
content = this.textComponent.value;
content = content.substring(0, this.startPos) + '`' + this.selectedText + '`' + content.substring(this.endPos, content.length);
this.$parent.updatemarkdown(content);
}
}
})
const titleComponent = Vue.component('title-component', {
props: ['compmarkdown', 'disabled'],
template: '<div><input type="text" ref="markdown" :value="compmarkdown" :disabled="disabled" @input="updatemarkdown"></div>',
@@ -411,16 +528,18 @@ const markdownComponent = Vue.component('markdown-component', {
props: ['compmarkdown', 'disabled'],
template: '<div>' +
'<div class="contenttype"><i class="icon-paragraph"></i></div>' +
'<textarea class="mdcontent" ref="markdown" :value="compmarkdown" :disabled="disabled" @input="updatemarkdown"></textarea>' +
'</div>',
'<inline-formats>' +
'<textarea id="activeEdit" class="mdcontent" ref="markdown" :value="compmarkdown" :disabled="disabled" @input="updatemarkdown(event.target.value)"></textarea>' +
'</inline-formats>' +
'</div>',
mounted: function(){
this.$refs.markdown.focus();
autosize(document.querySelectorAll('textarea'));
},
methods: {
updatemarkdown: function(event)
updatemarkdown: function(value)
{
this.$emit('updatedMarkdown', event.target.value);
this.$emit('updatedMarkdown', value);
},
},
})
@@ -509,8 +628,10 @@ const quoteComponent = Vue.component('quote-component', {
template: '<div>' +
'<input type="hidden" ref="markdown" :value="compmarkdown" :disabled="disabled" @input="updatemarkdown" />' +
'<div class="contenttype"><i class="icon-quote-left"></i></div>' +
'<textarea class="mdcontent" ref="markdown" v-model="quote" :disabled="disabled" @input="createmarkdown"></textarea>' +
'</div>',
'<inline-formats>' +
'<textarea class="mdcontent" ref="markdown" v-model="quote" :disabled="disabled" @input="updatemarkdown(event.target.value)"></textarea>' +
'</inline-formats>' +
'</div>',
data: function(){
return {
quote: ''
@@ -521,7 +642,7 @@ const quoteComponent = Vue.component('quote-component', {
if(this.compmarkdown)
{
var quote = this.compmarkdown.replace("> ", "");
quote = this.compmarkdown.replace(">", "");
quote = this.compmarkdown.replace(">", "").trim();
this.quote = quote;
}
this.$nextTick(function () {
@@ -529,14 +650,10 @@ const quoteComponent = Vue.component('quote-component', {
});
},
methods: {
createmarkdown: function(event)
{
this.quote = event.target.value;
var quote = '> ' + event.target.value;
this.updatemarkdown(quote);
},
updatemarkdown: function(quote)
updatemarkdown: function(value)
{
this.quote = value;
var quote = '> ' + value;
this.$emit('updatedMarkdown', quote);
},
},
@@ -546,7 +663,9 @@ const ulistComponent = Vue.component('ulist-component', {
props: ['compmarkdown', 'disabled'],
template: '<div>' +
'<div class="contenttype"><i class="icon-list-bullet"></i></div>' +
'<textarea class="mdcontent" ref="markdown" v-model="compmarkdown" :disabled="disabled" @input="updatemarkdown"></textarea>' +
'<inline-formats>' +
'<textarea class="mdcontent" ref="markdown" v-model="compmarkdown" :disabled="disabled" @input="updatemarkdown(event.target.value)"></textarea>' +
'</inline-formats>' +
'</div>',
mounted: function(){
this.$refs.markdown.focus();
@@ -581,9 +700,9 @@ const ulistComponent = Vue.component('ulist-component', {
});
},
methods: {
updatemarkdown: function(event)
updatemarkdown: function(value)
{
this.$emit('updatedMarkdown', event.target.value);
this.$emit('updatedMarkdown', value);
},
},
})
@@ -592,7 +711,9 @@ const olistComponent = Vue.component('olist-component', {
props: ['compmarkdown', 'disabled'],
template: '<div>' +
'<div class="contenttype"><i class="icon-list-numbered"></i></div>' +
'<textarea class="mdcontent" ref="markdown" v-model="compmarkdown" :disabled="disabled" @input="updatemarkdown"></textarea>' +
'<inline-formats>' +
'<textarea class="mdcontent" ref="markdown" v-model="compmarkdown" :disabled="disabled" @input="updatemarkdown(event.target.value)"></textarea>' +
'</inline-formats>' +
'</div>',
mounted: function(){
this.$refs.markdown.focus();
@@ -605,9 +726,9 @@ const olistComponent = Vue.component('olist-component', {
});
},
methods: {
updatemarkdown: function(event)
updatemarkdown: function(value)
{
this.$emit('updatedMarkdown', event.target.value);
this.$emit('updatedMarkdown', value);
},
},
})
@@ -911,7 +1032,7 @@ const definitionComponent = Vue.component('definition-component', {
template: '<div class="definitionList">' +
'<div class="contenttype"><i class="icon-colon"></i></div>' +
'<draggable v-model="definitionList" :animation="150" @end="moveDefinition">' +
'<div class="definitionRow" v-for="(definition, dindex) in definitionList" :key="definition.id">' +
'<div class="definitionRow" v-for="(definition, dindex) in definitionList" :key="definition.id">' +
'<i class="icon-resize-vertical"></i>' +
'<input type="text" class="definitionTerm" placeholder="term" :value="definition.term" :disabled="disabled" @input="updateterm($event,dindex)" @blur="updateMarkdown">' +
'<i class="icon-colon"></i>' +
@@ -1337,6 +1458,7 @@ let editor = new Vue({
'table-component': tableComponent,
'definition-component': definitionComponent,
'math-component': mathComponent,
'inline-formats' : inlineFormatsComponent,
},
data: {
root: document.getElementById("main").dataset.url,
@@ -1580,7 +1702,7 @@ let editor = new Vue({
renderMathInElement(document.getElementById("blox-"+elementid));
});
}
if (typeof MathJax !== false) {
if (typeof MathJax !== 'undefined') {
self.$nextTick(function () {
MathJax.Hub.Queue(["Typeset",MathJax.Hub,"blox-"+elementid]);
});

View File

@@ -203,24 +203,24 @@ let publishController = new Vue({
self.publishResult = "fail";
self.errors.message = "You are probably logged out. Please backup your changes, login and then try again."
}
else if(httpStatus != 200)
{
self.publishDisabled = false;
self.publishResult = "fail";
self.errors.message = "Something went wrong, please refresh the page and try again."
}
else if(response)
{
var result = JSON.parse(response);
self.modalWindow = false;
if(httpStatus != 200)
{
self.publishDisabled = false;
self.publishResult = "fail";
self.errors.message = "Something went wrong, please refresh the page and try again.";
}
if(result.errors)
{
self.modalWindow = "modal";
if(result.errors.message){ self.errors.message = result.errors.message };
}
else if(result.url)
{
self.modalWindow = "modal";
window.location.replace(result.url);
}
}

View File

@@ -3,6 +3,8 @@
{% set startpage = old.settings.startpage ? old.settings.startpage : settings.startpage %}
{% set linebreaks = old.settings.linebreaks ? old.settings.linebreaks : settings.linebreaks %}
{% set year = settings.year ? settings.year : "now"|date("Y") %}
{% set mylang = settings.language ? settings.language : locale %}
{% set mycopy = settings.copyright ? settings.copyright : "@" %}
{% block content %}
@@ -36,7 +38,7 @@
<label for="settings[copyright]">Copyright/Licence</label>
<select name="settings[copyright]" id="copyright">
{% for copy in copyright %}
<option value="{{ copy }}"{% if copy == old.settings.copyright %} selected{% endif %}>{{ copy }}</option>
<option value="{{ copy }}"{% if copy == old.settings.copyright or copy == mycopy %} selected{% endif %}>{{ copy }}</option>
{% endfor %}
</select>
{% if errors.settings.copyright %}
@@ -52,7 +54,7 @@
<label for="settings[language]">Language</label>
<select name="settings[language]" id="language">
{% for key,lang in languages %}
<option value="{{ key }}"{% if (key == old.settings.language or key == locale) %} selected{% endif %}>{{ lang }}</option>
<option value="{{ key }}"{% if (key == old.settings.language or key == mylang) %} selected{% endif %}>{{ lang }}</option>
{% endfor %}
</select>
{% if errors.settings.language %}

View File

@@ -14,7 +14,7 @@
{% for themeName, theme in themes %}
<form method="POST" action="{{ path_for('themes.save') }}">
<form method="POST" id="theme-{{ themeName }}" action="{{ path_for('themes.save') }}">
<fieldset class="card{{ errors[themeName] ? ' errors' : '' }}">
@@ -67,10 +67,10 @@
<input type="hidden" name="theme" value="{{ themeName }}">
<div class="medium">
<button type="button" class="theme-button fc-settings{{ (settings.theme == themeName) ? ' active' : '' }}{{ theme.forms.fields|length > 0 ? ' has-settings' : ' no-settings'}}">{{ theme.forms.fields|length > 0 ? 'Settings <span class="button-arrow"></span>' : 'No Settings'}}</button>
<button type="button" id="{{themeName}}-toggle" class="theme-button fc-settings{{ (settings.theme == themeName) ? ' active' : '' }}{{ theme.forms.fields|length > 0 ? ' has-settings' : ' no-settings'}}">{{ theme.forms.fields|length > 0 ? 'Settings <span class="button-arrow"></span>' : 'No Settings'}}</button>
</div>
<div class="medium">
<input type="submit" value="Save Theme" />
<input type="submit" id="{{themeName}}-submit" value="Save Theme" />
</div>
</div>
</div>