diff --git a/plugins/blurbbox/blurbbox.php b/plugins/blurbbox/blurbbox.php new file mode 100644 index 0000000..0e068df --- /dev/null +++ b/plugins/blurbbox/blurbbox.php @@ -0,0 +1,26 @@ + 'onPageReady', + ); + } + + public function onPageReady($page) + { + $pageData = $page->getData($page); + + $pageData['widgets']['blurbbox'] = '

Notes on (mostly) daily perceptions and experiences by Joseph Zitt, an American immigrant to Israel.

' . + '

Buy the book.

' . + '

Subscribe to the weekly newsletter.

'; + + $page->setData($pageData); + } +} \ No newline at end of file diff --git a/plugins/blurbbox/blurbbox.yaml b/plugins/blurbbox/blurbbox.yaml new file mode 100644 index 0000000..0e4ae08 --- /dev/null +++ b/plugins/blurbbox/blurbbox.yaml @@ -0,0 +1,8 @@ +name: Blurbbox +version: 1.0.0 +description: Add a search to your website with lunr.js. +author: Trendschau +homepage: https://typemill.net +licence: MIT +paypal: https://paypal.me/typemill +amount: 10 \ No newline at end of file diff --git a/plugins/blurbbox/index.php b/plugins/blurbbox/index.php new file mode 100644 index 0000000..74ff7ec --- /dev/null +++ b/plugins/blurbbox/index.php @@ -0,0 +1,87 @@ +getFile('cache', 'index.json'); + if(!$index) + { + $this->createIndex(); + $index = $write->getFile('cache', 'index.json'); + } + + return $this->returnJson($index); + } + + private function createIndex() + { + $write = new WriteCache(); + + # get content structure + $structure = $write->getCache('cache', 'structure.txt'); + + # get data for search-index + $index = $this->getAllContent($structure, $write); + + # store the index file here + $write->writeFile('cache', 'index.json', json_encode($index, JSON_UNESCAPED_SLASHES)); + } + + private function getAllContent($structure, $write, $index = NULL) + { + foreach($structure as $item) + { + if($item->elementType == "folder") + { + if($item->fileType == 'md') + { + $page = $write->getFileWithPath('content' . $item->path . DIRECTORY_SEPARATOR . 'index.md'); + $pageArray = $this->getPageContentArray($page, $item->urlAbs); + $index[$pageArray['url']] = $pageArray; + } + + $index = $this->getAllContent($item->folderContent, $write, $index); + } + else + { + $page = $write->getFileWithPath('content' . $item->path); + $pageArray = $this->getPageContentArray($page, $item->urlAbs); + $index[$pageArray['url']] = $pageArray; + } + } + return $index; + } + + private function getPageContentArray($page, $url) + { + $parts = explode("\n", $page, 2); + + # get the title / headline + $title = trim($parts[0], '# '); + $title = str_replace(["\r\n", "\n", "\r"],' ', $title); + + # get and cleanup the content + $content = $parts[1]; + $content = strip_tags($content); + $content = str_replace(["\r\n", "\n", "\r"],' ', $content); + + $pageContent = [ + 'title' => $title, + 'content' => $content, + 'url' => $url + ]; + + return $pageContent; + } +} \ No newline at end of file diff --git a/plugins/blurbbox/public/lunr-licence.md b/plugins/blurbbox/public/lunr-licence.md new file mode 100644 index 0000000..93bece9 --- /dev/null +++ b/plugins/blurbbox/public/lunr-licence.md @@ -0,0 +1,19 @@ +Copyright (C) 2013 by Oliver Nightingale + +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. \ No newline at end of file diff --git a/plugins/blurbbox/public/lunr.min.js b/plugins/blurbbox/public/lunr.min.js new file mode 100644 index 0000000..34b279d --- /dev/null +++ b/plugins/blurbbox/public/lunr.min.js @@ -0,0 +1,6 @@ +/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.6 + * Copyright (C) 2019 Oliver Nightingale + * @license MIT + */ +!function(){var e=function(t){var r=new e.Builder;return r.pipeline.add(e.trimmer,e.stopWordFilter,e.stemmer),r.searchPipeline.add(e.stemmer),t.call(r,r),r.build()};e.version="2.3.6",e.utils={},e.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),e.utils.asString=function(e){return void 0===e||null===e?"":e.toString()},e.utils.clone=function(e){if(null===e||void 0===e)return e;for(var t=Object.create(null),r=Object.keys(e),i=0;i0){var c=e.utils.clone(r)||{};c.position=[a,l],c.index=s.length,s.push(new e.Token(i.slice(a,o),c))}a=o+1}}return s},e.tokenizer.separator=/[\s\-]+/,e.Pipeline=function(){this._stack=[]},e.Pipeline.registeredFunctions=Object.create(null),e.Pipeline.registerFunction=function(t,r){r in this.registeredFunctions&&e.utils.warn("Overwriting existing registered function: "+r),t.label=r,e.Pipeline.registeredFunctions[t.label]=t},e.Pipeline.warnIfFunctionNotRegistered=function(t){var r=t.label&&t.label in this.registeredFunctions;r||e.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",t)},e.Pipeline.load=function(t){var r=new e.Pipeline;return t.forEach(function(t){var i=e.Pipeline.registeredFunctions[t];if(!i)throw new Error("Cannot load unregistered function: "+t);r.add(i)}),r},e.Pipeline.prototype.add=function(){var t=Array.prototype.slice.call(arguments);t.forEach(function(t){e.Pipeline.warnIfFunctionNotRegistered(t),this._stack.push(t)},this)},e.Pipeline.prototype.after=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(i==-1)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,r)},e.Pipeline.prototype.before=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(i==-1)throw new Error("Cannot find existingFn");this._stack.splice(i,0,r)},e.Pipeline.prototype.remove=function(e){var t=this._stack.indexOf(e);t!=-1&&this._stack.splice(t,1)},e.Pipeline.prototype.run=function(e){for(var t=this._stack.length,r=0;r1&&(se&&(r=n),s!=e);)i=r-t,n=t+Math.floor(i/2),s=this.elements[2*n];return s==e?2*n:s>e?2*n:sa?l+=2:o==a&&(t+=r[u+1]*i[l+1],u+=2,l+=2);return t},e.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},e.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),t=1,r=0;t0){var o,a=s.str.charAt(0);a in s.node.edges?o=s.node.edges[a]:(o=new e.TokenSet,s.node.edges[a]=o),1==s.str.length&&(o["final"]=!0),n.push({node:o,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(0!=s.editsRemaining){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new e.TokenSet;s.node.edges["*"]=u}if(0==s.str.length&&(u["final"]=!0),n.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&n.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),1==s.str.length&&(s.node["final"]=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new e.TokenSet;s.node.edges["*"]=l}1==s.str.length&&(l["final"]=!0),n.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var c,h=s.str.charAt(0),d=s.str.charAt(1);d in s.node.edges?c=s.node.edges[d]:(c=new e.TokenSet,s.node.edges[d]=c),1==s.str.length&&(c["final"]=!0),n.push({node:c,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return i},e.TokenSet.fromString=function(t){for(var r=new e.TokenSet,i=r,n=0,s=t.length;n=e;t--){var r=this.uncheckedNodes[t],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r["char"]]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}},e.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},e.Index.prototype.search=function(t){return this.query(function(r){var i=new e.QueryParser(t,r);i.parse()})},e.Index.prototype.query=function(t){for(var r=new e.Query(this.fields),i=Object.create(null),n=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},e.Builder.prototype.k1=function(e){this._k1=e},e.Builder.prototype.add=function(t,r){var i=t[this._ref],n=Object.keys(this._fields);this._documents[i]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return e.QueryLexer.EOS;var t=this.str.charAt(this.pos);return this.pos+=1,t},e.QueryLexer.prototype.width=function(){return this.pos-this.start},e.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},e.QueryLexer.prototype.backup=function(){this.pos-=1},e.QueryLexer.prototype.acceptDigitRun=function(){var t,r;do t=this.next(),r=t.charCodeAt(0);while(r>47&&r<58);t!=e.QueryLexer.EOS&&this.backup()},e.QueryLexer.prototype.more=function(){return this.pos1&&(t.backup(),t.emit(e.QueryLexer.TERM)),t.ignore(),t.more())return e.QueryLexer.lexText},e.QueryLexer.lexEditDistance=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.EDIT_DISTANCE),e.QueryLexer.lexText},e.QueryLexer.lexBoost=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.BOOST),e.QueryLexer.lexText},e.QueryLexer.lexEOS=function(t){t.width()>0&&t.emit(e.QueryLexer.TERM)},e.QueryLexer.termSeparator=e.tokenizer.separator,e.QueryLexer.lexText=function(t){for(;;){var r=t.next();if(r==e.QueryLexer.EOS)return e.QueryLexer.lexEOS;if(92!=r.charCodeAt(0)){if(":"==r)return e.QueryLexer.lexField;if("~"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexEditDistance;if("^"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexBoost;if("+"==r&&1===t.width())return t.emit(e.QueryLexer.PRESENCE),e.QueryLexer.lexText;if("-"==r&&1===t.width())return t.emit(e.QueryLexer.PRESENCE),e.QueryLexer.lexText;if(r.match(e.QueryLexer.termSeparator))return e.QueryLexer.lexTerm}else t.escapeCharacter()}},e.QueryParser=function(t,r){this.lexer=new e.QueryLexer(t),this.query=r,this.currentClause={},this.lexemeIdx=0},e.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var t=e.QueryParser.parseClause;t;)t=t(this);return this.query},e.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},e.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},e.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},e.QueryParser.parseClause=function(t){var r=t.peekLexeme();if(void 0!=r)switch(r.type){case e.QueryLexer.PRESENCE:return e.QueryParser.parsePresence;case e.QueryLexer.FIELD:return e.QueryParser.parseField;case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var i="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(i+=" with value '"+r.str+"'"),new e.QueryParseError(i,r.start,r.end)}},e.QueryParser.parsePresence=function(t){var r=t.consumeLexeme();if(void 0!=r){switch(r.str){case"-":t.currentClause.presence=e.Query.presence.PROHIBITED;break;case"+":t.currentClause.presence=e.Query.presence.REQUIRED;break;default:var i="unrecognised presence operator'"+r.str+"'";throw new e.QueryParseError(i,r.start,r.end)}var n=t.peekLexeme();if(void 0==n){var i="expecting term or field, found nothing";throw new e.QueryParseError(i,r.start,r.end)}switch(n.type){case e.QueryLexer.FIELD:return e.QueryParser.parseField;case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var i="expecting term or field, found '"+n.type+"'";throw new e.QueryParseError(i,n.start,n.end)}}},e.QueryParser.parseField=function(t){var r=t.consumeLexeme();if(void 0!=r){if(t.query.allFields.indexOf(r.str)==-1){var i=t.query.allFields.map(function(e){return"'"+e+"'"}).join(", "),n="unrecognised field '"+r.str+"', possible fields: "+i;throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.fields=[r.str];var s=t.peekLexeme();if(void 0==s){var n="expecting term, found nothing";throw new e.QueryParseError(n,r.start,r.end)}switch(s.type){case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var n="expecting term, found '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseTerm=function(t){var r=t.consumeLexeme();if(void 0!=r){t.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(t.currentClause.usePipeline=!1);var i=t.peekLexeme();if(void 0==i)return void t.nextClause();switch(i.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+i.type+"'";throw new e.QueryParseError(n,i.start,i.end)}}},e.QueryParser.parseEditDistance=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="edit distance must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.editDistance=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseBoost=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="boost must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.boost=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():e.lunr=t()}(this,function(){return e})}(); diff --git a/plugins/blurbbox/public/search.css b/plugins/blurbbox/public/search.css new file mode 100644 index 0000000..4979af4 --- /dev/null +++ b/plugins/blurbbox/public/search.css @@ -0,0 +1,64 @@ +.searchContainer{ + overflow: hidden; + width: 100%; + vertical-align: middle; + white-space: nowrap; +} +.searchContainer input{ + width: 100%; + height: 50px; + border: 1px solid #ddd; + font-size: 1rem; + float: left; + padding-left: 15px; + border-radius: 2px; + box-sizing:border-box; +} +.searchContainer button{ + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border: none; + background: #232833; + height: 50px; + width: 50px; + color: #fff; + font-size: 10pt; + margin-left: -50px; +} +.searchContainer button:hover,.searchContainer button:focus, .searchContainer button:active{ + cursor: pointer; +} + +#searchresult{ +} +.resultwrapper{ +} +button#closeSearchResult{ + position: absolute; + right: 0px; + top: 0px; + margin: 10px; + border: none; + border-radius: 2px; + font-size: 1rem; + color: #fff; + background: #000; + padding: 15px; +} +button#closeSearchResult:hover,#closeSearchResult:focus{ + cursor: pointer; +} +.resultlist{ + margin: 0px; + padding: 0px; + list-style:none; +} +.resultitem{ + +} +.resultheader{ + +} +.resultsnippet{ + +} \ No newline at end of file diff --git a/plugins/blurbbox/public/search.js b/plugins/blurbbox/public/search.js new file mode 100644 index 0000000..639ab18 --- /dev/null +++ b/plugins/blurbbox/public/search.js @@ -0,0 +1,115 @@ +var searchField = document.getElementById("searchField"); +var searchButton = document.getElementById("searchButton"); + +if(searchField && searchButton) +{ + var searchIndex = false; + var documents = false; + var holdcontent = false; + var contentwrapper = false; + + searchField.addEventListener("focus", function(event){ + + if(!searchIndex) + { + myaxios.get('/indexrs51gfe2o2') + .then(function (response) { + + documents = JSON.parse(response.data); + + searchIndex = lunr(function() { + this.ref("id"); + this.field("title", { boost: 10 }); + this.field("content"); + for (var key in documents){ + this.add({ + "id": documents[key].url, + "title": documents[key].title, + "content": documents[key].content + }); + } + }); + + }) + .catch(function (error) {}); + } + }); + + searchButton.addEventListener("click", function(event){ + event.preventDefault(); + + var term = document.getElementById('searchField').value; + var results = searchIndex.search(term); + + var resultPages = results.map(function (match) { + return documents[match.ref]; + }); + + resultsString = "

Result for " + term + "

"; + resultsString += ""; + resultsString += "
    "; + resultPages.forEach(function (r) { + resultsString += "
  • "; + resultsString += "

    " + r.title + "

    "; + resultsString += "
    " + r.content.substring(0, 200) + " ...
    "; + resultsString += "
  • " + }); + resultsString += "
"; + + if(!holdcontent) + { + contentwrapper = document.getElementById("searchresult").parentNode; + holdcontent = contentwrapper.innerHTML; + } + + contentwrapper.innerHTML = resultsString; + + document.getElementById("closeSearchResult").addEventListener("click", function(event){ + contentwrapper.innerHTML = holdcontent; + }); + + }, false); +} + +/* +var searchIndex = lunr(function() { + this.ref("id"); + this.field("title", { boost: 10 }); + this.field("content"); + for (var key in window.pages) { + this.add({ + "id": key, + "title": pages[key].title, + "content": pages[key].content + }); + } +}); + +function getQueryVariable(variable) { + var query = window.location.search.substring(1); + var vars = query.split("&"); + for (var i = 0; i < vars.length; i++) { + var pair = vars[i].split("="); + if (pair[0] === variable) { + return decodeURIComponent(pair[1].replace(/\+/g, "%20")); + } + } +} + +var searchTerm = getQueryVariable("q"); +// creation of searchIndex from earlier example +var results = searchIndex.search(searchTerm); +var resultPages = results.map(function (match) { + return pages[match.ref]; +}); + +// resultPages from previous example +resultsString = ""; +resultPages.forEach(function (r) { + resultsString += "
  • "; + resultsString += "

    " + r.title + "

    "; + resultsString += "
    " + r.content.substring(0, 200) + "
    "; + resultsString += "
  • " +}); +document.querySelector("#search-results").innerHTML = resultsString; +*/ \ No newline at end of file diff --git a/plugins/blurbbox/search.zip b/plugins/blurbbox/search.zip new file mode 100644 index 0000000..af82cbd Binary files /dev/null and b/plugins/blurbbox/search.zip differ diff --git a/plugins/search/index.php b/plugins/search/index.php new file mode 100644 index 0000000..74ff7ec --- /dev/null +++ b/plugins/search/index.php @@ -0,0 +1,87 @@ +getFile('cache', 'index.json'); + if(!$index) + { + $this->createIndex(); + $index = $write->getFile('cache', 'index.json'); + } + + return $this->returnJson($index); + } + + private function createIndex() + { + $write = new WriteCache(); + + # get content structure + $structure = $write->getCache('cache', 'structure.txt'); + + # get data for search-index + $index = $this->getAllContent($structure, $write); + + # store the index file here + $write->writeFile('cache', 'index.json', json_encode($index, JSON_UNESCAPED_SLASHES)); + } + + private function getAllContent($structure, $write, $index = NULL) + { + foreach($structure as $item) + { + if($item->elementType == "folder") + { + if($item->fileType == 'md') + { + $page = $write->getFileWithPath('content' . $item->path . DIRECTORY_SEPARATOR . 'index.md'); + $pageArray = $this->getPageContentArray($page, $item->urlAbs); + $index[$pageArray['url']] = $pageArray; + } + + $index = $this->getAllContent($item->folderContent, $write, $index); + } + else + { + $page = $write->getFileWithPath('content' . $item->path); + $pageArray = $this->getPageContentArray($page, $item->urlAbs); + $index[$pageArray['url']] = $pageArray; + } + } + return $index; + } + + private function getPageContentArray($page, $url) + { + $parts = explode("\n", $page, 2); + + # get the title / headline + $title = trim($parts[0], '# '); + $title = str_replace(["\r\n", "\n", "\r"],' ', $title); + + # get and cleanup the content + $content = $parts[1]; + $content = strip_tags($content); + $content = str_replace(["\r\n", "\n", "\r"],' ', $content); + + $pageContent = [ + 'title' => $title, + 'content' => $content, + 'url' => $url + ]; + + return $pageContent; + } +} \ No newline at end of file diff --git a/plugins/search/public/lunr-licence.md b/plugins/search/public/lunr-licence.md new file mode 100644 index 0000000..93bece9 --- /dev/null +++ b/plugins/search/public/lunr-licence.md @@ -0,0 +1,19 @@ +Copyright (C) 2013 by Oliver Nightingale + +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. \ No newline at end of file diff --git a/plugins/search/public/lunr.min.js b/plugins/search/public/lunr.min.js new file mode 100644 index 0000000..34b279d --- /dev/null +++ b/plugins/search/public/lunr.min.js @@ -0,0 +1,6 @@ +/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.6 + * Copyright (C) 2019 Oliver Nightingale + * @license MIT + */ +!function(){var e=function(t){var r=new e.Builder;return r.pipeline.add(e.trimmer,e.stopWordFilter,e.stemmer),r.searchPipeline.add(e.stemmer),t.call(r,r),r.build()};e.version="2.3.6",e.utils={},e.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),e.utils.asString=function(e){return void 0===e||null===e?"":e.toString()},e.utils.clone=function(e){if(null===e||void 0===e)return e;for(var t=Object.create(null),r=Object.keys(e),i=0;i0){var c=e.utils.clone(r)||{};c.position=[a,l],c.index=s.length,s.push(new e.Token(i.slice(a,o),c))}a=o+1}}return s},e.tokenizer.separator=/[\s\-]+/,e.Pipeline=function(){this._stack=[]},e.Pipeline.registeredFunctions=Object.create(null),e.Pipeline.registerFunction=function(t,r){r in this.registeredFunctions&&e.utils.warn("Overwriting existing registered function: "+r),t.label=r,e.Pipeline.registeredFunctions[t.label]=t},e.Pipeline.warnIfFunctionNotRegistered=function(t){var r=t.label&&t.label in this.registeredFunctions;r||e.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",t)},e.Pipeline.load=function(t){var r=new e.Pipeline;return t.forEach(function(t){var i=e.Pipeline.registeredFunctions[t];if(!i)throw new Error("Cannot load unregistered function: "+t);r.add(i)}),r},e.Pipeline.prototype.add=function(){var t=Array.prototype.slice.call(arguments);t.forEach(function(t){e.Pipeline.warnIfFunctionNotRegistered(t),this._stack.push(t)},this)},e.Pipeline.prototype.after=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(i==-1)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,r)},e.Pipeline.prototype.before=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(i==-1)throw new Error("Cannot find existingFn");this._stack.splice(i,0,r)},e.Pipeline.prototype.remove=function(e){var t=this._stack.indexOf(e);t!=-1&&this._stack.splice(t,1)},e.Pipeline.prototype.run=function(e){for(var t=this._stack.length,r=0;r1&&(se&&(r=n),s!=e);)i=r-t,n=t+Math.floor(i/2),s=this.elements[2*n];return s==e?2*n:s>e?2*n:sa?l+=2:o==a&&(t+=r[u+1]*i[l+1],u+=2,l+=2);return t},e.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},e.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),t=1,r=0;t0){var o,a=s.str.charAt(0);a in s.node.edges?o=s.node.edges[a]:(o=new e.TokenSet,s.node.edges[a]=o),1==s.str.length&&(o["final"]=!0),n.push({node:o,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(0!=s.editsRemaining){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new e.TokenSet;s.node.edges["*"]=u}if(0==s.str.length&&(u["final"]=!0),n.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&n.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),1==s.str.length&&(s.node["final"]=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new e.TokenSet;s.node.edges["*"]=l}1==s.str.length&&(l["final"]=!0),n.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var c,h=s.str.charAt(0),d=s.str.charAt(1);d in s.node.edges?c=s.node.edges[d]:(c=new e.TokenSet,s.node.edges[d]=c),1==s.str.length&&(c["final"]=!0),n.push({node:c,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return i},e.TokenSet.fromString=function(t){for(var r=new e.TokenSet,i=r,n=0,s=t.length;n=e;t--){var r=this.uncheckedNodes[t],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r["char"]]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}},e.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},e.Index.prototype.search=function(t){return this.query(function(r){var i=new e.QueryParser(t,r);i.parse()})},e.Index.prototype.query=function(t){for(var r=new e.Query(this.fields),i=Object.create(null),n=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},e.Builder.prototype.k1=function(e){this._k1=e},e.Builder.prototype.add=function(t,r){var i=t[this._ref],n=Object.keys(this._fields);this._documents[i]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return e.QueryLexer.EOS;var t=this.str.charAt(this.pos);return this.pos+=1,t},e.QueryLexer.prototype.width=function(){return this.pos-this.start},e.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},e.QueryLexer.prototype.backup=function(){this.pos-=1},e.QueryLexer.prototype.acceptDigitRun=function(){var t,r;do t=this.next(),r=t.charCodeAt(0);while(r>47&&r<58);t!=e.QueryLexer.EOS&&this.backup()},e.QueryLexer.prototype.more=function(){return this.pos1&&(t.backup(),t.emit(e.QueryLexer.TERM)),t.ignore(),t.more())return e.QueryLexer.lexText},e.QueryLexer.lexEditDistance=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.EDIT_DISTANCE),e.QueryLexer.lexText},e.QueryLexer.lexBoost=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.BOOST),e.QueryLexer.lexText},e.QueryLexer.lexEOS=function(t){t.width()>0&&t.emit(e.QueryLexer.TERM)},e.QueryLexer.termSeparator=e.tokenizer.separator,e.QueryLexer.lexText=function(t){for(;;){var r=t.next();if(r==e.QueryLexer.EOS)return e.QueryLexer.lexEOS;if(92!=r.charCodeAt(0)){if(":"==r)return e.QueryLexer.lexField;if("~"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexEditDistance;if("^"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexBoost;if("+"==r&&1===t.width())return t.emit(e.QueryLexer.PRESENCE),e.QueryLexer.lexText;if("-"==r&&1===t.width())return t.emit(e.QueryLexer.PRESENCE),e.QueryLexer.lexText;if(r.match(e.QueryLexer.termSeparator))return e.QueryLexer.lexTerm}else t.escapeCharacter()}},e.QueryParser=function(t,r){this.lexer=new e.QueryLexer(t),this.query=r,this.currentClause={},this.lexemeIdx=0},e.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var t=e.QueryParser.parseClause;t;)t=t(this);return this.query},e.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},e.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},e.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},e.QueryParser.parseClause=function(t){var r=t.peekLexeme();if(void 0!=r)switch(r.type){case e.QueryLexer.PRESENCE:return e.QueryParser.parsePresence;case e.QueryLexer.FIELD:return e.QueryParser.parseField;case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var i="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(i+=" with value '"+r.str+"'"),new e.QueryParseError(i,r.start,r.end)}},e.QueryParser.parsePresence=function(t){var r=t.consumeLexeme();if(void 0!=r){switch(r.str){case"-":t.currentClause.presence=e.Query.presence.PROHIBITED;break;case"+":t.currentClause.presence=e.Query.presence.REQUIRED;break;default:var i="unrecognised presence operator'"+r.str+"'";throw new e.QueryParseError(i,r.start,r.end)}var n=t.peekLexeme();if(void 0==n){var i="expecting term or field, found nothing";throw new e.QueryParseError(i,r.start,r.end)}switch(n.type){case e.QueryLexer.FIELD:return e.QueryParser.parseField;case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var i="expecting term or field, found '"+n.type+"'";throw new e.QueryParseError(i,n.start,n.end)}}},e.QueryParser.parseField=function(t){var r=t.consumeLexeme();if(void 0!=r){if(t.query.allFields.indexOf(r.str)==-1){var i=t.query.allFields.map(function(e){return"'"+e+"'"}).join(", "),n="unrecognised field '"+r.str+"', possible fields: "+i;throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.fields=[r.str];var s=t.peekLexeme();if(void 0==s){var n="expecting term, found nothing";throw new e.QueryParseError(n,r.start,r.end)}switch(s.type){case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var n="expecting term, found '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseTerm=function(t){var r=t.consumeLexeme();if(void 0!=r){t.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(t.currentClause.usePipeline=!1);var i=t.peekLexeme();if(void 0==i)return void t.nextClause();switch(i.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+i.type+"'";throw new e.QueryParseError(n,i.start,i.end)}}},e.QueryParser.parseEditDistance=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="edit distance must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.editDistance=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseBoost=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="boost must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.boost=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():e.lunr=t()}(this,function(){return e})}(); diff --git a/plugins/search/public/search.css b/plugins/search/public/search.css new file mode 100644 index 0000000..4979af4 --- /dev/null +++ b/plugins/search/public/search.css @@ -0,0 +1,64 @@ +.searchContainer{ + overflow: hidden; + width: 100%; + vertical-align: middle; + white-space: nowrap; +} +.searchContainer input{ + width: 100%; + height: 50px; + border: 1px solid #ddd; + font-size: 1rem; + float: left; + padding-left: 15px; + border-radius: 2px; + box-sizing:border-box; +} +.searchContainer button{ + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border: none; + background: #232833; + height: 50px; + width: 50px; + color: #fff; + font-size: 10pt; + margin-left: -50px; +} +.searchContainer button:hover,.searchContainer button:focus, .searchContainer button:active{ + cursor: pointer; +} + +#searchresult{ +} +.resultwrapper{ +} +button#closeSearchResult{ + position: absolute; + right: 0px; + top: 0px; + margin: 10px; + border: none; + border-radius: 2px; + font-size: 1rem; + color: #fff; + background: #000; + padding: 15px; +} +button#closeSearchResult:hover,#closeSearchResult:focus{ + cursor: pointer; +} +.resultlist{ + margin: 0px; + padding: 0px; + list-style:none; +} +.resultitem{ + +} +.resultheader{ + +} +.resultsnippet{ + +} \ No newline at end of file diff --git a/plugins/search/public/search.js b/plugins/search/public/search.js new file mode 100644 index 0000000..639ab18 --- /dev/null +++ b/plugins/search/public/search.js @@ -0,0 +1,115 @@ +var searchField = document.getElementById("searchField"); +var searchButton = document.getElementById("searchButton"); + +if(searchField && searchButton) +{ + var searchIndex = false; + var documents = false; + var holdcontent = false; + var contentwrapper = false; + + searchField.addEventListener("focus", function(event){ + + if(!searchIndex) + { + myaxios.get('/indexrs51gfe2o2') + .then(function (response) { + + documents = JSON.parse(response.data); + + searchIndex = lunr(function() { + this.ref("id"); + this.field("title", { boost: 10 }); + this.field("content"); + for (var key in documents){ + this.add({ + "id": documents[key].url, + "title": documents[key].title, + "content": documents[key].content + }); + } + }); + + }) + .catch(function (error) {}); + } + }); + + searchButton.addEventListener("click", function(event){ + event.preventDefault(); + + var term = document.getElementById('searchField').value; + var results = searchIndex.search(term); + + var resultPages = results.map(function (match) { + return documents[match.ref]; + }); + + resultsString = "

    Result for " + term + "

    "; + resultsString += ""; + resultsString += "
      "; + resultPages.forEach(function (r) { + resultsString += "
    • "; + resultsString += "

      " + r.title + "

      "; + resultsString += "
      " + r.content.substring(0, 200) + " ...
      "; + resultsString += "
    • " + }); + resultsString += "
    "; + + if(!holdcontent) + { + contentwrapper = document.getElementById("searchresult").parentNode; + holdcontent = contentwrapper.innerHTML; + } + + contentwrapper.innerHTML = resultsString; + + document.getElementById("closeSearchResult").addEventListener("click", function(event){ + contentwrapper.innerHTML = holdcontent; + }); + + }, false); +} + +/* +var searchIndex = lunr(function() { + this.ref("id"); + this.field("title", { boost: 10 }); + this.field("content"); + for (var key in window.pages) { + this.add({ + "id": key, + "title": pages[key].title, + "content": pages[key].content + }); + } +}); + +function getQueryVariable(variable) { + var query = window.location.search.substring(1); + var vars = query.split("&"); + for (var i = 0; i < vars.length; i++) { + var pair = vars[i].split("="); + if (pair[0] === variable) { + return decodeURIComponent(pair[1].replace(/\+/g, "%20")); + } + } +} + +var searchTerm = getQueryVariable("q"); +// creation of searchIndex from earlier example +var results = searchIndex.search(searchTerm); +var resultPages = results.map(function (match) { + return pages[match.ref]; +}); + +// resultPages from previous example +resultsString = ""; +resultPages.forEach(function (r) { + resultsString += "
  • "; + resultsString += "

    " + r.title + "

    "; + resultsString += "
    " + r.content.substring(0, 200) + "
    "; + resultsString += "
  • " +}); +document.querySelector("#search-results").innerHTML = resultsString; +*/ \ No newline at end of file diff --git a/plugins/search/search.php b/plugins/search/search.php new file mode 100644 index 0000000..8e93719 --- /dev/null +++ b/plugins/search/search.php @@ -0,0 +1,128 @@ + 'onsettingsLoaded', + 'onContentArrayLoaded' => 'onContentArrayLoaded', + 'onPageReady' => 'onPageReady', + 'onPagePublished' => 'onPagePublished', + 'onPageUnpublished' => 'onPageUnpublished', + 'onPageSorted' => 'onPageSorted', + 'onPageDeleted' => 'onPageDeleted', + ); + } + + # get search.json with route + # update search.json on publish + + public static function addNewRoutes() + { + # the route for the api calls + return array( + array( + 'httpMethod' => 'get', + 'route' => '/indexrs51gfe2o2', + 'class' => 'Plugins\search\index:index' + ), + ); + } + + public function onSettingsLoaded($settings) + { + $this->settings = $settings->getData(); + } + + # at any of theses events, delete the old search index + public function onPagePublished($item) + { + $this->deleteSearchIndex(); + } + public function onPageUnpublished($item) + { + $this->deleteSearchIndex(); + } + public function onPageSorted($inputParams) + { + $this->deleteSearchIndex(); + } + public function onPageDeleted($item) + { + $this->deleteSearchIndex(); + } + + private function deleteSearchIndex() + { + $write = new Write(); + + # store the index file here + $write->deleteFileWithPath('cache' . DIRECTORY_SEPARATOR . 'index.json'); + } + + public function onContentArrayLoaded($contentArray) + { + # get content array + $content = $contentArray->getData(); + $settings = $this->getPluginSettings('search'); + $salt = "asPx9Derf2"; + + # activate axios and vue in frontend + $this->activateAxios(); + $this->activateVue(); + + # add the css and vue application + $this->addCSS('/search/public/search.css'); + $this->addJS('/search/public/lunr.min.js'); + $this->addJS('/search/public/search.js'); + + # simple security for first request + $secret = time(); + $secret = substr($secret,0,-1); + $secret = md5($secret . $salt); + + # simple csrf protection with a session for long following requests + if (session_status() == PHP_SESSION_NONE) { + session_start(); + } + + $length = 32; + $token = substr(base_convert(sha1(uniqid(mt_rand())), 16, 36), 0, $length); + $_SESSION['search'] = $token; + $_SESSION['search-expire'] = time() + 1300; # 60 seconds * 30 minutes + + # create div for vue app + $search = '
    '; + + # create content type + $search = Array + ( + 'rawHtml' => $search, + 'allowRawHtmlInSafeMode' => true, + 'autobreak' => 1 + ); + + $content[] = $search; + + $contentArray->setData($content); + } + + public function onPageReady($page) + { + $pageData = $page->getData($page); + + $pageData['widgets']['search'] = '
    '. + ''. + ''. + '
    '; + $page->setData($pageData); + } +} \ No newline at end of file diff --git a/plugins/search/search.yaml b/plugins/search/search.yaml new file mode 100644 index 0000000..d47f64f --- /dev/null +++ b/plugins/search/search.yaml @@ -0,0 +1,8 @@ +name: Search +version: 1.0.0 +description: Add a search to your website with lunr.js. +author: Trendschau +homepage: https://typemill.net +licence: MIT +paypal: https://paypal.me/typemill +amount: 10 \ No newline at end of file diff --git a/plugins/search/search.zip b/plugins/search/search.zip new file mode 100644 index 0000000..af82cbd Binary files /dev/null and b/plugins/search/search.zip differ diff --git a/system/Assets.php b/system/Assets.php index bc6265d..ad7e83e 100644 --- a/system/Assets.php +++ b/system/Assets.php @@ -38,7 +38,7 @@ class Assets $resize = '-'; - if(ctype_digit($width) && $width < 10000) + if(is_int($width) && $width < 10000) { $resize .= $width; $desiredSizes['custom']['width'] = $width; @@ -46,7 +46,7 @@ class Assets $resize .= 'x'; - if(ctype_digit($height) && $height < 10000) + if(is_int($height) && $height < 10000) { $resize .= $height; $desiredSizes['custom']['height'] = $height; @@ -125,7 +125,7 @@ class Assets public function src() { - return $this->imageUrl; + return $this->baseUrl . '/' . $this->imageUrl; } public function addCSS($CSS) diff --git a/system/Controllers/ArticleApiController.php b/system/Controllers/ArticleApiController.php index b2fc511..351a9de 100644 --- a/system/Controllers/ArticleApiController.php +++ b/system/Controllers/ArticleApiController.php @@ -977,7 +977,7 @@ class ArticleApiController extends ContentController } # parse markdown-content-array to content-string - $content[$key] = ['id' => $key, 'html' => $parsedown->markup($contentArray, $relurl)]; + $content[$key] = ['id' => $key, 'html' => $parsedown->markup($contentArray)]; } if($toc) diff --git a/system/Controllers/AuthController.php b/system/Controllers/AuthController.php index b908f02..205ecfe 100644 --- a/system/Controllers/AuthController.php +++ b/system/Controllers/AuthController.php @@ -116,6 +116,13 @@ class AuthController extends Controller if($userdata && password_verify($params['password'], $userdata['password'])) { + # check if user has confirmed the account + if(isset($userdata['optintoken']) && $userdata['optintoken']) + { + $this->c->flash->addMessage('error', 'Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.'); + return $response->withRedirect($this->c->router->pathFor('auth.show')); + } + $user->login($userdata['username']); /* clear the user login attemps */ diff --git a/system/Controllers/BlockApiController.php b/system/Controllers/BlockApiController.php index 07d6857..73b3be6 100644 --- a/system/Controllers/BlockApiController.php +++ b/system/Controllers/BlockApiController.php @@ -143,7 +143,7 @@ class BlockApiController extends ContentController else { # parse markdown-content-array to content-string - $blockHTML = $parsedown->markup($blockArray, $relurl); + $blockHTML = $parsedown->markup($blockArray); # if it is a headline if($blockMarkdown[0] == '#') @@ -195,7 +195,7 @@ class BlockApiController extends ContentController } # parse markdown-content-array to content-string - $content[$key] = ['id' => $key, 'html' => $parsedown->markup($contentArray, $relurl)]; + $content[$key] = ['id' => $key, 'html' => $parsedown->markup($contentArray)]; } # if page has a table of content @@ -353,7 +353,7 @@ class BlockApiController extends ContentController else { # parse markdown-content-array to content-string - $blockHTML = $parsedown->markup($blockArray, $relurl); + $blockHTML = $parsedown->markup($blockArray); # if it is a headline if($blockMarkdown[0] == '#') diff --git a/system/Controllers/ContentBackendController.php b/system/Controllers/ContentBackendController.php index 2d8b24a..9a637ea 100644 --- a/system/Controllers/ContentBackendController.php +++ b/system/Controllers/ContentBackendController.php @@ -157,7 +157,7 @@ class ContentBackendController extends ContentController $contentArray = $parsedown->text($block); /* parse markdown-content-array to content-string */ - $content[$key] = $parsedown->markup($contentArray, $relurl); + $content[$key] = $parsedown->markup($contentArray); } # extract title and delete from content array, array will start at 1 after that. diff --git a/system/Controllers/PageController.php b/system/Controllers/PageController.php index 3a58db3..62bd779 100644 --- a/system/Controllers/PageController.php +++ b/system/Controllers/PageController.php @@ -206,15 +206,15 @@ class PageController extends Controller $parsedown->setSafeMode(true); /* parse markdown-file to content-array */ - $contentArray = $parsedown->text($contentMD, $itemUrl); + $contentArray = $parsedown->text($contentMD); $contentArray = $this->c->dispatcher->dispatch('onContentArrayLoaded', new OnContentArrayLoaded($contentArray))->getData(); /* parse markdown-content-array to content-string */ - $contentHTML = $parsedown->markup($contentArray, $itemUrl); + $contentHTML = $parsedown->markup($contentArray); $contentHTML = $this->c->dispatcher->dispatch('onHtmlLoaded', new OnHtmlLoaded($contentHTML))->getData(); /* extract the h1 headline*/ - $contentParts = explode("", $contentHTML); + $contentParts = explode("", $contentHTML, 2); $title = isset($contentParts[0]) ? strip_tags($contentParts[0]) : $settings['title']; $contentHTML = isset($contentParts[1]) ? $contentParts[1] : $contentHTML; @@ -240,9 +240,19 @@ class PageController extends Controller $img_alt = isset($img_alt_result[1]) ? $img_alt_result[1] : false; } } + elseif($logo) + { + $img_url = $logo; + $pathinfo = pathinfo($settings['logo']); + $img_alt = $pathinfo['filename']; + } } - $firstImage = array('img_url' => $base_url . '/' . $img_url, 'img_alt' => $img_alt); + $firstImage = false; + if($img_url) + { + $firstImage = array('img_url' => $base_url . '/' . $img_url, 'img_alt' => $img_alt); + } $route = empty($args) && isset($settings['themes'][$theme]['cover']) ? '/cover.twig' : '/index.twig'; diff --git a/system/Controllers/SettingsController.php b/system/Controllers/SettingsController.php index 422b3d5..0762b6f 100644 --- a/system/Controllers/SettingsController.php +++ b/system/Controllers/SettingsController.php @@ -65,7 +65,7 @@ class SettingsController extends Controller 'locale' => $locale, 'formats' => $defaultSettings['formats'], 'access' => $options, - 'route' => $route->getName() + 'route' => $route->getName() )); } @@ -83,18 +83,24 @@ class SettingsController extends Controller if($newSettings) { - /* make sure only allowed fields are stored */ + # check for image settings + $imgwidth = isset($newSettings['images']['live']['width']) ? $newSettings['images']['live']['width'] : false; + $imgheight = isset($newSettings['images']['live']['height']) ? $newSettings['images']['live']['height'] : false; + + # make sure only allowed fields are stored $newSettings = array( - 'title' => $newSettings['title'], - 'author' => $newSettings['author'], - 'copyright' => $newSettings['copyright'], - 'year' => $newSettings['year'], - 'language' => $newSettings['language'], - 'langattr' => $newSettings['langattr'], - 'editor' => $newSettings['editor'], - 'access' => $newSettings['access'], - 'formats' => $newSettings['formats'], - 'headlineanchors' => isset($newSettings['headlineanchors']) ? $newSettings['headlineanchors'] : null, + 'title' => $newSettings['title'], + 'author' => $newSettings['author'], + 'copyright' => $newSettings['copyright'], + 'year' => $newSettings['year'], + 'language' => $newSettings['language'], + 'langattr' => $newSettings['langattr'], + 'editor' => $newSettings['editor'], + 'access' => $newSettings['access'], + 'formats' => $newSettings['formats'], + 'headlineanchors' => isset($newSettings['headlineanchors']) ? $newSettings['headlineanchors'] : null, + 'displayErrorDetails' => isset($newSettings['displayErrorDetails']) ? true : null, + 'twigcache' => isset($newSettings['twigcache']) ? true : null ); # https://www.slimframework.com/docs/v3/cookbook/uploading-files.html; @@ -102,6 +108,17 @@ class SettingsController extends Controller $copyright = $this->getCopyright(); $validate->settings($newSettings, $copyright, $defaultSettings['formats'], 'settings'); + + # use custom image settings + if( $imgwidth && ctype_digit($imgwidth) && (strlen($imgwidth) < 5) ) + { + $newSettings['images']['live']['width'] = $imgwidth; + } + if( $imgheight && ctype_digit($imgheight) && (strlen($imgheight) < 5) ) + { + $newSettings['images']['live']['height'] = $imgheight; + } + } else { @@ -733,7 +750,9 @@ class SettingsController extends Controller { # set image size $settings = $this->c->get('settings'); - $settings->replace(['images' => ['live' => ['width' => 500, 'height' => 500]]]); + $imageSizes = $settings['images']; + $imageSizes['live'] = ['width' => 500, 'height' => 500]; + $settings->replace(['images' => $imageSizes]); $imageresult = $this->saveImages($imageFields, $userdata, $settings, $images['user']); if(isset($_SESSION['slimFlash']['error'])) @@ -818,6 +837,42 @@ class SettingsController extends Controller } } + public function clearCache($request, $response, $args) + { + $settings = $this->c->get('settings'); + $dir = $settings['basePath'] . 'cache'; + $iterator = new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS); + $files = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST); + + $error = false; + + foreach($files as $file) + { + if ($file->isDir()) + { + if(!rmdir($file->getRealPath())) + { + $error = 'Could not delete some folders.'; + } + } + elseif($file->getExtension() !== 'css') + { + if(!unlink($file->getRealPath()) ) + { + $error = 'Could not delete some files.'; + } + } + } + + if($error) + { + return $response->withJson(['errors' => $error], 500); + } + + return $response->withJson(array('errors' => false)); + + } + private function getUserFields($role) { $fields = []; @@ -996,7 +1051,6 @@ class SettingsController extends Controller protected function saveImages($imageFields, $userInput, $userSettings, $files) { - # initiate image processor with standard image sizes $processImages = new ProcessImage($userSettings['images']); @@ -1029,5 +1083,6 @@ class SettingsController extends Controller } } return $userInput; - } -} + } + +} \ No newline at end of file diff --git a/system/Extensions/ParsedownExtension.php b/system/Extensions/ParsedownExtension.php index fafe83e..e38d6bc 100644 --- a/system/Extensions/ParsedownExtension.php +++ b/system/Extensions/ParsedownExtension.php @@ -6,13 +6,16 @@ use \URLify; class ParsedownExtension extends \ParsedownExtra { - function __construct($showAnchor = NULL) + function __construct($showAnchor = NULL, $skipAbsoluteUrls = NULL) { parent::__construct(); # show anchor next to headline? $this->showAnchor = $showAnchor; + # base url is needed for media/images and relative links (e.g. if www.mydomain.com/mywebsite) + $this->baseUrl = $skipAbsoluteUrls ? '' : TM_BASE_URL; + # math support $this->BlockTypes['\\'][] = 'Math'; $this->BlockTypes['$'][] = 'Math'; @@ -38,18 +41,18 @@ class ParsedownExtension extends \ParsedownExtra public function text($text, $relurl = null) { - $this->relurl = $relurl ? $relurl : ''; + # $this->relurl = $relurl ? $relurl : ''; $Elements = $this->textElements($text); return $Elements; } - public function markup($Elements, $relurl) + public function markup($Elements) { # make relurl available for other functions - $this->relurl = $relurl; +# $this->relurl = $relurl; # convert to markup $markup = $this->elements($Elements); @@ -75,7 +78,6 @@ class ParsedownExtension extends \ParsedownExtra $markup .= "\n" . $this->element($Element); } - return $markup; } @@ -352,7 +354,7 @@ class ParsedownExtension extends \ParsedownExtra 'text' => $text, 'handler' => 'line', 'attributes' => array( - 'id' => "$headline" + 'id' => "h-$headline" ) ) ); @@ -363,8 +365,9 @@ class ParsedownExtension extends \ParsedownExtra array( 'name' => 'a', 'attributes' => array( - 'href' => $this->relurl . "#" . $headline, - 'class' => 'tm-heading-anchor', +# 'href' => $this->relurl . "#" . $headline, + 'href' => "#h-" . $headline, + 'class' => 'tm-heading-anchor', ), 'text' => '#', ), @@ -391,15 +394,16 @@ class ParsedownExtension extends \ParsedownExtra $thisLevel = $headline['level']; $prevLevel = $key > 0 ? $headlines[$key-1]['level'] : 1; $nextLevel = isset($headlines[$key+1]) ? $headlines[$key+1]['level'] : 0; - + if($thisLevel > $prevLevel) { $markup .= ''; $thisLevel--; } + + if($thisLevel > 0) + { + $markup .= ''; + } } } - $markup .= ''; - return $markup; } @@ -434,7 +441,8 @@ class ParsedownExtension extends \ParsedownExtra $href = $element['element']['element']['attributes']['href']; - $element['element']['element']['attributes']['href'] = $this->relurl . $href; +# $element['element']['element']['attributes']['href'] = $this->relurl . $href; + $element['element']['element']['attributes']['href'] = $href; return $element; } @@ -487,8 +495,9 @@ class ParsedownExtension extends \ParsedownExtra $backLinkElements[] = array( 'name' => 'a', 'attributes' => array( - 'href' => $this->relurl . "#fnref$number:$definitionId", - 'rev' => 'footnote', +# 'href' => $this->relurl . "#fnref$number:$definitionId", + 'href' => "#fnref$number:$definitionId", + 'rev' => 'footnote', 'class' => 'footnote-backref', ), 'rawHtml' => '↩', @@ -704,98 +713,188 @@ class ParsedownExtension extends \ParsedownExtra protected $regexAttribute = '(?:[#.][-\w:\\\]+[ ]*|[-\w:\\\]+(?:=(?:["\'][^\n]*?["\']|[^\s]+)?)?[ ]*)'; - # turn markdown into an array of markdown blocks for typemill edit mode - function markdownToArrayBlocks($markdown) + + + # manipulated method linesElements, returns array of markdown blocks + public function markdownToArrayBlocks($markdown) { + + # make sure no definitions are set + $this->DefinitionData = array(); + # standardize line breaks - $markdown = str_replace(array("\r\n", "\r"), "\n", $markdown); + $text = str_replace(array("\r\n", "\r"), "\n", $markdown); # remove surrounding line breaks - $markdown = trim($markdown, "\n"); + $text = trim($text, "\n"); - # trim to maximum two linebreaks - - # split text into blocks - $blocks = explode("\n\n", $markdown); - - # clean up code blocks - $cleanBlocks = array(); - - # holds the content of codeblocks - $codeBlock = ''; - - # flag if codeblock is on or off. - $codeBlockOn = false; - - # holds the content of a definition list - $definitionList = ""; + # split text into lines + $lines = explode("\n", $text); - # flag if definition-list is on or off. - $definitionListOn = false; + # manipulated method linesElements - foreach($blocks as $block) + $Elements = array(); + $CurrentBlock = null; +/*new*/ $mdElements = array(); + + foreach ($lines as $line) { - # remove empty lines - if (chop($block) === '') continue; - - # if the block starts with a fenced code - if(substr($block,0,2) == '``') + if (chop($line) === '') { - # and if we are in an open code-block - if($codeBlockOn) + if (isset($CurrentBlock)) { - # it must be the end of the codeblock, so add it to the codeblock - $block = $codeBlock . "\n" . $block; - - # reset codeblock-value and close the codeblock. - $codeBlock = ''; - $codeBlockOn = false; + $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted']) + ? $CurrentBlock['interrupted'] + 1 : 1 + ); } - else - { - # it must be the start of the codeblock. - $codeBlockOn = true; - } - } - if($codeBlockOn) - { - # if the codeblock is complete - if($this->isComplete($block)) - { - $block = $codeBlock . "\n" . $block; - # reset codeblock-value and close the codeblock. - $codeBlock = ''; - $codeBlockOn = false; - } - else + continue; + } + + while (($beforeTab = strstr($line, "\t", true)) !== false) + { + $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4; + + $line = $beforeTab + . str_repeat(' ', $shortage) + . substr($line, strlen($beforeTab) + 1) + ; + } + + $indent = strspn($line, ' '); + + $text = $indent > 0 ? substr($line, $indent) : $line; + + # ~ + + $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); + + # ~ + + # if we are in a multiline block element + if (isset($CurrentBlock['continuable'])) + { + $methodName = 'block' . $CurrentBlock['type'] . 'Continue'; + $Block = $this->$methodName($Line, $CurrentBlock); + + # if this line still belongs to the current multiline block + if (isset($Block)) { - $codeBlock .= "\n" . $block; + $CurrentBlock = $Block; +/*new*/ $mdCurrentBlock = $mdCurrentBlock . "\n" . $line; continue; } + # if this line does not belong to the current multiline block + else + { + # if multiline block element is finished + if ($this->isBlockCompletable($CurrentBlock['type'])) + { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); +/*new*/ $mdCurrentBlock = $mdCurrentBlock; + } + } } - - # handle definition lists - $checkDL = preg_split('/\r\n|\r|\n/',$block); - if(isset($checkDL[1]) && substr($checkDL[1],0,2) == ': ') + + # ~ + + $marker = $text[0]; + + # ~ + + $blockTypes = $this->unmarkedBlockTypes; + + if (isset($this->BlockTypes[$marker])) { - $definitionList .= $block . "\n\n"; - $definitionListOn = true; - continue; + foreach ($this->BlockTypes[$marker] as $blockType) + { + $blockTypes []= $blockType; + } } - elseif($definitionListOn) + + # + # ~ + + foreach ($blockTypes as $blockType) { - $cleanBlocks[] = $definitionList; - $definitionList = ""; - $definitionListOn = false; + $Block = $this->{"block$blockType"}($Line, $CurrentBlock); +/*new*/ $mdBlock = $line; + if (isset($Block)) + { + $Block['type'] = $blockType; + if ( ! isset($Block['identified'])) + { + if (isset($CurrentBlock)) + { + $Elements[] = $this->extractElement($CurrentBlock); +/*new*/ $mdElements[] = $mdCurrentBlock; + } + + $Block['identified'] = true; + } + + if ($this->isBlockContinuable($blockType)) + { + $Block['continuable'] = true; + } + + $CurrentBlock = $Block; +/*new*/ $mdCurrentBlock = $mdBlock; + continue 2; + } } - - $block = trim($block, "\n"); - - $cleanBlocks[] = $block; + + # ~ + + if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph') + { + $Block = $this->paragraphContinue($Line, $CurrentBlock); +/*new*/ $mdBlock = $mdCurrentBlock . "\n" . $line; + } + + if (isset($Block)) + { + $CurrentBlock = $Block; +/*new*/ $mdCurrentBlock = $mdBlock; + } + else + { + if (isset($CurrentBlock)) + { + $Elements[] = $this->extractElement($CurrentBlock); +/*new*/ $mdElements[] = $mdCurrentBlock; + } + + $CurrentBlock = $this->paragraph($Line); +/*new*/ $mdCurrentBlock = $line; + $CurrentBlock['identified'] = true; + } + } - return $cleanBlocks; + + # ~ + + if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) + { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); +/*new*/ $mdCurrentBlock = $mdCurrentBlock; + } + + # ~ + + if (isset($CurrentBlock)) + { + $Elements[] = $this->extractElement($CurrentBlock); +/*new*/ $mdElements[] = $mdCurrentBlock; + } + + # ~ +/*new*/ return $mdElements; +# return $Elements; } + public function arrayBlocksToMarkdown(array $arrayBlocks) { @@ -823,4 +922,106 @@ class ParsedownExtension extends \ParsedownExtra } return false; } + + + protected function inlineLink($Excerpt) + { + $Element = array( + 'name' => 'a', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => null, + 'destination' => 'elements', + ), + 'nonNestables' => array('Url', 'Link'), + 'attributes' => array( + 'href' => null, + 'title' => null, + ), + ); + + $extent = 0; + + $remainder = $Excerpt['text']; + + if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) + { + $Element['handler']['argument'] = $matches[1]; + + $extent += strlen($matches[0]); + + $remainder = substr($remainder, $extent); + } + else + { + return; + } + + if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches)) + { + # start typemill: if relative link or media-link + $href = $matches[1]; + if($href[0] == '/') + { + $href = $this->baseUrl . $href; + } + elseif(substr( $href, 0, 6 ) === "media/") + { + $href = $this->baseUrl . '/' . $href; + } + # end typemill + + $Element['attributes']['href'] = $href; + + if (isset($matches[2])) + { + $Element['attributes']['title'] = substr($matches[2], 1, - 1); + } + + $extent += strlen($matches[0]); + } + else + { + if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) + { + $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument']; + $definition = strtolower($definition); + + $extent += strlen($matches[0]); + } + else + { + $definition = strtolower($Element['handler']['argument']); + } + + if ( ! isset($this->DefinitionData['Reference'][$definition])) + { + return; + } + + $Definition = $this->DefinitionData['Reference'][$definition]; + + $Element['attributes']['href'] = $Definition['url']; + $Element['attributes']['title'] = $Definition['title']; + } + + $Link = array( + 'extent' => $extent, + 'element' => $Element, + ); + + # Parsedown Extra + $remainder = $Link !== null ? substr($Excerpt['text'], $Link['extent']) : ''; + + if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches)) + { + $Link['element']['attributes'] += $this->parseAttributeData($matches[1]); + + $Link['extent'] += strlen($matches[0]); + } + + return $Link; + + } + } \ No newline at end of file diff --git a/system/Extensions/TwigMarkdownExtension.php b/system/Extensions/TwigMarkdownExtension.php index 4d0c0c7..ae4aa6c 100644 --- a/system/Extensions/TwigMarkdownExtension.php +++ b/system/Extensions/TwigMarkdownExtension.php @@ -19,6 +19,6 @@ class TwigMarkdownExtension extends \Twig_Extension $markdownArray = $parsedown->text($markdown); - return $parsedown->markup($markdownArray, false); + return $parsedown->markup($markdownArray); } } \ No newline at end of file diff --git a/system/Models/ProcessAssets.php b/system/Models/ProcessAssets.php index 4752e89..b64c08d 100644 --- a/system/Models/ProcessAssets.php +++ b/system/Models/ProcessAssets.php @@ -249,5 +249,5 @@ class ProcessAssets } return $bytes; - } + } } \ No newline at end of file diff --git a/system/Models/ProcessImage.php b/system/Models/ProcessImage.php index a62c58f..4bfceee 100644 --- a/system/Models/ProcessImage.php +++ b/system/Models/ProcessImage.php @@ -177,7 +177,7 @@ class ProcessImage extends ProcessAssets $new = imagecreatetruecolor($desired['width'], $desired['height']); // preserve transparency - if($imageType == "gif" or $imageType == "png") + if($imageType == "gif" or $imageType == "png" or $imageType == "webp") { imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127)); imagealphablending($new, false); @@ -214,6 +214,10 @@ class ProcessImage extends ProcessAssets { $result = imagegif( $image, $folder . $name . '.gif' ); } + elseif($type == "webp") + { + $result = imagewebp( $image, $folder . $name . '.webp', 100); + } elseif($type == "jpg" OR $type == "jpeg") { $result = imagejpeg( $image, $folder . $name . '.' . $type ); @@ -400,6 +404,7 @@ class ProcessImage extends ProcessAssets case 'jpg' : case 'jpeg': $image = imagecreatefromjpeg($imagePath); break; case 'png': $image = imagecreatefrompng($imagePath); break; + case 'webp': $image = imagecreatefromwebp($imagePath); break; default: return 'image type not supported'; } diff --git a/system/Models/User.php b/system/Models/User.php index d990b5f..ec2f6e9 100644 --- a/system/Models/User.php +++ b/system/Models/User.php @@ -38,26 +38,12 @@ class User extends WriteYaml } public function createUser($params) - { - $userdata = array( - 'username' => $params['username'], - 'email' => $params['email'], - 'password' => $this->generatePassword($params['password']), - 'userrole' => $params['userrole'] - ); - - if(isset($params['firstname'])) - { - $userdata['firstname'] = $params['firstname']; - } - if(isset($params['lastname'])) - { - $userdata['lastname'] = $params['lastname']; - } + { + $params['password'] = $this->generatePassword($params['password']); - if($this->updateYaml('settings/users', $userdata['username'] . '.yaml', $userdata)) + if($this->updateYaml('settings/users', $params['username'] . '.yaml', $params)) { - return $userdata['username']; + return $params['username']; } return false; } @@ -81,11 +67,14 @@ class User extends WriteYaml } $update = array_merge($userdata, $params); + + # cleanup data here + $this->updateYaml('settings/users', $userdata['username'] . '.yaml', $update); # if user updated his own profile, update session data - if($_SESSION['user'] == $params['username']) + if(isset($_SESSION['user']) && $_SESSION['user'] == $params['username']) { $_SESSION['role'] = $update['userrole']; diff --git a/system/Models/Validation.php b/system/Models/Validation.php index 84b0554..fedb492 100644 --- a/system/Models/Validation.php +++ b/system/Models/Validation.php @@ -29,12 +29,12 @@ class Validation Validator::addRule('image_types', function($field, $value, array $params, array $fields) use ($user) { - $allowed = ['jpg', 'jpeg', 'png']; + $allowed = ['jpg', 'jpeg', 'png', 'webp']; $pathinfo = pathinfo($value); $extension = strtolower($pathinfo['extension']); - if(array_search($extension, $allowed)){ return true; } + if(in_array($extension, $allowed)){ return true; } return false; - }, 'only jpg, jpeg, png allowed'); + }, 'only jpg, jpeg, png, webp, allowed'); Validator::addRule('userAvailable', function($field, $value, array $params, array $fields) use ($user) { diff --git a/system/Models/WriteCache.php b/system/Models/WriteCache.php index 2d0f461..cfb80c2 100644 --- a/system/Models/WriteCache.php +++ b/system/Models/WriteCache.php @@ -46,11 +46,16 @@ class WriteCache extends Write public function updateCache($folderName, $cacheFileName, $requestFileName, $cacheData) { $sCacheData = serialize($cacheData); - $this->writeFile($folderName, $cacheFileName, $sCacheData); - if($requestFileName) + if($this->writeFile($folderName, $cacheFileName, $sCacheData)) { - $this->writeFile($folderName, $requestFileName, time()); + if($requestFileName) + { + $this->writeFile($folderName, $requestFileName, time()); + } + + return true; } + return false; } /** diff --git a/system/Plugin.php b/system/Plugin.php index 216f4cc..2ec3eaa 100644 --- a/system/Plugin.php +++ b/system/Plugin.php @@ -153,7 +153,7 @@ abstract class Plugin implements EventSubscriberInterface $parsedown = new ParsedownExtension(); $contentArray = $parsedown->text($markdown); - $html = $parsedown->markup($contentArray,false); + $html = $parsedown->markup($contentArray); return $html; } diff --git a/system/Plugins.php b/system/Plugins.php index f40000a..5e4ba9e 100644 --- a/system/Plugins.php +++ b/system/Plugins.php @@ -77,8 +77,7 @@ class Plugins if( isset($route['httpMethod']) AND in_array($route['httpMethod'], array('get','post','put','delete','head','patch','options')) AND isset($route['route']) AND is_string($route['route']) - AND isset($route['class']) AND is_string($route['class']) - AND !$this->in_array_r(strtolower($route['route']),$routes)) + AND isset($route['class']) AND is_string($route['class'])) { return true; } diff --git a/system/Routes/Api.php b/system/Routes/Api.php index f1095a6..ab786a5 100644 --- a/system/Routes/Api.php +++ b/system/Routes/Api.php @@ -10,6 +10,7 @@ use Typemill\Controllers\MetaApiController; use Typemill\Middleware\RestrictApiAccess; $app->get('/api/v1/themes', SettingsController::class . ':getThemeSettings')->setName('api.themes')->add(new RestrictApiAccess($container['router'])); +$app->delete('/api/v1/clearcache', SettingsController::class . ':clearCache')->setName('api.clearcache')->add(new RestrictApiAccess($container['router'])); $app->post('/api/v1/article/markdown', ArticleApiController::class . ':getArticleMarkdown')->setName('api.article.markdown')->add(new RestrictApiAccess($container['router'])); $app->post('/api/v1/article/html', ArticleApiController::class . ':getArticleHtml')->setName('api.article.html')->add(new RestrictApiAccess($container['router'])); diff --git a/system/Settings.php b/system/Settings.php index b553cea..27c9fc9 100644 --- a/system/Settings.php +++ b/system/Settings.php @@ -20,8 +20,11 @@ class Settings $settings = array_merge($defaultSettings, $userSettings); } - # no individual image sizes are allowed sind 1.3.4 - $settings['images'] = $defaultSettings['images']; + # no individual image sizes are allowed since 1.3.4 + # $settings['images'] = $defaultSettings['images']; + # make sure that thumb sizes are still there. + $images = array_merge($defaultSettings['images'], $settings['images']); + $settings['images'] = $images; # we have to check if the theme has been deleted $themefolder = $settings['rootPath'] . $settings['themeFolder'] . DIRECTORY_SEPARATOR; @@ -60,10 +63,9 @@ class Settings public static function getDefaultSettings() { $rootPath = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR; - + return [ 'determineRouteBeforeAppMiddleware' => true, - 'displayErrorDetails' => false, 'title' => 'TYPEMILL', 'author' => 'Unknown', 'copyright' => 'Copyright', @@ -80,8 +82,6 @@ class Settings 'editor' => 'visual', 'formats' => ['markdown', 'headline', 'ulist', 'olist', 'table', 'quote', 'notice', 'image', 'video', 'file', 'toc', 'hr', 'definition', 'code'], 'contentFolder' => 'content', - 'cache' => true, - 'cachePath' => $rootPath . 'cache', 'version' => '1.3.8', 'setup' => true, 'welcome' => true, @@ -166,11 +166,15 @@ class Settings 'setup' => true, 'welcome' => true, 'images' => true, + 'live' => true, + 'width' => true, + 'height' => true, 'plugins' => true, 'themes' => true, 'latestVersion' => true, 'logo' => true, - 'favicon' => true + 'favicon' => true, + 'twigcache' => true ]; # cleanup the existing usersettings diff --git a/system/Translations.php b/system/Translations.php index 3bd1abf..9f7b854 100644 --- a/system/Translations.php +++ b/system/Translations.php @@ -62,7 +62,9 @@ class Translations } } foreach($plugin_labels as $key => $value) { - $plugins_labels = array_merge($plugins_labels, $value); + if(is_array($value)){ + $plugins_labels = array_merge($plugins_labels, $value); + } } } } diff --git a/system/author/css/style.css b/system/author/css/style.css index 7911176..20c86e8 100644 --- a/system/author/css/style.css +++ b/system/author/css/style.css @@ -185,6 +185,7 @@ body,input,select,textarea{ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } + header, nav, h1, h2, h3, h4, h5, h6{ font-family: arial, sans-serif; } @@ -593,9 +594,6 @@ footer{ margin-left: auto; margin-right: auto; } -.setupWrapper label{ - font-weight: 700; -} .setupWrapper input[type="submit"] { border-radius: 0px; @@ -607,7 +605,7 @@ footer{ border: 2px solid #f9f8f6; } .setupWrapper .formElement{ - margin: 0 0 20px 0; + margin: 0 0 30px 0; } /************************ @@ -899,6 +897,9 @@ form .hidden{ .large img{ width: 100%; } +.large.img-component img, .large .img-upload img{ + width: auto; +} fieldset{ display: block; position: relative; @@ -911,20 +912,19 @@ fieldset{ label,.label{ display: inline-block; width: 100%; - font-size: 0.75em; + font-size: 0.9em; font-weight: 300; padding: 0px; line-height: 1.5em; - text-transform: uppercase; } -input, select, textarea{ +input, select, textarea{ display: inline-block; box-shadow: none; width: 100%; font-size: 1em; box-sizing: border-box; min-height: 42px; - border: 1px solid #f9f8f6; + border: 1px solid #eee; background: #f9f8f6; } input:focus, select:focus, textarea:focus{ @@ -1150,11 +1150,15 @@ ul.cardInfo{ overflow: hidden; border: 1px; } +.description{ + font-size: .8em; + font-style: italic; +} .cardFields .cardField:first-child{ margin-top: 50px; } .cardFields input, .cardFields select, .cardFields textarea, .cardFields .onoffswitch{ - margin-bottom: 12px; + margin-bottom: 6px; } .cardFields label{ margin-top: 12px; @@ -1168,7 +1172,7 @@ ul.cardInfo{ position: relative; width: 100%; display: inline-block; - margin: 10px 0; + margin: 26px 0 0; } .cardField.full{ width: 100%; @@ -1660,8 +1664,8 @@ label .help, .label .help{ background:#e0474c; color: #fff; } -.editor button:disabled, -.editor button[disabled]{ +button:disabled, +button[disabled]{ border: 1px solid #eee; background: #eee; color: #444; diff --git a/system/author/editor/editor-blox.twig b/system/author/editor/editor-blox.twig index 5a037d5..cf249e0 100644 --- a/system/author/editor/editor-blox.twig +++ b/system/author/editor/editor-blox.twig @@ -51,7 +51,7 @@
    - +
    diff --git a/system/author/js/author.js b/system/author/js/author.js index 372e401..8ddc12b 100644 --- a/system/author/js/author.js +++ b/system/author/js/author.js @@ -297,8 +297,39 @@ visiblefilename.value = ''; } - }()); + }()); } + + /************************************* + ** Clear Cache ** + *************************************/ + + var cachebutton = document.getElementById("clearcache"); + + if(cachebutton) + { + + cachebutton.addEventListener("click", function(event){ + + event.preventDefault(); + + myaxios.delete('/api/v1/clearcache',{ + data: { + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + } + }) + .then(function (response) { + cachebutton.disabled = true; + document.getElementById("cacheresult").innerHTML = "Done!"; + }) + .catch(function (error) + { + document.getElementById("cacheresult").innerHTML = "" + error.response.data.errors + ""; + }); + }); + } + /************************************* ** COLOR PICKER ** diff --git a/system/author/js/vue-blox.js b/system/author/js/vue-blox.js index 2433121..615462c 100644 --- a/system/author/js/vue-blox.js +++ b/system/author/js/vue-blox.js @@ -16,7 +16,7 @@ const contentComponent = Vue.component('content-block', { '' + '
    ' + '' + - '' + + '' + '
    ' + '
    ' + '
    ' + @@ -79,6 +79,10 @@ const contentComponent = Vue.component('content-block', { }, updateMarkdown: function($event) { + eventBus.$emit('markdownChanged'); + self.$root.$data.unsafed = true; + this.edit = true; + this.unsafed = true; this.compmarkdown = $event; this.setComponentSize(); }, @@ -105,10 +109,19 @@ const contentComponent = Vue.component('content-block', { switchToEditMode: function() { if(this.edit){ return; } + + if(this.$root.$data.unsafed) + { + publishController.errors.message = "Please save or cancel your changes."; + return; + } + 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 */ @@ -125,19 +138,25 @@ const contentComponent = Vue.component('content-block', { this.$refs.preview.style.minHeight = "auto"; } }, + cancel: function() + { + self = this; + self.$root.$data.unsafed = false; + this.switchToPreviewMode(); + }, switchToPreviewMode: function() { self = this; - self.$root.$data.freeze = false; /* activate the data again */ - self.$root.sortdisabled = false; /* activate sorting again */ - this.preview = 'visible'; /* show the html-preview */ - this.edit = false; /* hide the edit mode */ - this.compmarkdown = ''; /* clear markdown content */ - this.componentType = false; /* delete the component type */ + self.$root.$data.freeze = false; /* activate the data again */ + self.$root.sortdisabled = false; /* activate sorting again */ + this.preview = 'visible'; /* show the html-preview */ + this.edit = false; /* hide the edit mode */ + this.compmarkdown = ''; /* clear markdown content */ + this.componentType = false; /* delete the component type */ self.$root.$data.blockType = false; self.$root.$data.blockMarkdown = false; self.$root.$data.file = false; - publishController.errors.message = false; /* delete all error messages */ + publishController.errors.message = false; /* delete all error messages */ this.$refs.preview.style.minHeight = "auto"; }, freezePage: function() @@ -216,7 +235,8 @@ const contentComponent = Vue.component('content-block', { { if(this.compmarkdown == undefined || this.compmarkdown.replace(/(\r\n|\n|\r|\s)/gm,"") == '') { - this.switchToPreviewMode(); + this.$root.$data.unsafed = false; + this.switchToPreviewMode(); } else { @@ -263,6 +283,7 @@ const contentComponent = Vue.component('content-block', { var oldVideoID = this.$root.$data.blockMarkdown.match(/#.*? /); if(this.compmarkdown.indexOf(oldVideoID[0].substring(1).trim()) !== -1) { + this.$root.$data.unsafed = false; this.activatePage(); this.switchToPreviewMode(); return; @@ -294,6 +315,7 @@ const contentComponent = Vue.component('content-block', { { if(httpStatus == 400) { + this.$root.$data.unsafed = false; self.activatePage(); publishController.errors.message = "Looks like you are logged out. Please login and try again."; } @@ -305,12 +327,14 @@ const contentComponent = Vue.component('content-block', { if(result.errors) { + self.$root.$data.unsafed = false; publishController.errors.message = result.errors.message; } else { var thisBlockType = self.$root.$data.blockType; - + + self.$root.$data.unsafed = false; self.switchToPreviewMode(); if(self.$root.$data.blockId == 99999) @@ -374,6 +398,7 @@ const contentComponent = Vue.component('content-block', { } else if(httpStatus != 200) { + self.$root.$data.unsafed = false; self.activatePage(); publishController.errors.message = "Sorry, something went wrong. Please refresh the page and try again."; } @@ -405,11 +430,13 @@ const contentComponent = Vue.component('content-block', { { if(httpStatus == 400) { + self.$root.$data.unsafed = false; self.activatePage(); publishController.errors.message = "Looks like you are logged out. Please login and try again."; } if(response) { + self.$root.$data.unsafed = false; self.activatePage(); var result = JSON.parse(response); @@ -798,8 +825,9 @@ const noticeComponent = Vue.component('notice-component', { this.prefix = this.getNoticePrefix(this.compmarkdown); var lines = this.compmarkdown.match(/^.*([\n\r]+|$)/gm); - for (var i = 0; i < lines.length; i++) { - lines[i] = lines[i].replace(/(^[\! ]+)/mg, ''); + for (var i = 0; i < lines.length; i++) + { + lines[i] = lines[i].replace(/(^[\! ]+(?!\[))/mg, ''); } this.notice = lines.join(''); @@ -1432,11 +1460,11 @@ const imageComponent = Vue.component('image-component', { } } - var imgpreview = imgmarkdown.match(/\(.*?\)/); - if(imgpreview) + var imgfile = imgmarkdown.match(/\(.*?\)/); + if(imgfile) { - this.imgpreview = imgpreview[0].slice(1,-1); - this.imgfile = this.imgpreview; + this.imgfile = imgfile[0].slice(1,-1); + this.imgpreview = this.$root.$data.root + '/' + this.imgfile; } } }, @@ -1822,500 +1850,6 @@ const fileComponent = Vue.component('file-component', { } }) -const medialib = Vue.component('medialib', { - props: ['parentcomponent'], - template: '
    ' + - '
    ' + - '
    ' + - '' + - '
    ' + - '
    ' + - '
    ' + - '' + - '' + - '
    ' + - '
    ' + - '
    {{errors}}
    ' + - '' + - '
    ' + - '' + - ' click to select' + - '' + - '
    ' + - '
    {{ image.name }}
    ' + - '' + - '' + - '
    ' + - '
    ' + - '
    ' + - '
    ' + - '
    ' + - '
    ' + - '
    ' + - '' + - '
    ' + - '
    ' + - '
    ' + - '
    Name
    {{ imagedetaildata.name}}
    ' + - '
    URL
    {{ imagedetaildata.src_live}}
    ' + - '
    ' + - '
    ' + - '
    Size
    {{ getSize(imagedetaildata.bytes) }}
    ' + - '
    ' + - '
    ' + - '
    Dimensions
    {{ imagedetaildata.width }}x{{ imagedetaildata.height }} px
    ' + - '
    ' + - '
    ' + - '
    Type
    {{ imagedetaildata.type }}
    ' + - '
    ' + - '
    ' + - '
    Date
    {{ getDate(imagedetaildata.timestamp) }}
    ' + - '
    ' + - '
    ' + - '
    ' + - '' + - '' + - '
    ' + - '
    ' + - '
    ' + - '' + - '
    ' + - '

    Image used in:

    ' + - '' + - '
    No pages found.
    '+ - '
    ' + - '
    ' + - '' + - '
    ' + - '' + - '
    ' + - '' + - '
    ' + - '
    ' + - '
    {{ file.name }}
    ' + - '' + - '' + - '
    ' + - '
    ' + - '
    ' + - '
    ' + - '
    ' + - '
    ' + - '
    ' + - '
    {{ filedetaildata.info.extension }}
    ' + - '
    ' + - '
    ' + - '
    ' + - '
    Name
    {{ filedetaildata.name}}
    ' + - '
    URL
    {{ filedetaildata.url}}
    ' + - '
    ' + - '
    ' + - '
    Size
    {{ getSize(filedetaildata.bytes) }}
    ' + - '
    ' + - '
    ' + - '
    Type
    {{ filedetaildata.info.extension }}
    ' + - '
    ' + - '
    ' + - '
    Date
    {{ getDate(filedetaildata.timestamp) }}
    ' + - '
    ' + - '
    ' + - '
    ' + - '' + - '' + - '
    ' + - '
    ' + - '
    ' + - '' + - '
    ' + - '

    File used in:

    ' + - '' + - '
    No pages found.
    '+ - '
    ' + - '
    ' + - '
    ' + - '
    ' + - '
    ', - data: function(){ - return { - imagedata: false, - showimages: true, - imagedetaildata: false, - showimagedetails: false, - filedata: false, - showfiles: false, - filedetaildata: false, - showfiledetails: false, - detailindex: false, - load: false, - baseurl: false, - search: '', - errors: false, - } - }, - mounted: function(){ - - if(this.parentcomponent == 'files') - { - this.showFiles(); - } - - this.errors = false; - var self = this; - - myaxios.get('/api/v1/medialib/images',{ - params: { - 'url': document.getElementById("path").value, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - } - }) - .then(function (response) - { - self.imagedata = response.data.images; - }) - .catch(function (error) - { - if(error.response) - { - self.errors = error.response.data.errors; - } - }); - }, - computed: { - filteredImages() { - - var searchimages = this.search; - var filteredImages = {}; - var images = this.imagedata; - if(images) - { - Object.keys(images).forEach(function(key) { - var searchindex = key + ' ' + images[key].name; - if(searchindex.toLowerCase().indexOf(searchimages.toLowerCase()) !== -1) - { - filteredImages[key] = images[key]; - } - }); - } - return filteredImages; - }, - filteredFiles() { - - var searchfiles = this.search; - var filteredFiles = {}; - var files = this.filedata; - if(files) - { - Object.keys(files).forEach(function(key) { - var searchindex = key + ' ' + files[key].name; - if(searchindex.toLowerCase().indexOf(searchfiles.toLowerCase()) !== -1) - { - filteredFiles[key] = files[key]; - } - }); - } - return filteredFiles; - } - }, - methods: { - isImagesActive: function() - { - if(this.showimages) - { - return 'bg-tm-green white'; - } - return 'bg-light-gray black'; - }, - isFilesActive: function() - { - if(this.showfiles) - { - return 'bg-tm-green white'; - } - return 'bg-light-gray black'; - }, - closemedialib: function() - { - this.$parent.showmedialib = false; - }, - getBackgroundImage: function(image) - { - return 'background-image: url(' + image.src_thumb + ');width:250px'; - }, - showImages: function() - { - this.errors = false; - this.showimages = true; - this.showfiles = false; - this.showimagedetails = false; - this.showfiledetails = false; - this.imagedetaildata = false; - this.detailindex = false; - }, - showFiles: function() - { - this.showimages = false; - this.showfiles = true; - this.showimagedetails = false; - this.showfiledetails = false; - this.imagedetaildata = false; - this.filedetaildata = false; - this.detailindex = false; - - if(!this.files) - { - this.errors = false; - var filesself = this; - - myaxios.get('/api/v1/medialib/files',{ - params: { - 'url': document.getElementById("path").value, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - } - }) - .then(function (response) - { - filesself.filedata = response.data.files; - }) - .catch(function (error) - { - if(error.response) - { - filesself.errors = error.response.data.errors; - } - }); - } - }, - showImageDetails: function(image,index) - { - this.errors = false; - this.showimages = false; - this.showfiles = false; - this.showimagedetails = true; - this.detailindex = index; - this.baseurl = myaxios.defaults.baseURL + '/tm/content/visual'; - - var imageself = this; - - myaxios.get('/api/v1/image',{ - params: { - 'url': document.getElementById("path").value, - 'name': image.name, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - } - }) - .then(function (response) - { - imageself.imagedetaildata = response.data.image; - }) - .catch(function (error) - { - if(error.response) - { - imageself.errors = error.response.data.errors; - } - }); - }, - showFileDetails: function(file,index) - { - this.errors = false; - this.showimages = false; - this.showfiles = false; - this.showimagedetails = false; - this.showfiledetails = true; - this.detailindex = index; - - this.baseurl = myaxios.defaults.baseURL + '/tm/content/visual'; - - var fileself = this; - - myaxios.get('/api/v1/file',{ - params: { - 'url': document.getElementById("path").value, - 'name': file.name, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - } - }) - .then(function (response) - { - fileself.filedetaildata = response.data.file; - }) - .catch(function (error) - { - if(error.response) - { - fileself.errors = error.response.data.errors; - } - }); - }, - selectImage: function(image) - { - this.showImages(); - - if(this.parentcomponent == 'images') - { - var imgmarkdown = {target: {value: '![alt]('+ image.src_live +')' }}; - - this.$parent.imgfile = image.src_live; - this.$parent.imgpreview = image.src_live; - this.$parent.imgmeta = true; - - this.$parent.showmedialib = false; - - this.$parent.createmarkdown(image.src_live); -/* this.$parent.updatemarkdown(imgmarkdown, image.src_live); */ - } - if(this.parentcomponent == 'files') - { - var filemarkdown = {target: {value: '[' + image.name + '](' + image.src_live +'){.tm-download}' }}; - - this.$parent.filemeta = true; - this.$parent.filetitle = image.name; - - this.$parent.showmedialib = false; - - this.$parent.updatemarkdown(filemarkdown, image.src_live); - } - }, - selectFile: function(file) - { - /* if image component is open */ - if(this.parentcomponent == 'images') - { - var imgextensions = ['png','jpg', 'jpeg', 'gif', 'svg']; - if(imgextensions.indexOf(file.info.extension) == -1) - { - this.errors = "you cannot insert a file into an image component"; - return; - } - var imgmarkdown = {target: {value: '![alt]('+ file.url +')' }}; - - this.$parent.imgfile = file.url; - this.$parent.imgpreview = file.url; - this.$parent.imgmeta = true; - - this.$parent.showmedialib = false; - - this.$parent.createmarkdown(file.url); -/* this.$parent.updatemarkdown(imgmarkdown, file.url);*/ - } - if(this.parentcomponent == 'files') - { - var filemarkdown = {target: {value: '['+ file.name +']('+ file.url +'){.tm-download file-' + file.info.extension + '}' }}; - - this.$parent.showmedialib = false; - - this.$parent.filemeta = true; - this.$parent.filetitle = file.info.filename + ' (' + file.info.extension.toUpperCase() + ')'; - - this.$parent.updatemarkdown(filemarkdown, file.url); - } - this.showFiles(); - }, - removeImage: function(index) - { - this.imagedata.splice(index,1); - }, - removeFile: function(index) - { - this.filedata.splice(index,1); - }, - deleteImage: function(image, index) - { - imageself = this; - - myaxios.delete('/api/v1/image',{ - data: { - 'url': document.getElementById("path").value, - 'name': image.name, - 'index': index, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - } - }) - .then(function (response) - { - imageself.showImages(); - imageself.removeImage(index); - }) - .catch(function (error) - { - if(error.response) - { - imageself.errors = error.response.data.errors; - } - }); - }, - deleteFile: function(file, index) - { - fileself = this; - - myaxios.delete('/api/v1/file',{ - data: { - 'url': document.getElementById("path").value, - 'name': file.name, - 'index': index, - 'csrf_name': document.getElementById("csrf_name").value, - 'csrf_value': document.getElementById("csrf_value").value, - } - }) - .then(function (response) - { - fileself.showFiles(); - fileself.removeFile(index); - }) - .catch(function (error) - { - if(error.response) - { - fileself.errors = error.response.data.errors; - } - }); - }, - getDate(timestamp) - { - date = new Date(timestamp * 1000); - - datevalues = { - 'year': date.getFullYear(), - 'month': date.getMonth()+1, - 'day': date.getDate(), - 'hour': date.getHours(), - 'minute': date.getMinutes(), - 'second': date.getSeconds(), - }; - return datevalues.year + '-' + datevalues.month + '-' + datevalues.day; - }, - getSize(bytes) - { - var i = Math.floor(Math.log(bytes) / Math.log(1024)), - sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - - return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; - }, - isChecked: function(classname) - { - if(this.imgclass == classname) - { - return ' checked'; - } - }, - }, -}) - let activeFormats = []; @@ -2342,6 +1876,7 @@ let editor = new Vue({ blockMarkdown: false, file: false, freeze: false, + unsafed: false, newBlocks: [], addblock: false, draftDisabled: true, @@ -2495,7 +2030,8 @@ let editor = new Vue({ }, setData: function(event, blocktype, body) { - // change this, not needed anymore. + if(this.unsafed){ return; } + this.blockId = event.currentTarget.dataset.id; this.blockMarkdown = this.markdown[this.blockId]; if(blocktype) diff --git a/system/author/js/vue-shared.js b/system/author/js/vue-shared.js index 1a13a8e..31790b3 100644 --- a/system/author/js/vue-shared.js +++ b/system/author/js/vue-shared.js @@ -1,6 +1,6 @@ Vue.component('component-image', { props: ['class', 'id', 'description', 'maxlength', 'hidden', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'errors'], - template: '
    ' + + template: '
    ' + '' + '
    ' + '
    ' + @@ -52,7 +52,7 @@ Vue.component('component-image', { } }, mounted: function(){ - this.imgpreview = this.value; + this.imgpreview = myaxios.defaults.baseURL + '/' + this.value; }, methods: { update: function(value) @@ -138,4 +138,503 @@ Vue.component('component-image', { } } }, -}) \ No newline at end of file +}) + +const medialib = Vue.component('medialib', { + props: ['parentcomponent'], + template: '
    ' + + '
    ' + + '
    ' + + '' + + '
    ' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '
    {{errors}}
    ' + + '' + + '
    ' + + '' + + ' click to select' + + '' + + '
    ' + + '
    {{ image.name }}
    ' + + '' + + '' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '' + + '
    ' + + '
    ' + + '
    ' + + '
    Name
    {{ imagedetaildata.name}}
    ' + + '
    URL
    {{ getImageUrl(imagedetaildata.src_live)}}
    ' + + '
    ' + + '
    ' + + '
    Size
    {{ getSize(imagedetaildata.bytes) }}
    ' + + '
    ' + + '
    ' + + '
    Dimensions
    {{ imagedetaildata.width }}x{{ imagedetaildata.height }} px
    ' + + '
    ' + + '
    ' + + '
    Type
    {{ imagedetaildata.type }}
    ' + + '
    ' + + '
    ' + + '
    Date
    {{ getDate(imagedetaildata.timestamp) }}
    ' + + '
    ' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '
    ' + + '' + + '
    ' + + '

    Image used in:

    ' + + '' + + '
    No pages found.
    '+ + '
    ' + + '
    ' + + '' + + '
    ' + + '' + + '
    ' + + '' + + '
    ' + + '
    ' + + '
    {{ file.name }}
    ' + + '' + + '' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    {{ filedetaildata.info.extension }}
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    Name
    {{ filedetaildata.name}}
    ' + + '
    URL
    {{ filedetaildata.url}}
    ' + + '
    ' + + '
    ' + + '
    Size
    {{ getSize(filedetaildata.bytes) }}
    ' + + '
    ' + + '
    ' + + '
    Type
    {{ filedetaildata.info.extension }}
    ' + + '
    ' + + '
    ' + + '
    Date
    {{ getDate(filedetaildata.timestamp) }}
    ' + + '
    ' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '
    ' + + '' + + '
    ' + + '

    File used in:

    ' + + '' + + '
    No pages found.
    '+ + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + data: function(){ + return { + imagedata: false, + showimages: true, + imagedetaildata: false, + showimagedetails: false, + filedata: false, + showfiles: false, + filedetaildata: false, + showfiledetails: false, + detailindex: false, + load: false, + baseurl: myaxios.defaults.baseURL, + adminurl: false, + search: '', + errors: false, + } + }, + mounted: function(){ + + if(this.parentcomponent == 'files') + { + this.showFiles(); + } + + this.errors = false; + var self = this; + + myaxios.get('/api/v1/medialib/images',{ + params: { + 'url': document.getElementById("path").value, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + } + }) + .then(function (response) + { + self.imagedata = response.data.images; + }) + .catch(function (error) + { + if(error.response) + { + self.errors = error.response.data.errors; + } + }); + }, + computed: { + filteredImages() { + + var searchimages = this.search; + var filteredImages = {}; + var images = this.imagedata; + if(images) + { + Object.keys(images).forEach(function(key) { + var searchindex = key + ' ' + images[key].name; + if(searchindex.toLowerCase().indexOf(searchimages.toLowerCase()) !== -1) + { + filteredImages[key] = images[key]; + } + }); + } + return filteredImages; + }, + filteredFiles() { + + var searchfiles = this.search; + var filteredFiles = {}; + var files = this.filedata; + if(files) + { + Object.keys(files).forEach(function(key) { + var searchindex = key + ' ' + files[key].name; + if(searchindex.toLowerCase().indexOf(searchfiles.toLowerCase()) !== -1) + { + filteredFiles[key] = files[key]; + } + }); + } + return filteredFiles; + } + }, + methods: { + isImagesActive: function() + { + if(this.showimages) + { + return 'bg-tm-green white'; + } + return 'bg-light-gray black'; + }, + isFilesActive: function() + { + if(this.showfiles) + { + return 'bg-tm-green white'; + } + return 'bg-light-gray black'; + }, + closemedialib: function() + { + this.$parent.showmedialib = false; + }, + getBackgroundImage: function(image) + { + return 'background-image: url(' + this.baseurl + '/' + image.src_thumb + ');width:250px'; + }, + getImageUrl(relativeUrl) + { + return this.baseurl + '/' + relativeUrl; + }, + showImages: function() + { + this.errors = false; + this.showimages = true; + this.showfiles = false; + this.showimagedetails = false; + this.showfiledetails = false; + this.imagedetaildata = false; + this.detailindex = false; + }, + showFiles: function() + { + this.showimages = false; + this.showfiles = true; + this.showimagedetails = false; + this.showfiledetails = false; + this.imagedetaildata = false; + this.filedetaildata = false; + this.detailindex = false; + + if(!this.files) + { + this.errors = false; + var filesself = this; + + myaxios.get('/api/v1/medialib/files',{ + params: { + 'url': document.getElementById("path").value, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + } + }) + .then(function (response) + { + filesself.filedata = response.data.files; + }) + .catch(function (error) + { + if(error.response) + { + filesself.errors = error.response.data.errors; + } + }); + } + }, + showImageDetails: function(image,index) + { + this.errors = false; + this.showimages = false; + this.showfiles = false; + this.showimagedetails = true; + this.detailindex = index; + this.adminurl = myaxios.defaults.baseURL + '/tm/content/visual'; + + var imageself = this; + + myaxios.get('/api/v1/image',{ + params: { + 'url': document.getElementById("path").value, + 'name': image.name, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + } + }) + .then(function (response) + { + imageself.imagedetaildata = response.data.image; + }) + .catch(function (error) + { + if(error.response) + { + imageself.errors = error.response.data.errors; + } + }); + }, + showFileDetails: function(file,index) + { + this.errors = false; + this.showimages = false; + this.showfiles = false; + this.showimagedetails = false; + this.showfiledetails = true; + this.detailindex = index; + + this.adminurl = myaxios.defaults.baseURL + '/tm/content/visual'; + + var fileself = this; + + myaxios.get('/api/v1/file',{ + params: { + 'url': document.getElementById("path").value, + 'name': file.name, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + } + }) + .then(function (response) + { + fileself.filedetaildata = response.data.file; + }) + .catch(function (error) + { + if(error.response) + { + fileself.errors = error.response.data.errors; + } + }); + }, + selectImage: function(image) + { + this.showImages(); + + if(this.parentcomponent == 'images') + { + var imgmarkdown = {target: {value: '![alt]('+ image.src_live +')' }}; + + this.$parent.imgfile = image.src_live; + this.$parent.imgpreview = this.baseurl + '/' + image.src_live; + this.$parent.imgmeta = true; + + this.$parent.showmedialib = false; + + this.$parent.createmarkdown(image.src_live); +/* this.$parent.updatemarkdown(imgmarkdown, image.src_live); */ + } + if(this.parentcomponent == 'files') + { + var filemarkdown = {target: {value: '[' + image.name + '](' + image.src_live +'){.tm-download}' }}; + + this.$parent.filemeta = true; + this.$parent.filetitle = image.name; + + this.$parent.showmedialib = false; + + this.$parent.updatemarkdown(filemarkdown, image.src_live); + } + }, + selectFile: function(file) + { + /* if image component is open */ + if(this.parentcomponent == 'images') + { + var imgextensions = ['png','jpg', 'jpeg', 'gif', 'svg', 'webp']; + if(imgextensions.indexOf(file.info.extension) == -1) + { + this.errors = "you cannot insert a file into an image component"; + return; + } + var imgmarkdown = {target: {value: '![alt]('+ file.url +')' }}; + + this.$parent.imgfile = file.url; + this.$parent.imgpreview = file.url; + this.$parent.imgmeta = true; + + this.$parent.showmedialib = false; + + this.$parent.createmarkdown(file.url); +/* this.$parent.updatemarkdown(imgmarkdown, file.url);*/ + } + if(this.parentcomponent == 'files') + { + var filemarkdown = {target: {value: '['+ file.name +']('+ file.url +'){.tm-download file-' + file.info.extension + '}' }}; + + this.$parent.showmedialib = false; + + this.$parent.filemeta = true; + this.$parent.filetitle = file.info.filename + ' (' + file.info.extension.toUpperCase() + ')'; + + this.$parent.updatemarkdown(filemarkdown, file.url); + } + this.showFiles(); + }, + removeImage: function(index) + { + this.imagedata.splice(index,1); + }, + removeFile: function(index) + { + this.filedata.splice(index,1); + }, + deleteImage: function(image, index) + { + imageself = this; + + myaxios.delete('/api/v1/image',{ + data: { + 'url': document.getElementById("path").value, + 'name': image.name, + 'index': index, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + } + }) + .then(function (response) + { + imageself.showImages(); + imageself.removeImage(index); + }) + .catch(function (error) + { + if(error.response) + { + imageself.errors = error.response.data.errors; + } + }); + }, + deleteFile: function(file, index) + { + fileself = this; + + myaxios.delete('/api/v1/file',{ + data: { + 'url': document.getElementById("path").value, + 'name': file.name, + 'index': index, + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + } + }) + .then(function (response) + { + fileself.showFiles(); + fileself.removeFile(index); + }) + .catch(function (error) + { + if(error.response) + { + fileself.errors = error.response.data.errors; + } + }); + }, + getDate(timestamp) + { + date = new Date(timestamp * 1000); + + datevalues = { + 'year': date.getFullYear(), + 'month': date.getMonth()+1, + 'day': date.getDate(), + 'hour': date.getHours(), + 'minute': date.getMinutes(), + 'second': date.getSeconds(), + }; + return datevalues.year + '-' + datevalues.month + '-' + datevalues.day; + }, + getSize(bytes) + { + var i = Math.floor(Math.log(bytes) / Math.log(1024)), + sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; + }, + isChecked: function(classname) + { + if(this.imgclass == classname) + { + return ' checked'; + } + }, + }, +}) diff --git a/system/author/layouts/layout.twig b/system/author/layouts/layout.twig index 7284f58..b63a8af 100644 --- a/system/author/layouts/layout.twig +++ b/system/author/layouts/layout.twig @@ -7,8 +7,6 @@ - - @@ -21,44 +19,13 @@ + + {{ assets.renderCSS() }} + - - - - external-link - - - - text-file - - - - cog - - - - power-off - - - - - - - - - - - - - - - - - - {{ assets.renderSvg() }} - - + + {% include 'partials/symbols.twig' %}
    {% include 'partials/navi.twig' %} @@ -79,11 +46,16 @@ const myaxios = axios.create(); myaxios.defaults.baseURL = "{{ base_url }}"; + + + + {{ assets.renderJS() }} + \ No newline at end of file diff --git a/system/author/layouts/layoutAuth.twig b/system/author/layouts/layoutAuth.twig index 67c75f8..24ce000 100644 --- a/system/author/layouts/layoutAuth.twig +++ b/system/author/layouts/layoutAuth.twig @@ -7,8 +7,6 @@ - - @@ -21,34 +19,12 @@ - + + {{ assets.renderCSS() }} + - - - - external-link - - - - text-file - - - - cog - - - - power-off - - - - bookmark-o - - - - - + {% include 'partials/symbols.twig' %} {% include 'partials/flash.twig' %}
    diff --git a/system/author/layouts/layoutBlank.twig b/system/author/layouts/layoutBlank.twig index 6263470..55a25e2 100644 --- a/system/author/layouts/layoutBlank.twig +++ b/system/author/layouts/layoutBlank.twig @@ -6,9 +6,7 @@ - - - + @@ -20,28 +18,11 @@ + - - - - external-link - - - - text-file - - - - cog - - - - power-off - - - - + + {% include 'partials/symbols.twig' %}
    {% include 'partials/navi.twig' %} @@ -54,5 +35,6 @@
    + \ No newline at end of file diff --git a/system/author/layouts/layoutBlox.twig b/system/author/layouts/layoutBlox.twig index d033196..8e68602 100644 --- a/system/author/layouts/layoutBlox.twig +++ b/system/author/layouts/layoutBlox.twig @@ -6,9 +6,7 @@ - - - + @@ -27,162 +25,8 @@ - - - - - {{ __('EXTERNAL_LINK') }} - - - - {{ __('TEXT_FILE') }} - - - - {{ __('COG') }} - - - - {{ __('POWER_OFF') }} - - - - - {{ __('DELETE') }} - - - - {{ __('ADD') }} - - - - {{ __('DELETE_CLOSE') }} - - - - {{ __('HOME') }} - - - - {{ __('MOVE_VERTICAL') }} - - - - {{ __('FOLDER') }} - - - - {{ __('UPLOAD') }} - - - - {{ __('IMAGE') }} - - - - - - {{ __('NOTICE') }} - - - - {{ __('PAPERCLIP') }} - - - - {{ __('VIDEO') }} - - - - {{ __('QUOTES') }} - - - - {{ __('NUMBERED_LIST') }} - - - - {{ __('BULLET_LIST') }} - - - - {{ ('LINK') }} - - - - - {{ __('BOLD') }} - - - - {{ __('ITALIC') }} - - - - {{ __('HORIZONTAL_LINE') }} - - - - {{ __('TABLE') }} - - - - {{ __('PARAGRAPH') }} - - - - {{ __('CODE') }} - - - - - {{ __('HEADLINE') }} - - - - {{ __('TABLE_OF_CONTENTS') }} - - - - {{ __('DEFINITION') }} - - - - {{ __('CHECK') }} - - - - {{ __('CROSS') }} - - - - {{ __('TRASH') }} - - - - {{ __('INFO') }} - - - - - - {{ __('EYE_BLOCKED') }} - - - - - - {{ __('SEARCH') }} - - - - {{ __('CANCEL') }} - - - - {{ assets.renderSvg() }} - - + + {% include 'partials/symbols.twig' %}
    {% include 'partials/navi.twig' %} diff --git a/system/author/layouts/layoutEditor.twig b/system/author/layouts/layoutEditor.twig index 6e934d3..c50f816 100644 --- a/system/author/layouts/layoutEditor.twig +++ b/system/author/layouts/layoutEditor.twig @@ -7,8 +7,6 @@ - - @@ -25,50 +23,8 @@ - - - - {{ __('EXTERNAL_LINK') }} - - - - {{ __('TEXT_FILE') }} - - - - {{ __('DELETE') }} - - - - {{ __('ADD') }} - - - - {{ __('DELETE_CLOSE') }} - - - - {{ __('HOME') }} - - - - {{ __('MOVE_VERTICAL') }} - - - - {{ __('FOLDER') }} - - - - {{ __('COG') }} - - - - {{ __('POWER_OFF') }} - - - - + + {% include 'partials/symbols.twig' %}
    {% include 'partials/navi.twig' %} diff --git a/system/author/partials/fields.twig b/system/author/partials/fields.twig index a73f2bf..93a6693 100644 --- a/system/author/partials/fields.twig +++ b/system/author/partials/fields.twig @@ -9,7 +9,7 @@
    - +
    diff --git a/system/author/partials/navi.twig b/system/author/partials/navi.twig index b1d01d0..de04e0c 100644 --- a/system/author/partials/navi.twig +++ b/system/author/partials/navi.twig @@ -10,9 +10,9 @@ {{ __('Content') }}
  • {% endif %} {% if acl.isAllowed(get_role(), 'system', 'view') %} - {{ __('Settings') }}
  • + {{ __('Settings') }}
  • {% else %} - {{ __('Settings') }}
  • + {{ __('Settings') }}
  • {% endif %} {{ __('View Site') }}
  • {{ __('Logout') }}
  • diff --git a/system/author/partials/symbols.twig b/system/author/partials/symbols.twig new file mode 100644 index 0000000..89e9abb --- /dev/null +++ b/system/author/partials/symbols.twig @@ -0,0 +1,173 @@ + + + + {{ __('EXTERNAL_LINK') }} + + + + {{ __('TEXT_FILE') }} + + + + {{ __('COG') }} + + + + {{ __('POWER_OFF') }} + + + + {{ __('DELETE') }} + + + + {{ __('ADD') }} + + + + {{ __('DELETE_CLOSE') }} + + + + {{ __('HOME') }} + + + + {{ __('MOVE_VERTICAL') }} + + + + {{ __('FOLDER') }} + + + + {{ __('UPLOAD') }} + + + + {{ __('IMAGE') }} + + + + + + {{ __('NOTICE') }} + + + + {{ __('PAPERCLIP') }} + + + + {{ __('VIDEO') }} + + + + {{ __('QUOTES') }} + + + + {{ __('NUMBERED_LIST') }} + + + + {{ __('BULLET_LIST') }} + + + + {{ ('LINK') }} + + + + + {{ __('BOLD') }} + + + + {{ __('ITALIC') }} + + + + {{ __('HORIZONTAL_LINE') }} + + + + {{ __('TABLE') }} + + + + {{ __('PARAGRAPH') }} + + + + {{ __('CODE') }} + + + + + {{ __('HEADLINE') }} + + + + {{ __('TABLE_OF_CONTENTS') }} + + + + {{ __('DEFINITION') }} + + + + {{ __('CHECK') }} + + + + {{ __('CROSS') }} + + + + {{ __('TRASH') }} + + + + {{ __('INFO') }} + + + + + + {{ __('EYE_BLOCKED') }} + + + + + + {{ __('SEARCH') }} + + + + {{ __('CANCEL') }} + + + + + bookmark-o + + + + + + + + + + + + + + + + + + {{ assets.renderSvg() }} + + diff --git a/system/author/settings/blank.twig b/system/author/settings/blank.twig index 70063a6..dca410a 100644 --- a/system/author/settings/blank.twig +++ b/system/author/settings/blank.twig @@ -8,7 +8,10 @@
    {{ content }}
    - + + + {{ csrf_field() | raw }} +
    {% endblock %} \ No newline at end of file diff --git a/system/author/settings/system.twig b/system/author/settings/system.twig index 4fb337d..9eb29a3 100644 --- a/system/author/settings/system.twig +++ b/system/author/settings/system.twig @@ -147,7 +147,46 @@ {% endfor %} -
    +
    +
    +
    +

    {{ __('Developer') }}

    +

    The following options are only for developers and experienced administrators. Only change the options if you really understand them. For example: Never activate the error reporting for a live website, use this option only for bug-fixing.

    +
    +
    + + +
    +
    + + +
    +
    +
    {{ __('Delete all cache files') }}
    +
    +
    +
    + + +
    This applies only for future images in the content area.
    + {% if errors.settings.images.live.width %} + {{ errors.settings.images.live.width | first }} + {% endif %} +
    +
    + + +
    If you add a value for the height, then the image will be cropped.
    + {% if errors.settings.images.live.height %} + {{ errors.settings.images.live.height | first }} + {% endif %} +
    diff --git a/system/system.php b/system/system.php index 72360fa..b36a235 100644 --- a/system/system.php +++ b/system/system.php @@ -149,6 +149,8 @@ $container['dispatcher'] = function($container) use ($dispatcher) # delete username and password from uri $uri = $container['request']->getUri()->withUserInfo(''); +define("TM_BASE_URL", $uri->getBaseUrl()); + /******************************** * ADD ASSET-FUNCTION FOR TWIG * ********************************/ @@ -224,8 +226,10 @@ $container['view'] = function ($container) use ($uri) { $path = array($container->get('settings')['themePath'], $container->get('settings')['authorPath']); + $cache = ( isset($container->get('settings')['twigcache']) && $container->get('settings')['twigcache'] ) ? $container->get('settings')['rootPath'] . '/cache/twig' : false; + $view = new \Slim\Views\Twig( $path, [ - 'cache' => false, + 'cache' => $cache, 'autoescape' => false, 'debug' => true ]); diff --git a/themes/cyanine/404.twig b/themes/cyanine/404.twig index d9826ee..eaa2431 100644 --- a/themes/cyanine/404.twig +++ b/themes/cyanine/404.twig @@ -4,18 +4,18 @@ {% block content %} -
    +
    -

    Not Found

    +

    Not Found

    Sorry, but we did not find the page that you are looking for.

    - Home + Home
    diff --git a/themes/cyanine/css/style.css b/themes/cyanine/css/style.css index 07c81f1..49022d1 100644 --- a/themes/cyanine/css/style.css +++ b/themes/cyanine/css/style.css @@ -76,7 +76,10 @@ article h6{ font-size: 1em; font-style: italic; font-weight:300; margin: 1em 0 0 article .h1, article .h2, article .h3, article .h4, article .h5, article .h6{ height: auto; /* fix for tachyons */ } -hr{} +hr{ + border: none; + border-top: 1px solid; +} ol{} footer ul{ padding-left:1em; } li{} @@ -114,7 +117,10 @@ a, a:link, a:visited, a:focus, a:hover, a:active, button{ transition: all .15s ease!important; transition-property: color, background-color, text-shadow, border; } - +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 */ code.hljs{ background: transparent; } @@ -180,12 +186,12 @@ ul.TOC,.TOC ul{ text-decoration: none; display: inline-block; width: 100%; - border-bottom: 1px dashed lightseagreen; + border-bottom: 1px dashed; line-height: 1em; margin: .3em 0; } .TOC li a:hover,.TOC li a:focus,.TOC li a:active{ - border-bottom: 1px solid lightseagreen; + border-bottom: 1px solid; } .TOC li a:after{ content: '\203A'; @@ -199,14 +205,10 @@ ul.TOC,.TOC ul{ .notice1 { margin: 1em 0; padding: 10px 1em; - background-color: #ffded4; - border-left: 4px solid #f65a3c; } .notice2 { margin: 1em 0; padding: 10px 1em; - background-color: #fff3d4; - border-left: 4px solid #f6b73c; } .notice3, .notice4, @@ -310,6 +312,15 @@ button.play-video::after { content: ' '; } +.landingpageinfo h2{ + font-size: 2.25rem; +} +.landingpageinfo h3{ + font-size: 1.5rem; +} +.landingpageinfo h4{ + font-size: 1.25rem; +} /************************************ * TACHYONS ADDITIONS * diff --git a/themes/cyanine/cyanine.yaml b/themes/cyanine/cyanine.yaml index b05eb60..4bffa34 100644 --- a/themes/cyanine/cyanine.yaml +++ b/themes/cyanine/cyanine.yaml @@ -18,6 +18,14 @@ settings: forms: fields: + layoutsize: + type: select + label: Layout Size + options: + standard: Standard + large: Large + full: Full Width + landingpage: type: checkbox checkboxlabel: Activate a landingpage @@ -30,6 +38,15 @@ forms: type: number label: Position of Intro Segment description: Use 0 to disable the section + introTitle: + type: text + label: Title for your landingpage intro + placeholder: Typemill + description: Leave empty to use the title of your base content page. + introMarkdown: + type: textarea + label: Text for your landingpage intro (use markdown) + description: Leave empty to use the content of your base content page. introButtonLink: type: text label: Link for startbutton @@ -247,76 +264,137 @@ forms: fields: font: type: select - label: Basic font-family - description: All fonts are system fonts with (fallbacks) if the font is not installed - options: - serif: serif - sans-serif-tm: sans-serif - courier: courier (sans-serif) - helvetica: helvetica (sans-serif) - avenir: avenir (sans-serif) - athelas: athelas (serif) - georgia: georgia (serif) - times: times (serif) - bodoni: bodoni (serif) - calisto: calisto (serif) - garamond: garamond (serif) - baskerville: baskerville (serif) + label: General font-family + description: All fonts are system fonts with fallbacks + options: + BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif: sans-serif + georgia,times,serif: serif + courier,sans-serif: courier (sans-serif) + helvetica,sans-serif: helvetica (sans-serif) + avenir,sans-serif: avenir (sans-serif) + athelas,serif: athelas (serif) + georgia,serif: georgia (serif) + times,serif: times (serif) + bodoni,serif: bodoni (serif) + calisto,serif: calisto (serif) + garamond,serif: garamond (serif) + baskerville,serif: baskerville (serif) fontheadline: type: select label: Font-family for headlines - description: All fonts are system fonts with (fallbacks) if the font is not installed - options: - serif: serif - sans-serif-tm: sans-serif + description: All fonts are system fonts with fallbacks + options: + BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif: sans-serif + georgia,times,serif: serif + courier,sans-serif: courier (sans-serif) + helvetica,sans-serif: helvetica (sans-serif) + avenir,sans-serif: avenir (sans-serif) + athelas,serif: athelas (serif) + georgia,serif: georgia (serif) + times,serif: times (serif) + bodoni,serif: bodoni (serif) + calisto,serif: calisto (serif) + garamond,serif: garamond (serif) + baskerville,serif: baskerville (serif) fontnavi: type: select label: Font-family for navigations - description: All fonts are system fonts with (fallbacks) if the font is not installed - options: - serif: serif - sans-serif-tm: sans-serif - courier: courier (sans-serif) - helvetica: helvetica (sans-serif) - avenir: avenir (sans-serif) - athelas: athelas (serif) - georgia: georgia (serif) - times: times (serif) - bodoni: bodoni (serif) - calisto: calisto (serif) - garamond: garamond (serif) - baskerville: baskerville (serif) + description: All fonts are system fonts with fallbacks + options: + BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif: sans-serif + georgia,times,serif: serif + courier,sans-serif: courier (sans-serif) + helvetica,sans-serif: helvetica (sans-serif) + avenir,sans-serif: avenir (sans-serif) + athelas,serif: athelas (serif) + georgia,serif: georgia (serif) + times,serif: times (serif) + bodoni,serif: bodoni (serif) + calisto,serif: calisto (serif) + garamond,serif: garamond (serif) + baskerville,serif: baskerville (serif) fieldsetColors: type: fieldset legend: Colors fields: brandcolorprimary: type: text - label: Primary brand color + label: Background color for body placeholder: 'leightseagreen' - description: Used for the body background and borders - brandcolorsecondary: - type: text - label: Secondary brand color - placeholder: '#f7f7f7' - description: Used for content background, font-colors on hover and more + fieldsize: half fontcolorprimary: type: text - label: Primary font color - placeholder: 'lightseagreen' - description: Used for text - fontcolorsecondary: + label: Font color for body + placeholder: 'white' + fieldsize: half + newsbackground: type: text - label: Secondary font color + label: Background color for news-box + placeholder: 'white' + fieldsize: half + newscolor: + type: text + label: Font color for news-box + placeholder: '#333' + fieldsize: half + brandcolortertiary: + type: text + label: Background color for buttons + placeholder: 'lightseagreen' + fieldsize: half + fontcolortertiary: + type: text + label: Font color for buttons placeholder: '#F7F7F7' - description: Used as contrary color for hovers in navigation and buttons + fieldsize: half + bordercolortertiary: + type: text + label: Border color for buttons + placeholder: '#F7F7F7' + fieldsize: half fontcolorlink: type: text - label: text-links + label: Font color for content links placeholder: '#007F7F' - description: Used for links, check contrast for a11y. + fieldsize: half + brandcolorsecondary: + type: text + label: Background color for content + placeholder: '#f7f7f7' + fieldsize: half + fontcolorsecondary: + type: text + label: Font color for content + placeholder: '#333' + fieldsize: half + codebackground: + type: text + label: Background color for code + placeholder: '#ddd' + fieldsize: half + codecolor: + type: text + label: Font color for code + placeholder: '#333' + fieldsize: half + contentnavihoverbackground: + type: text + label: Background color for hover of content navigation + placeholder: 'lightseagreen' + fieldsize: half + contentnavihovercolor: + type: text + label: Font color for hover of content navigation + placeholder: 'white' + fieldsize: half thinbordercolor: type: text label: Thin border color placeholder: 'lightgray' - description: Used for thin borders in navigations and tables + description: Used for content navigation, table and horizontal line + fieldsize: half + noticecolors: + type: checkbox + label: Color for notices + checkboxlabel: Use grayscale color schema for notices + fieldsize: half diff --git a/themes/cyanine/home/landingpageContrast.twig b/themes/cyanine/home/landingpageContrast.twig index 72dbaf8..c95098b 100644 --- a/themes/cyanine/home/landingpageContrast.twig +++ b/themes/cyanine/home/landingpageContrast.twig @@ -1,9 +1,9 @@ -
    +
    -

    {{ settings.themes.cyanine.contrastTitle }}

    +

    {{ settings.themes.cyanine.contrastTitle }}

    {{ settings.themes.cyanine.contrastText }}

    - {{ settings.themes.cyanine.contrastLabel }} + {{ settings.themes.cyanine.contrastLabel }}
    diff --git a/themes/cyanine/home/landingpageInfo.twig b/themes/cyanine/home/landingpageInfo.twig index 34beb02..75d0ffc 100644 --- a/themes/cyanine/home/landingpageInfo.twig +++ b/themes/cyanine/home/landingpageInfo.twig @@ -1,6 +1,6 @@ -
    +
    -
    +
    {{ markdown(settings.themes.cyanine.infoMarkdown) }} diff --git a/themes/cyanine/home/landingpageIntro.twig b/themes/cyanine/home/landingpageIntro.twig index afb286f..383abb7 100644 --- a/themes/cyanine/home/landingpageIntro.twig +++ b/themes/cyanine/home/landingpageIntro.twig @@ -1,17 +1,25 @@ -
    +
    -

    {{ title }}

    + {% if settings.themes.cyanine.introTitle %} +

    {{ settings.themes.cyanine.introTitle }}

    + {% else %} +

    {{ title }}

    + {% endif %}
    - {{ content }} + {% if settings.themes.cyanine.introMarkdown %} + {{ markdown(settings.themes.cyanine.introMarkdown) }} + {% else %} + {{ content }} + {% endif %}
    {% if settings.themes.cyanine.introButtonLink %} - {{ settings.themes.cyanine.introButtonLabel }} + {{ settings.themes.cyanine.introButtonLabel }} {% endif %} diff --git a/themes/cyanine/home/landingpageNavi.twig b/themes/cyanine/home/landingpageNavi.twig index 8f2ad06..fde7c0c 100644 --- a/themes/cyanine/home/landingpageNavi.twig +++ b/themes/cyanine/home/landingpageNavi.twig @@ -1,9 +1,9 @@ -
    +
    -

    {{ settings.themes.cyanine.naviTitle }}

    -