From d12d5662b0f307fa40072d33269408f087a669a2 Mon Sep 17 00:00:00 2001 From: Danang Probo Sayekti Date: Mon, 21 Sep 2015 10:45:44 +0700 Subject: [PATCH] Pagedown Extra + sitemap.category.xml Use the pagedown extra instead of standard pagedown. Adding sitemap.category.xml --- config/config.ini.example | 1 + system/admin/editor/js/Markdown.Extra.js | 874 +++++++++++++++++++++++++ system/admin/editor/js/{image.js => editor.js} | 1 + system/admin/editor/js/node-pagedown-extra.js | 3 + system/admin/views/add-audio.html.php | 3 +- system/admin/views/add-category.html.php | 3 +- system/admin/views/add-image.html.php | 3 +- system/admin/views/add-link.html.php | 3 +- system/admin/views/add-page.html.php | 3 +- system/admin/views/add-post.html.php | 3 +- system/admin/views/add-quote.html.php | 3 +- system/admin/views/add-video.html.php | 3 +- system/admin/views/edit-audio.html.php | 3 +- system/admin/views/edit-category.html.php | 3 +- system/admin/views/edit-image.html.php | 3 +- system/admin/views/edit-link.html.php | 3 +- system/admin/views/edit-page.html.php | 3 +- system/admin/views/edit-post.html.php | 3 +- system/admin/views/edit-profile.html.php | 3 +- system/admin/views/edit-quote.html.php | 3 +- system/admin/views/edit-video.html.php | 3 +- system/htmly.php | 4 +- system/includes/functions.php | 52 +- 23 files changed, 965 insertions(+), 21 deletions(-) create mode 100644 system/admin/editor/js/Markdown.Extra.js rename system/admin/editor/js/{image.js => editor.js} (97%) create mode 100644 system/admin/editor/js/node-pagedown-extra.js diff --git a/config/config.ini.example b/config/config.ini.example index 6b00c14..f054d19 100644 --- a/config/config.ini.example +++ b/config/config.ini.example @@ -107,6 +107,7 @@ views.counter = "true" sitemap.priority.base = "1.0" sitemap.priority.post = "0.5" sitemap.priority.static = "0.5" +sitemap.priority.category = "0.5" sitemap.priority.tag = "0.5" sitemap.priority.archiveDay = "0.5" sitemap.priority.archiveMonth = "0.5" diff --git a/system/admin/editor/js/Markdown.Extra.js b/system/admin/editor/js/Markdown.Extra.js new file mode 100644 index 0000000..d3b859a --- /dev/null +++ b/system/admin/editor/js/Markdown.Extra.js @@ -0,0 +1,874 @@ +(function () { + // A quick way to make sure we're only keeping span-level tags when we need to. + // This isn't supposed to be foolproof. It's just a quick way to make sure we + // keep all span-level tags returned by a pagedown converter. It should allow + // all span-level tags through, with or without attributes. + var inlineTags = new RegExp(['^(<\\/?(a|abbr|acronym|applet|area|b|basefont|', + 'bdo|big|button|cite|code|del|dfn|em|figcaption|', + 'font|i|iframe|img|input|ins|kbd|label|map|', + 'mark|meter|object|param|progress|q|ruby|rp|rt|s|', + 'samp|script|select|small|span|strike|strong|', + 'sub|sup|textarea|time|tt|u|var|wbr)[^>]*>|', + '<(br)\\s?\\/?>)$'].join(''), 'i'); + + /****************************************************************** + * Utility Functions * + *****************************************************************/ + + // patch for ie7 + if (!Array.indexOf) { + Array.prototype.indexOf = function(obj) { + for (var i = 0; i < this.length; i++) { + if (this[i] == obj) { + return i; + } + } + return -1; + }; + } + + function trim(str) { + return str.replace(/^\s+|\s+$/g, ''); + } + + function rtrim(str) { + return str.replace(/\s+$/g, ''); + } + + // Remove one level of indentation from text. Indent is 4 spaces. + function outdent(text) { + return text.replace(new RegExp('^(\\t|[ ]{1,4})', 'gm'), ''); + } + + function contains(str, substr) { + return str.indexOf(substr) != -1; + } + + // Sanitize html, removing tags that aren't in the whitelist + function sanitizeHtml(html, whitelist) { + return html.replace(/<[^>]*>?/gi, function(tag) { + return tag.match(whitelist) ? tag : ''; + }); + } + + // Merge two arrays, keeping only unique elements. + function union(x, y) { + var obj = {}; + for (var i = 0; i < x.length; i++) + obj[x[i]] = x[i]; + for (i = 0; i < y.length; i++) + obj[y[i]] = y[i]; + var res = []; + for (var k in obj) { + if (obj.hasOwnProperty(k)) + res.push(obj[k]); + } + return res; + } + + // JS regexes don't support \A or \Z, so we add sentinels, as Pagedown + // does. In this case, we add the ascii codes for start of text (STX) and + // end of text (ETX), an idea borrowed from: + // https://github.com/tanakahisateru/js-markdown-extra + function addAnchors(text) { + if(text.charAt(0) != '\x02') + text = '\x02' + text; + if(text.charAt(text.length - 1) != '\x03') + text = text + '\x03'; + return text; + } + + // Remove STX and ETX sentinels. + function removeAnchors(text) { + if(text.charAt(0) == '\x02') + text = text.substr(1); + if(text.charAt(text.length - 1) == '\x03') + text = text.substr(0, text.length - 1); + return text; + } + + // Convert markdown within an element, retaining only span-level tags + function convertSpans(text, extra) { + return sanitizeHtml(convertAll(text, extra), inlineTags); + } + + // Convert internal markdown using the stock pagedown converter + function convertAll(text, extra) { + var result = extra.blockGamutHookCallback(text); + // We need to perform these operations since we skip the steps in the converter + result = unescapeSpecialChars(result); + result = result.replace(/~D/g, "$$").replace(/~T/g, "~"); + result = extra.previousPostConversion(result); + return result; + } + + // Convert escaped special characters + function processEscapesStep1(text) { + // Markdown extra adds two escapable characters, `:` and `|` + return text.replace(/\\\|/g, '~I').replace(/\\:/g, '~i'); + } + function processEscapesStep2(text) { + return text.replace(/~I/g, '|').replace(/~i/g, ':'); + } + + // Duplicated from PageDown converter + function unescapeSpecialChars(text) { + // Swap back in all the special characters we've hidden. + text = text.replace(/~E(\d+)E/g, function(wholeMatch, m1) { + var charCodeToReplace = parseInt(m1); + return String.fromCharCode(charCodeToReplace); + }); + return text; + } + + function slugify(text) { + return text.toLowerCase() + .replace(/\s+/g, '-') // Replace spaces with - + .replace(/[^\w\-]+/g, '') // Remove all non-word chars + .replace(/\-\-+/g, '-') // Replace multiple - with single - + .replace(/^-+/, '') // Trim - from start of text + .replace(/-+$/, ''); // Trim - from end of text + } + + /***************************************************************************** + * Markdown.Extra * + ****************************************************************************/ + + Markdown.Extra = function() { + // For converting internal markdown (in tables for instance). + // This is necessary since these methods are meant to be called as + // preConversion hooks, and the Markdown converter passed to init() + // won't convert any markdown contained in the html tags we return. + this.converter = null; + + // Stores html blocks we generate in hooks so that + // they're not destroyed if the user is using a sanitizing converter + this.hashBlocks = []; + + // Stores footnotes + this.footnotes = {}; + this.usedFootnotes = []; + + // Special attribute blocks for fenced code blocks and headers enabled. + this.attributeBlocks = false; + + // Fenced code block options + this.googleCodePrettify = false; + this.highlightJs = false; + + // Table options + this.tableClass = ''; + + this.tabWidth = 4; + }; + + Markdown.Extra.init = function(converter, options) { + // Each call to init creates a new instance of Markdown.Extra so it's + // safe to have multiple converters, with different options, on a single page + var extra = new Markdown.Extra(); + var postNormalizationTransformations = []; + var preBlockGamutTransformations = []; + var postSpanGamutTransformations = []; + var postConversionTransformations = ["unHashExtraBlocks"]; + + options = options || {}; + options.extensions = options.extensions || ["all"]; + if (contains(options.extensions, "all")) { + options.extensions = ["tables", "fenced_code_gfm", "def_list", "attr_list", "footnotes", "smartypants", "strikethrough", "newlines"]; + } + preBlockGamutTransformations.push("wrapHeaders"); + if (contains(options.extensions, "attr_list")) { + postNormalizationTransformations.push("hashFcbAttributeBlocks"); + preBlockGamutTransformations.push("hashHeaderAttributeBlocks"); + postConversionTransformations.push("applyAttributeBlocks"); + extra.attributeBlocks = true; + } + if (contains(options.extensions, "fenced_code_gfm")) { + // This step will convert fcb inside list items and blockquotes + preBlockGamutTransformations.push("fencedCodeBlocks"); + // This extra step is to prevent html blocks hashing and link definition/footnotes stripping inside fcb + postNormalizationTransformations.push("fencedCodeBlocks"); + } + if (contains(options.extensions, "tables")) { + preBlockGamutTransformations.push("tables"); + } + if (contains(options.extensions, "def_list")) { + preBlockGamutTransformations.push("definitionLists"); + } + if (contains(options.extensions, "footnotes")) { + postNormalizationTransformations.push("stripFootnoteDefinitions"); + preBlockGamutTransformations.push("doFootnotes"); + postConversionTransformations.push("printFootnotes"); + } + if (contains(options.extensions, "smartypants")) { + postConversionTransformations.push("runSmartyPants"); + } + if (contains(options.extensions, "strikethrough")) { + postSpanGamutTransformations.push("strikethrough"); + } + if (contains(options.extensions, "newlines")) { + postSpanGamutTransformations.push("newlines"); + } + + converter.hooks.chain("postNormalization", function(text) { + return extra.doTransform(postNormalizationTransformations, text) + '\n'; + }); + + converter.hooks.chain("preBlockGamut", function(text, blockGamutHookCallback) { + // Keep a reference to the block gamut callback to run recursively + extra.blockGamutHookCallback = blockGamutHookCallback; + text = processEscapesStep1(text); + text = extra.doTransform(preBlockGamutTransformations, text) + '\n'; + text = processEscapesStep2(text); + return text; + }); + + converter.hooks.chain("postSpanGamut", function(text) { + return extra.doTransform(postSpanGamutTransformations, text); + }); + + // Keep a reference to the hook chain running before doPostConversion to apply on hashed extra blocks + extra.previousPostConversion = converter.hooks.postConversion; + converter.hooks.chain("postConversion", function(text) { + text = extra.doTransform(postConversionTransformations, text); + // Clear state vars that may use unnecessary memory + extra.hashBlocks = []; + extra.footnotes = {}; + extra.usedFootnotes = []; + return text; + }); + + if ("highlighter" in options) { + extra.googleCodePrettify = options.highlighter === 'prettify'; + extra.highlightJs = options.highlighter === 'highlight'; + } + + if ("table_class" in options) { + extra.tableClass = options.table_class; + } + + extra.converter = converter; + + // Caller usually won't need this, but it's handy for testing. + return extra; + }; + + // Do transformations + Markdown.Extra.prototype.doTransform = function(transformations, text) { + for(var i = 0; i < transformations.length; i++) + text = this[transformations[i]](text); + return text; + }; + + // Return a placeholder containing a key, which is the block's index in the + // hashBlocks array. We wrap our output in a

tag here so Pagedown won't. + Markdown.Extra.prototype.hashExtraBlock = function(block) { + return '\n

~X' + (this.hashBlocks.push(block) - 1) + 'X

\n'; + }; + Markdown.Extra.prototype.hashExtraInline = function(block) { + return '~X' + (this.hashBlocks.push(block) - 1) + 'X'; + }; + + // Replace placeholder blocks in `text` with their corresponding + // html blocks in the hashBlocks array. + Markdown.Extra.prototype.unHashExtraBlocks = function(text) { + var self = this; + function recursiveUnHash() { + var hasHash = false; + text = text.replace(/(?:

)?~X(\d+)X(?:<\/p>)?/g, function(wholeMatch, m1) { + hasHash = true; + var key = parseInt(m1, 10); + return self.hashBlocks[key]; + }); + if(hasHash === true) { + recursiveUnHash(); + } + } + recursiveUnHash(); + return text; + }; + + // Wrap headers to make sure they won't be in def lists + Markdown.Extra.prototype.wrapHeaders = function(text) { + function wrap(text) { + return '\n' + text + '\n'; + } + text = text.replace(/^.+[ \t]*\n=+[ \t]*\n+/gm, wrap); + text = text.replace(/^.+[ \t]*\n-+[ \t]*\n+/gm, wrap); + text = text.replace(/^\#{1,6}[ \t]*.+?[ \t]*\#*\n+/gm, wrap); + return text; + }; + + + /****************************************************************** + * Attribute Blocks * + *****************************************************************/ + + // TODO: use sentinels. Should we just add/remove them in doConversion? + // TODO: better matches for id / class attributes + var attrBlock = "\\{[ \\t]*((?:[#.][-_:a-zA-Z0-9]+[ \\t]*)+)\\}"; + var hdrAttributesA = new RegExp("^(#{1,6}.*#{0,6})[ \\t]+" + attrBlock + "[ \\t]*(?:\\n|0x03)", "gm"); + var hdrAttributesB = new RegExp("^(.*)[ \\t]+" + attrBlock + "[ \\t]*\\n" + + "(?=[\\-|=]+\\s*(?:\\n|0x03))", "gm"); // underline lookahead + var fcbAttributes = new RegExp("^(```[^`\\n]*)[ \\t]+" + attrBlock + "[ \\t]*\\n" + + "(?=([\\s\\S]*?)\\n```[ \\t]*(\\n|0x03))", "gm"); + + // Extract headers attribute blocks, move them above the element they will be + // applied to, and hash them for later. + Markdown.Extra.prototype.hashHeaderAttributeBlocks = function(text) { + + var self = this; + function attributeCallback(wholeMatch, pre, attr) { + return '

