Initial commit

This commit is contained in:
isUnknown 2026-02-12 15:22:46 +01:00
commit 65e0da7e11
1397 changed files with 596542 additions and 0 deletions

View file

@ -0,0 +1,101 @@
# SimpleMDE for Kirby <a href="https://www.paypal.me/medienbaecker"><img width="99" src="http://www.medienbaecker.com/beer.png" alt="Buy me a beer" align="right"></a>
This is a textarea with Markdown highlighting using [SimpleMDE](https://github.com/sparksuite/simplemde-markdown-editor).
![Preview](https://user-images.githubusercontent.com/7975568/33235164-07cf8c6c-d233-11e7-979e-58981a306b7b.gif)
## Installation
Put the `kirby-simplemde-master` folder into your `site/plugins` folder and rename it to `simplemde`.
You can then replace your `textarea` fields with `simplemde` like that:
```
text:
label: Text
type: simplemde
```
## Features
Compared to the built-in textarea, this field has some advantages:
- Live Markdown highlighting. Including green Kirbytags.
- Undo/redo via `Ctrl`/`⌘` + `Z`/`Y`.
- No modals for URLs and email addresses as this prevents the buttons from showing in structure fields.
- Automatic link/email detection when selecting text and using the `link` or `email` button.
- Easy to add custom buttons
- Sticky toolbar on the top for better reachability
## Options
### Buttons
By default the following buttons are displayed:
- `h2`
- `h3`
- `bold`
- `italic`
- `unordered-list`
- `ordered-list`
- `link`
- `pagelink`
- `email`
There are also some more built-in buttons:
- `h1`
- `quote`
- `code`
- `horizontal-rule`
You can define what buttons you want to use for any field:
```
text:
label: Text
type: simplemde
buttons:
- h1
- italic
- link
```
And you can also globally define default buttons for any SimpleMDE field on your site by setting the `simplemde.buttons` variable in your config.php (Thank you, [rasteiner](https://github.com/rasteiner)):
```php
c::set('simplemde.buttons', array(
"bold",
"italic",
"link",
"email"
));
```
### Page link
As of version 1.1.2 this field will automatically hide modules and modules container pages with the title `_modules` from the page list. To include them you can add this to your `config.php`:
```
c::set('simplemde.excludeModules', false);
```
### Highlighting
If you don't want to highlight Kirbytags you can add this to your `config.php`:
```
c::set('simplemde.kirbytagHighlighting', false);
```
### Replace core textarea
You can replace the core textarea with this setting in your `config.php`:
```
c::set('simplemde.replaceTextarea', true);
```

View file

@ -0,0 +1,8 @@
{
"name": "simplemde",
"description": "SimpleMDE for Kirby CMS",
"author": "Thomas Günther",
"license": "MIT",
"version": "1.3.18",
"type": "kirby-plugin"
}

View file

@ -0,0 +1,55 @@
<?php
if(!function_exists('panel')) return;
$kirby->set('field', 'simplemde', __DIR__ . DS . 'simplemde');
function search() {
return site()->search(get("phrase"), array(
'minlength' => 1,
'fields' => array(
"title",
"uri"
)))->toArray();
}
panel()->routes[] = array(
'pattern' => array(
'(:any)/simplemde/index.json',
),
'action' => function() {
$search = site()->search(get("phrase"), array(
'minlength' => 1,
'fields' => array(
"title",
"uri"
)))->toArray();
return json_encode($search, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
},
'filter' => 'auth',
'method' => 'POST|GET'
);
panel()->routes[] = array(
'pattern' => array(
'(:any)/simplemde/translation.json',
),
'action' => function() {
if (version_compare(panel()->version(), '2.2', '>=')) {
$lang = panel()->translation()->code();
} else {
$lang = panel()->language();
}
$langDir = __DIR__ . DS . "simplemde" . DS . 'languages' . DS;
if (file_exists($langDir . $lang . '.php')) {
$translation = include $langDir . $lang . '.php';
}
else {
$translation = include $langDir . 'en.php';
}
return json_encode($translation, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
},
'filter' => 'auth',
'method' => 'POST|GET'
);

View file

@ -0,0 +1,132 @@
.field-with-simplemde .field-content {
border: 2px solid #ddd; }
.field-with-simplemde .field-content:focus-within {
border-color: #8dae28; }
.field-with-simplemde.field-is-readonly .field-content:focus-within {
border-color: #ddd; }
.field-with-simplemde textarea {
border: none;
min-height: 300px; }
.field-with-simplemde .editor-toolbar {
padding: 0;
text-align: center;
border: none;
border-bottom: 1px solid #EFEFEF;
background: white;
opacity: 1;
border-radius: 0;
position: static;
position: -webkit-sticky;
position: sticky;
top: 0px;
z-index: 10; }
.field-with-simplemde .editor-toolbar .editor-toolbar-inner {
position: relative;
white-space: nowrap;
max-width: calc(100vw - 3em);
overflow: auto; }
.field-with-simplemde .editor-toolbar:before, .field-with-simplemde .editor-toolbar:after {
display: none; }
.field-with-simplemde .editor-toolbar a {
color: #777777 !important;
border: none;
border-radius: 0;
font-size: 16px;
width: 45px;
height: 45px; }
.field-with-simplemde .editor-toolbar a:hover, .field-with-simplemde .editor-toolbar a.active {
background: none;
border: none;
color: black !important; }
.field-with-simplemde .editor-toolbar a:focus {
outline: none; }
.field-with-simplemde .editor-toolbar a:before {
line-height: 45px; }
.field-with-simplemde .editor-toolbar a.fa-header-1:after, .field-with-simplemde .editor-toolbar a.fa-header-2:after, .field-with-simplemde .editor-toolbar a.fa-header-3:after {
font-size: 13px;
top: 1px; }
.field-with-simplemde .editor-toolbar input.pagesearch {
height: 45px;
width: 100%;
border: none;
font: inherit;
box-sizing: border-box;
padding: 10px 14px; }
.field-with-simplemde .editor-toolbar input.pagesearch:focus {
outline: none; }
.field-with-simplemde .editor-toolbar .easy-autocomplete-container {
position: absolute;
width: 100%;
max-height: 600%;
overflow: auto;
z-index: 10;
text-align: left;
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.2); }
.field-with-simplemde .editor-toolbar .easy-autocomplete-container ul {
list-style: none; }
.field-with-simplemde .editor-toolbar .easy-autocomplete-container ul li {
background: white;
display: block;
line-height: 1; }
.field-with-simplemde .editor-toolbar .easy-autocomplete-container ul li:hover, .field-with-simplemde .editor-toolbar .easy-autocomplete-container ul li.selected {
cursor: pointer;
background: #f7f7f7; }
.field-with-simplemde .editor-toolbar .easy-autocomplete-container ul li.no-results {
height: 45px;
line-height: 45px;
padding: 0px 14px;
color: #777;
border-top: 1px solid #EFEFEF; }
.field-with-simplemde .editor-toolbar .easy-autocomplete-container ul li.no-results:hover {
background: white;
cursor: auto; }
.field-with-simplemde .editor-toolbar .easy-autocomplete-container ul li .eac-item {
border-top: 1px solid #EFEFEF;
padding: 6px 14px; }
.field-with-simplemde .editor-toolbar .easy-autocomplete-container ul li .title {
display: block; }
.field-with-simplemde .editor-toolbar .easy-autocomplete-container ul li .uri {
display: block;
color: #777;
font-size: 12px;
padding-top: 4px; }
.field-with-simplemde .editor-toolbar.pagelink-open .editor-toolbar-inner {
overflow: visible; }
.field-with-simplemde .editor-toolbar.pagelink-open a {
display: none; }
.field-with-simplemde .editor-toolbar.pagelink-open a.fa-file {
display: block;
position: absolute;
right: 0;
z-index: 2;
border-left: 1px dashed #ddd; }
.field-with-simplemde .editor-toolbar.pagelink-open a.fa-file:before {
color: black; }
.field-with-simplemde.tabs-helper .editor-toolbar {
top: 63px; }
.field-with-simplemde .CodeMirror {
border-radius: 0;
border: none;
line-height: 1.5; }
.field-with-simplemde .CodeMirror, .field-with-simplemde .CodeMirror .CodeMirror-scroll {
min-height: 130px; }
.field-with-simplemde .CodeMirror.CodeMirror-over {
border-color: black; }
.field-with-simplemde .CodeMirror .cm-formatting {
opacity: .5; }
.field-with-simplemde.kirbytag-highlighting .cm-kirbytag {
background: rgba(141, 174, 40, 0.2);
border-radius: 2px; }
@media screen and (max-height: 700px), screen and (max-width: 50em) {
.field-with-simplemde .editor-toolbar {
position: relative;
top: auto; }
.field-with-simplemde.tabs-helper .editor-toolbar {
top: auto; } }

View file

@ -0,0 +1,194 @@
.field-with-simplemde {
.field-content {
border: 2px solid #ddd;
&:focus-within {
border-color: #8dae28;
}
}
&.field-is-readonly {
.field-content:focus-within {
border-color: #ddd;
}
}
textarea {
border: none;
min-height: 300px;
}
.editor-toolbar {
padding: 0;
text-align: center;
border: none;
border-bottom: 1px solid #EFEFEF;
background: white;
opacity: 1;
border-radius: 0;
position: static;
position: -webkit-sticky;
position: sticky;
top: 0px;
z-index: 10;
.editor-toolbar-inner {
position: relative;
white-space: nowrap;
max-width: calc(100vw - 3em);
overflow: auto;
}
&:before, &:after {
display: none;
}
a {
color: rgb(119, 119, 119) !important;
border: none;
border-radius: 0;
font-size: 16px;
width: 45px;
height: 45px;
&:hover , &.active{
background: none;
border: none;
color: black !important;
}
&:focus {
outline: none;
}
&:before {
line-height: 45px;
}
&.fa-header-1, &.fa-header-2, &.fa-header-3 {
&:after {
font-size: 13px;
top: 1px;
}
}
}
.easy-autocomplete {
}
input.pagesearch {
height: 45px;
width: 100%;
border: none;
font: inherit;
box-sizing: border-box;
padding: 10px 14px;
&:focus {
outline: none;
}
}
.easy-autocomplete-container {
position: absolute;
width: 100%;
max-height: 600%;
overflow: auto;
z-index: 10;
text-align: left;
box-shadow: 0px 2px 3px rgba(0,0,0,0.2);
ul {
list-style: none;
li {
background: white;
display: block;
line-height: 1;
&:hover, &.selected {
cursor: pointer;
background: #f7f7f7;
}
&.no-results {
height: 45px;
line-height: 45px;
padding: 0px 14px;
color: #777;
border-top: 1px solid #EFEFEF;
&:hover {
background: white;
cursor: auto;
}
}
.eac-item {
border-top: 1px solid #EFEFEF;
padding: 6px 14px;
}
.title {
display: block;
}
.uri {
display: block;
color: #777;
font-size: 12px;
padding-top: 4px;
}
}
}
}
&.pagelink-open {
.editor-toolbar-inner {
overflow: visible;
}
a {
display: none;
&.fa-file {
display: block;
position: absolute;
right: 0;
z-index: 2;
border-left: 1px dashed #ddd;
&:before {
color: black;
}
}
}
}
}
&.tabs-helper .editor-toolbar {
top: 63px;
}
.CodeMirror {
border-radius: 0;
border: none;
line-height: 1.5;
&, .CodeMirror-scroll {
min-height: 130px;
}
&.CodeMirror-over {
border-color: black;
}
.cm-formatting {
opacity: .5;
}
}
&.kirbytag-highlighting {
.cm-kirbytag {
background: rgba(#8dae28,.2);
border-radius: 2px;
}
}
}
@media screen and (max-height: 700px) , screen and (max-width: 50em) {
.field-with-simplemde {
.editor-toolbar {
position: relative;
top: auto;
}
&.tabs-helper .editor-toolbar {
top: auto;
}
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,333 @@
(function($) {
$.fn.simplemde = function() {
return this.each(function() {
var simplemde = $(this);
// Abort mission when field is readonly
if (simplemde.attr("readonly")) {
simplemde.autosize();
return;
}
var field = simplemde.closest(".field");
var indexUrl = simplemde.data("json") + '/index.json';
var translationUrl = simplemde.data("json") + '/translation.json';
if(field.data('editor')) {
return $(this);
}
// Translation
$.ajax({
url: translationUrl,
dataType: 'json',
success: function(translation) {
// Buttons
$(".field-with-simplemde").find(".editor-toolbar-inner a").each(function() {
var title = $(this).attr("title").replace(/[{}]/g, "");
if (translation[title]) {
$(this).attr("title", translation[title]);
}
});
// Pagelink
$(".field-with-simplemde").find(".editor-toolbar").data("pagelink-placeholder", translation["pagelink.placeholder"] + "...");
$(".field-with-simplemde").find(".editor-toolbar").data("no-results", translation["pagelink.no-results"]);
}
});
var buttons = [
"heading-2",
"heading-3",
"bold",
"italic",
"unordered-list",
"ordered-list",
"link",
"pagelink",
"email"
];
if (simplemde.data("buttons")) {
var setButtons = simplemde.data("buttons");
if (setButtons == "no") {
buttons = [];
}
else {
setButtons = setButtons.replace("h1", "heading-1");
setButtons = setButtons.replace("h2", "heading-2");
setButtons = setButtons.replace("h3", "heading-3");
buttons = setButtons.split(" ");
}
}
var toolbarItems = [
{
name: "heading-1",
action: SimpleMDE.toggleHeading1,
className: "fa fa-header fa-header-x fa-header-1",
title: "{{button.h1}}",
},
{
name: "heading-2",
action: SimpleMDE.toggleHeading2,
className: "fa fa-header fa-header-x fa-header-2",
title: "{{button.h2}}",
},
{
name: "heading-3",
action: SimpleMDE.toggleHeading3,
className: "fa fa-header fa-header-x fa-header-3",
title: "{{button.h3}}",
},
{
name: "bold",
action: SimpleMDE.toggleBold,
className: "fa fa-bold",
title: "{{button.bold}}",
},
{
name: "italic",
action: SimpleMDE.toggleItalic,
className: "fa fa-italic",
title: "{{button.italic}}",
},
{
name: "unordered-list",
action: SimpleMDE.toggleUnorderedList,
className: "fa fa-list-ul",
title: "{{button.unordered-list}}",
},
{
name: "ordered-list",
action: SimpleMDE.toggleOrderedList,
className: "fa fa-list-ol",
title: "{{button.ordered-list}}",
},
{
name: "quote",
action: SimpleMDE.toggleBlockquote,
className: "fa fa-quote-left",
title: "{{button.quote}}",
},
{
name: "code",
action: SimpleMDE.toggleCodeBlock,
className: "fa fa-code",
title: "{{button.code}}",
},
{
name: "horizontal-rule",
action: SimpleMDE.drawHorizontalRule,
className: "fa fa-minus",
title: "{{button.horizontal-rule}}",
},
{
name: "link",
action: function linkFunction(){
var cm = simplemde.codemirror;
var selection = cm.getSelection();
var text = '';
var link = '';
if (selection.match(/^https?:\/\//)) {
link = selection;
} else {
text = selection;
}
var replacement = '(link: ' + link + ' text: ' + text + ')';
cm.replaceSelection(replacement);
var cursorPos = cm.getCursor();
if (link) {
cm.setCursor(cursorPos.line, cursorPos.ch - 1);
} else {
cm.setCursor(cursorPos.line, cursorPos.ch - (replacement.length - 7));
}
cm.focus();
},
className: "fa fa-link",
title: "{{button.link}}",
},
{
name: "pagelink",
action: function pagelinkFunction() {
field.find(".editor-toolbar").toggleClass("pagelink-open");
if (field.find(".pagesearch").length) {
field.find(".pagesearch").remove();
return;
}
else {
var input = $('<input type="text" class="pagesearch" placeholder="' + field.find(".editor-toolbar").data("pagelink-placeholder") + '">');
field.find(".editor-toolbar").append(input);
}
var index = {
url: function(phrase) {
return indexUrl + "?phrase=" + phrase;
},
getValue: "title",
template: {
type: "custom",
method: function(value, item) {
return '<span class="title">' + value + '</span>' +
'<span class="uri"> (' + item.uri + ')</span>';
}
},
list: {
maxNumberOfElements: 100,
onChooseEvent: function() {
var title = input.getSelectedItemData().title;
var uri = input.getSelectedItemData().uri
var cm = simplemde.codemirror;
var selection = cm.getSelection();
if (selection) {
var replacement = '(link: ' + uri + ' text: ' + selection + ')';
}
else {
var replacement = '(link: ' + uri + ' text: ' + title + ')';
}
cm.replaceSelection(replacement);
var cursorPos = cm.getCursor();
cm.focus();
field.find(".editor-toolbar").removeClass("pagelink-open");
field.find(".editor-toolbar .easy-autocomplete").remove();
},
onHideListEvent: function() {
var containerList = field.find(".easy-autocomplete-container ul");
if ($(containerList).children('li').length <= 0) {
$(containerList).html('<li class="no-results">' + field.find(".editor-toolbar").data("no-results") + '</li>').show();
}
}
}
};
input.easyAutocomplete(index);
input.focus();
input.on('keyup',function(evt) {
if (evt.keyCode == 27) {
field.find(".editor-toolbar").removeClass("pagelink-open");
field.find(".pagesearch").remove();
simplemde.codemirror.focus();
}
});
},
className: "fa fa-file",
title: "{{button.page}}",
},
{
name: "email",
action: function emailFunction(){
var cm = simplemde.codemirror;
var selection = cm.getSelection();
var text = '';
var email = '';
if (selection) {
if (selection.match("@")) {
email = selection;
} else {
text = selection;
}
var replacement = '(email: ' + email + ' text: ' + text + ')';
}
else {
var replacement = '(email: )';
}
cm.replaceSelection(replacement);
var cursorPos = cm.getCursor();
if (email) {
cm.setCursor(cursorPos.line, cursorPos.ch - 1);
} else {
cm.setCursor(cursorPos.line, cursorPos.ch - (replacement.length - 8));
}
cm.focus();
},
className: "fa fa-envelope",
title: "{{button.email}}",
}
];
toolbarItems = toolbarItems.filter(function(item) {
for (var i2 = 0; i2 < buttons.length; i2++) {
if (buttons[i2] == item.name) {
return true;
}
}
});
var simplemde = new SimpleMDE({
element: $(this)[0],
spellChecker: false,
status: false,
parsingConfig: {
allowAtxHeaderWithoutSpace: true
},
forceSync: true,
toolbar: toolbarItems,
});
// Drag and Drop
field.find('.CodeMirror').droppable({
hoverClass: 'CodeMirror-over',
accept: $('.sidebar .draggable'),
drop: function(e, ui) {
var editor = simplemde.codemirror;
var selection = editor.getSelection();
if(selection.length>0){
editor.replaceSelection(ui.draggable.data('text'));
}
else{
var doc = editor.getDoc();
var cursor = doc.getCursor();
var pos = {
line: cursor.line,
ch: cursor.ch
}
doc.replaceRange(ui.draggable.data('text'), pos);
}
}
});
// Keep changes
simplemde.codemirror.on("change", function() {
// Validation
var counter = field.find(".field-counter");
var textarea = counter.parent('.field').find('.input');
var length = $.trim(textarea.val()).length;
var max = textarea.data('max');
var min = textarea.data('min');
length = $.trim(textarea.val()).length;
counter.text(length + (max ? '/' + max : ''));
if((max && length > max) || (min && length < min)) {
counter.addClass('outside-range');
} else {
counter.removeClass('outside-range');
}
field.closest('form').trigger('keep');
});
setTimeout(function() {
simplemde.codemirror.refresh();
}, 200);
// Check for tabs plugin
if ($(".tab-placeholder").length || $(".tab-container").length) {
field.addClass("tabs-helper");
}
field.data('editor', true);
});
};
})(jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,23 @@
<?php
return [
// Toolbar buttons
'button.h1' => 'Überschrift 1',
'button.h2' => 'Überschrift 2',
'button.h3' => 'Überschrift 3',
'button.bold' => 'Fett',
'button.italic' => 'Kursiv',
'button.unordered-list' => 'Liste',
'button.ordered-list' => 'Nummerierte Liste',
'button.quote' => 'Zitat',
'button.horizontal-rule' => 'Trennlinie',
'button.link' => 'Link',
'button.page' => 'Seite',
'button.email' => 'E-Mail',
// Pagelink
'pagelink.placeholder' => 'Nach Seite suchen',
'pagelink.no-results' => 'Keine Seiten gefunden',
];

View file

@ -0,0 +1,23 @@
<?php
return [
// Toolbar buttons
'button.h1' => 'Heading 1',
'button.h2' => 'Heading 2',
'button.h3' => 'Heading 3',
'button.bold' => 'Bold',
'button.italic' => 'Italic',
'button.unordered-list' => 'List',
'button.ordered-list' => 'Numbered list',
'button.quote' => 'Quote',
'button.horizontal-rule' => 'Separator line',
'button.link' => 'Link',
'button.page' => 'Page',
'button.email' => 'Email',
// Pagelink
'pagelink.placeholder' => 'Search for page',
'pagelink.no-results' => 'No pages found',
];

View file

@ -0,0 +1,23 @@
<?php
return [
// Toolbar buttons
'button.h1' => 'Titolo 1',
'button.h2' => 'Titolo 2',
'button.h3' => 'Titolo 3',
'button.bold' => 'Grassetto',
'button.italic' => 'Corsivo',
'button.unordered-list' => 'Lista',
'button.ordered-list' => 'Lista numerata',
'button.quote' => 'Citazione',
'button.horizontal-rule' => 'Linea di separazione',
'button.link' => 'Link',
'button.page' => 'Pagina',
'button.email' => 'Email',
// Pagelink
'pagelink.placeholder' => 'Cerca una pagina',
'pagelink.no-results' => 'Nessuna pagina trovata',
];

View file

@ -0,0 +1,23 @@
<?php
return [
// Toolbar buttons
'button.h1' => '标题 H1',
'button.h2' => '标题 H2',
'button.h3' => '标题 H3',
'button.bold' => '粗体',
'button.italic' => '斜体',
'button.unordered-list' => '无序列表',
'button.ordered-list' => '有序列表',
'button.quote' => '引语',
'button.horizontal-rule' => '分割线',
'button.link' => '链接',
'button.page' => '页面',
'button.email' => '邮箱',
// Pagelink
'pagelink.placeholder' => '搜索页面',
'pagelink.no-results' => '找不到页面',
];

View file

@ -0,0 +1,59 @@
<?php
class SimplemdeField extends TextField {
static public $assets = array(
'js' => array(
'simplemde.min.js',
'jquery.easy-autocomplete.min.js',
'editor.js'
),
'css' => array(
'simplemde.min.css',
'editor.css'
)
);
public function input() {
$input = parent::input();
$input->tag('textarea');
$input->data('field', 'simplemde');
$input->data('json', preg_split( '/(\/options|\/pages)/', purl($this->model) )[0] . "/plugins/simplemde" );
$input->removeAttr('value');
$input->html($this->value() ? htmlentities($this->value(), ENT_NOQUOTES, 'UTF-8') : false);
if (isset($this->buttons)) {
if (is_array($this->buttons)) {
$input->data('buttons', $this->buttons);
}
else {
$input->data('buttons', "no");
}
}
else {
$input->data('buttons', c::get('simplemde.buttons', false));
}
return $input;
}
public function element() {
$element = parent::element();
$element->addClass('field-with-simplemde');
if (c::get('simplemde.kirbytagHighlighting', true)) {
$element->addClass('kirbytag-highlighting');
}
return $element;
}
}
if (c::get('simplemde.replaceTextarea', false)) {
class TextareaField extends SimplemdeField {
}
}