~XX' + (self.hashBlocks.push(attr) - 1) + 'XX

\n' + pre + "\n"; + } + + text = text.replace(hdrAttributesA, attributeCallback); // ## headers + text = text.replace(hdrAttributesB, attributeCallback); // underline headers + return text; + }; + + // Extract FCB attribute blocks, move them above the element they will be + // applied to, and hash them for later. + Markdown.Extra.prototype.hashFcbAttributeBlocks = function(text) { + // TODO: use sentinels. Should we just add/remove them in doConversion? + // TODO: better matches for id / class attributes + + var self = this; + function attributeCallback(wholeMatch, pre, attr) { + return '

~XX' + (self.hashBlocks.push(attr) - 1) + 'XX

\n' + pre + "\n"; + } + + return text.replace(fcbAttributes, attributeCallback); + }; + + Markdown.Extra.prototype.applyAttributeBlocks = function(text) { + var self = this; + var blockRe = new RegExp('

~XX(\\d+)XX

[\\s]*' + + '(?:<(h[1-6]|pre)(?: +class="(\\S+)")?(>[\\s\\S]*?))', "gm"); + text = text.replace(blockRe, function(wholeMatch, k, tag, cls, rest) { + if (!tag) // no following header or fenced code block. + return ''; + + // get attributes list from hash + var key = parseInt(k, 10); + var attributes = self.hashBlocks[key]; + + // get id + var id = attributes.match(/#[^\s#.]+/g) || []; + var idStr = id[0] ? ' id="' + id[0].substr(1, id[0].length - 1) + '"' : ''; + + // get classes and merge with existing classes + var classes = attributes.match(/\.[^\s#.]+/g) || []; + for (var i = 0; i < classes.length; i++) // Remove leading dot + classes[i] = classes[i].substr(1, classes[i].length - 1); + + var classStr = ''; + if (cls) + classes = union(classes, [cls]); + + if (classes.length > 0) + classStr = ' class="' + classes.join(' ') + '"'; + + return "<" + tag + idStr + classStr + rest; + }); + + return text; + }; + + /****************************************************************** + * Tables * + *****************************************************************/ + + // Find and convert Markdown Extra tables into html. + Markdown.Extra.prototype.tables = function(text) { + var self = this; + + var leadingPipe = new RegExp( + ['^' , + '[ ]{0,3}' , // Allowed whitespace + '[|]' , // Initial pipe + '(.+)\\n' , // $1: Header Row + + '[ ]{0,3}' , // Allowed whitespace + '[|]([ ]*[-:]+[-| :]*)\\n' , // $2: Separator + + '(' , // $3: Table Body + '(?:[ ]*[|].*\\n?)*' , // Table rows + ')', + '(?:\\n|$)' // Stop at final newline + ].join(''), + 'gm' + ); + + var noLeadingPipe = new RegExp( + ['^' , + '[ ]{0,3}' , // Allowed whitespace + '(\\S.*[|].*)\\n' , // $1: Header Row + + '[ ]{0,3}' , // Allowed whitespace + '([-:]+[ ]*[|][-| :]*)\\n' , // $2: Separator + + '(' , // $3: Table Body + '(?:.*[|].*\\n?)*' , // Table rows + ')' , + '(?:\\n|$)' // Stop at final newline + ].join(''), + 'gm' + ); + + text = text.replace(leadingPipe, doTable); + text = text.replace(noLeadingPipe, doTable); + + // $1 = header, $2 = separator, $3 = body + function doTable(match, header, separator, body, offset, string) { + // remove any leading pipes and whitespace + header = header.replace(/^ *[|]/m, ''); + separator = separator.replace(/^ *[|]/m, ''); + body = body.replace(/^ *[|]/gm, ''); + + // remove trailing pipes and whitespace + header = header.replace(/[|] *$/m, ''); + separator = separator.replace(/[|] *$/m, ''); + body = body.replace(/[|] *$/gm, ''); + + // determine column alignments + var alignspecs = separator.split(/ *[|] */); + var align = []; + for (var i = 0; i < alignspecs.length; i++) { + var spec = alignspecs[i]; + if (spec.match(/^ *-+: *$/m)) + align[i] = ' align="right"'; + else if (spec.match(/^ *:-+: *$/m)) + align[i] = ' align="center"'; + else if (spec.match(/^ *:-+ *$/m)) + align[i] = ' align="left"'; + else align[i] = ''; + } + + // TODO: parse spans in header and rows before splitting, so that pipes + // inside of tags are not interpreted as separators + var headers = header.split(/ *[|] */); + var colCount = headers.length; + + // build html + var cls = self.tableClass ? ' class="' + self.tableClass + '"' : ''; + var html = ['\n', '\n', '\n'].join(''); + + // build column headers. + for (i = 0; i < colCount; i++) { + var headerHtml = convertSpans(trim(headers[i]), self); + html += [" ", headerHtml, "\n"].join(''); + } + html += "\n\n"; + + // build rows + var rows = body.split('\n'); + for (i = 0; i < rows.length; i++) { + if (rows[i].match(/^\s*$/)) // can apply to final row + continue; + + // ensure number of rowCells matches colCount + var rowCells = rows[i].split(/ *[|] */); + var lenDiff = colCount - rowCells.length; + for (var j = 0; j < lenDiff; j++) + rowCells.push(''); + + html += "\n"; + for (j = 0; j < colCount; j++) { + var colHtml = convertSpans(trim(rowCells[j]), self); + html += [" ", colHtml, "\n"].join(''); + } + html += "\n"; + } + + html += "\n"; + + // replace html with placeholder until postConversion step + return self.hashExtraBlock(html); + } + + return text; + }; + + + /****************************************************************** + * Footnotes * + *****************************************************************/ + + // Strip footnote, store in hashes. + Markdown.Extra.prototype.stripFootnoteDefinitions = function(text) { + var self = this; + + text = text.replace( + /\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/g, + function(wholeMatch, m1, m2) { + m1 = slugify(m1); + m2 += "\n"; + m2 = m2.replace(/^[ ]{0,3}/g, ""); + self.footnotes[m1] = m2; + return "\n"; + }); + + return text; + }; + + + // Find and convert footnotes references. + Markdown.Extra.prototype.doFootnotes = function(text) { + var self = this; + if(self.isConvertingFootnote === true) { + return text; + } + + var footnoteCounter = 0; + text = text.replace(/\[\^(.+?)\]/g, function(wholeMatch, m1) { + var id = slugify(m1); + var footnote = self.footnotes[id]; + if (footnote === undefined) { + return wholeMatch; + } + footnoteCounter++; + self.usedFootnotes.push(id); + var html = '' + footnoteCounter + + ''; + return self.hashExtraInline(html); + }); + + return text; + }; + + // Print footnotes at the end of the document + Markdown.Extra.prototype.printFootnotes = function(text) { + var self = this; + + if (self.usedFootnotes.length === 0) { + return text; + } + + text += '\n\n
\n
\n
    \n\n'; + for(var i=0; i' + + formattedfootnote + + ' \n\n'; + } + text += '
\n
'; + return text; + }; + + + /****************************************************************** + * Fenced Code Blocks (gfm) * + ******************************************************************/ + + // Find and convert gfm-inspired fenced code blocks into html. + Markdown.Extra.prototype.fencedCodeBlocks = function(text) { + function encodeCode(code) { + code = code.replace(/&/g, "&"); + code = code.replace(//g, ">"); + // These were escaped by PageDown before postNormalization + code = code.replace(/~D/g, "$$"); + code = code.replace(/~T/g, "~"); + return code; + } + + var self = this; + text = text.replace(/(?:^|\n)```([^`\n]*)\n([\s\S]*?)\n```[ \t]*(?=\n)/g, function(match, m1, m2) { + var language = trim(m1), codeblock = m2; + + // adhere to specified options + var preclass = self.googleCodePrettify ? ' class="prettyprint"' : ''; + var codeclass = ''; + if (language) { + if (self.googleCodePrettify || self.highlightJs) { + // use html5 language- class names. supported by both prettify and highlight.js + codeclass = ' class="language-' + language + '"'; + } else { + codeclass = ' class="' + language + '"'; + } + } + + var html = ['', + encodeCode(codeblock), ''].join(''); + + // replace codeblock with placeholder until postConversion step + return self.hashExtraBlock(html); + }); + + return text; + }; + + + /****************************************************************** + * SmartyPants * + ******************************************************************/ + + Markdown.Extra.prototype.educatePants = function(text) { + var self = this; + var result = ''; + var blockOffset = 0; + // Here we parse HTML in a very bad manner + text.replace(/(?:)|(<)([a-zA-Z1-6]+)([^\n]*?>)([\s\S]*?)(<\/\2>)/g, function(wholeMatch, m1, m2, m3, m4, m5, offset) { + var token = text.substring(blockOffset, offset); + result += self.applyPants(token); + self.smartyPantsLastChar = result.substring(result.length - 1); + blockOffset = offset + wholeMatch.length; + if(!m1) { + // Skip commentary + result += wholeMatch; + return; + } + // Skip special tags + if(!/code|kbd|pre|script|noscript|iframe|math|ins|del|pre/i.test(m2)) { + m4 = self.educatePants(m4); + } + else { + self.smartyPantsLastChar = m4.substring(m4.length - 1); + } + result += m1 + m2 + m3 + m4 + m5; + }); + var lastToken = text.substring(blockOffset); + result += self.applyPants(lastToken); + self.smartyPantsLastChar = result.substring(result.length - 1); + return result; + }; + + function revertPants(wholeMatch, m1) { + var blockText = m1; + blockText = blockText.replace(/&\#8220;/g, "\""); + blockText = blockText.replace(/&\#8221;/g, "\""); + blockText = blockText.replace(/&\#8216;/g, "'"); + blockText = blockText.replace(/&\#8217;/g, "'"); + blockText = blockText.replace(/&\#8212;/g, "---"); + blockText = blockText.replace(/&\#8211;/g, "--"); + blockText = blockText.replace(/&\#8230;/g, "..."); + return blockText; + } + + Markdown.Extra.prototype.applyPants = function(text) { + // Dashes + text = text.replace(/---/g, "—").replace(/--/g, "–"); + // Ellipses + text = text.replace(/\.\.\./g, "…").replace(/\.\s\.\s\./g, "…"); + // Backticks + text = text.replace(/``/g, "“").replace (/''/g, "”"); + + if(/^'$/.test(text)) { + // Special case: single-character ' token + if(/\S/.test(this.smartyPantsLastChar)) { + return "’"; + } + return "‘"; + } + if(/^"$/.test(text)) { + // Special case: single-character " token + if(/\S/.test(this.smartyPantsLastChar)) { + return "”"; + } + return "“"; + } + + // Special case if the very first character is a quote + // followed by punctuation at a non-word-break. Close the quotes by brute force: + text = text.replace (/^'(?=[!"#\$\%'()*+,\-.\/:;<=>?\@\[\\]\^_`{|}~]\B)/, "’"); + text = text.replace (/^"(?=[!"#\$\%'()*+,\-.\/:;<=>?\@\[\\]\^_`{|}~]\B)/, "”"); + + // Special case for double sets of quotes, e.g.: + //

He said, "'Quoted' words in a larger quote."

+ text = text.replace(/"'(?=\w)/g, "“‘"); + text = text.replace(/'"(?=\w)/g, "‘“"); + + // Special case for decade abbreviations (the '80s): + text = text.replace(/'(?=\d{2}s)/g, "’"); + + // Get most opening single quotes: + text = text.replace(/(\s| |--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)'(?=\w)/g, "$1‘"); + + // Single closing quotes: + text = text.replace(/([^\s\[\{\(\-])'/g, "$1’"); + text = text.replace(/'(?=\s|s\b)/g, "’"); + + // Any remaining single quotes should be opening ones: + text = text.replace(/'/g, "‘"); + + // Get most opening double quotes: + text = text.replace(/(\s| |--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)"(?=\w)/g, "$1“"); + + // Double closing quotes: + text = text.replace(/([^\s\[\{\(\-])"/g, "$1”"); + text = text.replace(/"(?=\s)/g, "”"); + + // Any remaining quotes should be opening ones. + text = text.replace(/"/ig, "“"); + return text; + }; + + // Find and convert markdown extra definition lists into html. + Markdown.Extra.prototype.runSmartyPants = function(text) { + this.smartyPantsLastChar = ''; + text = this.educatePants(text); + // Clean everything inside html tags (some of them may have been converted due to our rough html parsing) + text = text.replace(/(<([a-zA-Z1-6]+)\b([^\n>]*?)(\/)?>)/g, revertPants); + return text; + }; + + /****************************************************************** + * Definition Lists * + ******************************************************************/ + + // Find and convert markdown extra definition lists into html. + Markdown.Extra.prototype.definitionLists = function(text) { + var wholeList = new RegExp( + ['(\\x02\\n?|\\n\\n)' , + '(?:' , + '(' , // $1 = whole list + '(' , // $2 + '[ ]{0,3}' , + '((?:[ \\t]*\\S.*\\n)+)', // $3 = defined term + '\\n?' , + '[ ]{0,3}:[ ]+' , // colon starting definition + ')' , + '([\\s\\S]+?)' , + '(' , // $4 + '(?=\\0x03)' , // \z + '|' , + '(?=' , + '\\n{2,}' , + '(?=\\S)' , + '(?!' , // Negative lookahead for another term + '[ ]{0,3}' , + '(?:\\S.*\\n)+?' , // defined term + '\\n?' , + '[ ]{0,3}:[ ]+' , // colon starting definition + ')' , + '(?!' , // Negative lookahead for another definition + '[ ]{0,3}:[ ]+' , // colon starting definition + ')' , + ')' , + ')' , + ')' , + ')' + ].join(''), + 'gm' + ); + + var self = this; + text = addAnchors(text); + + text = text.replace(wholeList, function(match, pre, list) { + var result = trim(self.processDefListItems(list)); + result = "
\n" + result + "\n
"; + return pre + self.hashExtraBlock(result) + "\n\n"; + }); + + return removeAnchors(text); + }; + + // Process the contents of a single definition list, splitting it + // into individual term and definition list items. + Markdown.Extra.prototype.processDefListItems = function(listStr) { + var self = this; + + var dt = new RegExp( + ['(\\x02\\n?|\\n\\n+)' , // leading line + '(' , // definition terms = $1 + '[ ]{0,3}' , // leading whitespace + '(?![:][ ]|[ ])' , // negative lookahead for a definition + // mark (colon) or more whitespace + '(?:\\S.*\\n)+?' , // actual term (not whitespace) + ')' , + '(?=\\n?[ ]{0,3}:[ ])' // lookahead for following line feed + ].join(''), // with a definition mark + 'gm' + ); + + var dd = new RegExp( + ['\\n(\\n+)?' , // leading line = $1 + '(' , // marker space = $2 + '[ ]{0,3}' , // whitespace before colon + '[:][ ]+' , // definition mark (colon) + ')' , + '([\\s\\S]+?)' , // definition text = $3 + '(?=\\n*' , // stop at next definition mark, + '(?:' , // next term or end of text + '\\n[ ]{0,3}[:][ ]|' , + '
|\\x03' , // \z + ')' , + ')' + ].join(''), + 'gm' + ); + + listStr = addAnchors(listStr); + // trim trailing blank lines: + listStr = listStr.replace(/\n{2,}(?=\\x03)/, "\n"); + + // Process definition terms. + listStr = listStr.replace(dt, function(match, pre, termsStr) { + var terms = trim(termsStr).split("\n"); + var text = ''; + for (var i = 0; i < terms.length; i++) { + var term = terms[i]; + // process spans inside dt + term = convertSpans(trim(term), self); + text += "\n
" + term + "
"; + } + return text + "\n"; + }); + + // Process actual definitions. + listStr = listStr.replace(dd, function(match, leadingLine, markerSpace, def) { + if (leadingLine || def.match(/\n{2,}/)) { + // replace marker with the appropriate whitespace indentation + def = Array(markerSpace.length + 1).join(' ') + def; + // process markdown inside definition + // TODO?: currently doesn't apply extensions + def = outdent(def) + "\n\n"; + def = "\n" + convertAll(def, self) + "\n"; + } else { + // convert span-level markdown inside definition + def = rtrim(def); + def = convertSpans(outdent(def), self); + } + + return "\n
" + def + "
\n"; + }); + + return removeAnchors(listStr); + }; + + + /*********************************************************** + * Strikethrough * + ************************************************************/ + + Markdown.Extra.prototype.strikethrough = function(text) { + // Pretty much duplicated from _DoItalicsAndBold + return text.replace(/([\W_]|^)~T~T(?=\S)([^\r]*?\S[\*_]*)~T~T([\W_]|$)/g, + "$1$2$3"); + }; + + + /*********************************************************** + * New lines * + ************************************************************/ + + Markdown.Extra.prototype.newlines = function(text) { + // We have to ignore already converted newlines and line breaks in sub-list items + return text.replace(/(<(?:br|\/li)>)?\n/g, function(wholeMatch, previousTag) { + return previousTag ? wholeMatch : "
\n"; + }); + }; + +})(); + diff --git a/system/admin/editor/js/image.js b/system/admin/editor/js/editor.js similarity index 97% rename from system/admin/editor/js/image.js rename to system/admin/editor/js/editor.js index ddb5f2c..203d360 100644 --- a/system/admin/editor/js/image.js +++ b/system/admin/editor/js/editor.js @@ -1,6 +1,7 @@ (function () { var converter = new Markdown.Converter(); + Markdown.Extra.init(converter); var editor = new Markdown.Editor(converter); var $dialog = $('#insertImageDialog').dialog({ diff --git a/system/admin/editor/js/node-pagedown-extra.js b/system/admin/editor/js/node-pagedown-extra.js new file mode 100644 index 0000000..a19f1d9 --- /dev/null +++ b/system/admin/editor/js/node-pagedown-extra.js @@ -0,0 +1,3 @@ +GLOBAL.Markdown = {}; +require('./Markdown.Extra.js'); +exports.Extra = Markdown.Extra; diff --git a/system/admin/views/add-audio.html.php b/system/admin/views/add-audio.html.php index aaecfdf..5b0d722 100644 --- a/system/admin/views/add-audio.html.php +++ b/system/admin/views/add-audio.html.php @@ -4,6 +4,7 @@ + @@ -78,4 +79,4 @@ $desc = get_category_info(null);
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/add-category.html.php b/system/admin/views/add-category.html.php index 9175858..eb06667 100644 --- a/system/admin/views/add-category.html.php +++ b/system/admin/views/add-category.html.php @@ -4,6 +4,7 @@ + @@ -54,4 +55,4 @@
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/add-image.html.php b/system/admin/views/add-image.html.php index 55e4f64..f40e3ec 100644 --- a/system/admin/views/add-image.html.php +++ b/system/admin/views/add-image.html.php @@ -4,6 +4,7 @@ + @@ -78,4 +79,4 @@ $desc = get_category_info(null);
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/add-link.html.php b/system/admin/views/add-link.html.php index bb14f9f..8111c03 100644 --- a/system/admin/views/add-link.html.php +++ b/system/admin/views/add-link.html.php @@ -4,6 +4,7 @@ + @@ -78,4 +79,4 @@ $desc = get_category_info(null);
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/add-page.html.php b/system/admin/views/add-page.html.php index 2f96bc9..7ff663d 100644 --- a/system/admin/views/add-page.html.php +++ b/system/admin/views/add-page.html.php @@ -4,6 +4,7 @@ + @@ -54,4 +55,4 @@
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/add-post.html.php b/system/admin/views/add-post.html.php index 4f59913..b75abbb 100644 --- a/system/admin/views/add-post.html.php +++ b/system/admin/views/add-post.html.php @@ -4,6 +4,7 @@ + @@ -71,4 +72,4 @@ $desc = get_category_info(null);
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/add-quote.html.php b/system/admin/views/add-quote.html.php index 7430c0d..787b35d 100644 --- a/system/admin/views/add-quote.html.php +++ b/system/admin/views/add-quote.html.php @@ -4,6 +4,7 @@ + @@ -78,4 +79,4 @@ $desc = get_category_info(null);
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/add-video.html.php b/system/admin/views/add-video.html.php index fec5a57..b24afdb 100644 --- a/system/admin/views/add-video.html.php +++ b/system/admin/views/add-video.html.php @@ -4,6 +4,7 @@ + @@ -78,4 +79,4 @@ $desc = get_category_info(null);
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/edit-audio.html.php b/system/admin/views/edit-audio.html.php index 5fb00db..44919cd 100644 --- a/system/admin/views/edit-audio.html.php +++ b/system/admin/views/edit-audio.html.php @@ -55,6 +55,7 @@ if (config('permalink.type') == 'post') { + @@ -126,4 +127,4 @@ if (config('permalink.type') == 'post') {
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/edit-category.html.php b/system/admin/views/edit-category.html.php index 8bac6cd..f1730f1 100644 --- a/system/admin/views/edit-category.html.php +++ b/system/admin/views/edit-category.html.php @@ -38,6 +38,7 @@ else { + @@ -83,4 +84,4 @@ else {
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/edit-image.html.php b/system/admin/views/edit-image.html.php index d1be870..72d7ffe 100644 --- a/system/admin/views/edit-image.html.php +++ b/system/admin/views/edit-image.html.php @@ -55,6 +55,7 @@ if (config('permalink.type') == 'post') { + @@ -126,4 +127,4 @@ if (config('permalink.type') == 'post') {
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/edit-link.html.php b/system/admin/views/edit-link.html.php index 5322942..67042d5 100644 --- a/system/admin/views/edit-link.html.php +++ b/system/admin/views/edit-link.html.php @@ -56,6 +56,7 @@ if (config('permalink.type') == 'post') { + @@ -127,4 +128,4 @@ if (config('permalink.type') == 'post') {
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/edit-page.html.php b/system/admin/views/edit-page.html.php index 3a41ffa..dde61ce 100644 --- a/system/admin/views/edit-page.html.php +++ b/system/admin/views/edit-page.html.php @@ -38,6 +38,7 @@ else { + @@ -83,4 +84,4 @@ else {
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/edit-post.html.php b/system/admin/views/edit-post.html.php index ca415d2..384c868 100644 --- a/system/admin/views/edit-post.html.php +++ b/system/admin/views/edit-post.html.php @@ -55,6 +55,7 @@ if (config('permalink.type') == 'post') { + @@ -121,4 +122,4 @@ if (config('permalink.type') == 'post') {
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/edit-profile.html.php b/system/admin/views/edit-profile.html.php index 36624fe..0ab68f3 100644 --- a/system/admin/views/edit-profile.html.php +++ b/system/admin/views/edit-profile.html.php @@ -22,6 +22,7 @@ if (file_exists($filename)) { + @@ -62,4 +63,4 @@ if (file_exists($filename)) {
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/edit-quote.html.php b/system/admin/views/edit-quote.html.php index 06ced01..f8f748f 100644 --- a/system/admin/views/edit-quote.html.php +++ b/system/admin/views/edit-quote.html.php @@ -56,6 +56,7 @@ if (config('permalink.type') == 'post') { + @@ -127,4 +128,4 @@ if (config('permalink.type') == 'post') {
- \ No newline at end of file + \ No newline at end of file diff --git a/system/admin/views/edit-video.html.php b/system/admin/views/edit-video.html.php index 80664da..06e93ff 100644 --- a/system/admin/views/edit-video.html.php +++ b/system/admin/views/edit-video.html.php @@ -57,6 +57,7 @@ if (config('permalink.type') == 'post') { + @@ -128,4 +129,4 @@ if (config('permalink.type') == 'post') {
- \ No newline at end of file + \ No newline at end of file diff --git a/system/htmly.php b/system/htmly.php index 40b97c5..c020f19 100644 --- a/system/htmly.php +++ b/system/htmly.php @@ -1955,7 +1955,7 @@ post('/post/:name/delete', function () { // Show various page (top-level), admin, login, sitemap, static page. get('/:static', function ($static) { - if ($static === 'sitemap.xml' || $static === 'sitemap.base.xml' || $static === 'sitemap.post.xml' || $static === 'sitemap.static.xml' || $static === 'sitemap.tag.xml' || $static === 'sitemap.archive.xml' || $static === 'sitemap.author.xml') { + if ($static === 'sitemap.xml' || $static === 'sitemap.base.xml' || $static === 'sitemap.post.xml' || $static === 'sitemap.static.xml' || $static === 'sitemap.tag.xml' || $static === 'sitemap.archive.xml' || $static === 'sitemap.author.xml' || $static === 'sitemap.category.xml') { header('Content-Type: text/xml'); @@ -1973,6 +1973,8 @@ get('/:static', function ($static) { generate_sitemap('archive'); } elseif ($static === 'sitemap.author.xml') { generate_sitemap('author'); + } elseif ($static === 'sitemap.category.xml') { + generate_sitemap('category'); } die; diff --git a/system/includes/functions.php b/system/includes/functions.php index e2a2ecd..518775e 100644 --- a/system/includes/functions.php +++ b/system/includes/functions.php @@ -453,9 +453,9 @@ function get_category($category, $page, $perpage) $posts = get_post_sorted(); $tmp = array(); - + if (empty($perpage)) { - $perpage = 10; + $perpage = 10; } foreach ($posts as $index => $v) { @@ -1920,6 +1920,10 @@ function generate_sitemap($str) if (config('sitemap.priority.static') !== 'false') { echo '' . site_url() . 'sitemap.static.xml'; } + + if (config('sitemap.priority.category') !== 'false') { + echo '' . site_url() . 'sitemap.category.xml'; + } if (config('sitemap.priority.tag') !== 'false') { echo '' . site_url() . 'sitemap.tag.xml'; @@ -2091,6 +2095,48 @@ function generate_sitemap($str) } } + echo ''; + + } elseif ($str == 'category') { + + $priority = (config('sitemap.priority.category')) ? config('sitemap.priority.category') : $default_priority; + + $posts = array(); + if ($priority !== 'false') { + $posts = get_post_unsorted(); + } + + $cats = array(); + + echo ''; + + if($posts) { + foreach ($posts as $index => $v) { + + $arr = explode('_', $v); + + $replaced = substr($arr[0], 0, strrpos($arr[0], '/')) . '/'; + + $str = explode('/', $replaced); + + $cats[] = $str[count($str) - 3]; + + } + + foreach ($cats as $c) { + $cat[] = site_url() . 'category/' . strtolower($c); + } + + if (isset($cat)) { + + $cat = array_unique($cat, SORT_REGULAR); + + foreach ($cat as $c) { + echo '' . $c . '' . $priority . ''; + } + } + } + echo ''; } } @@ -2716,7 +2762,7 @@ function migrate_old_content() } } - + $dir = 'content/data/'; if (!is_dir($dir)) { mkdir($dir, 0775, true);