apostrophe
Version:
Apostrophe is a user-friendly content management system. This core module of Apostrophe provides rich content editing and essential facilities to integrate Apostrophe into your Express project. Apostrophe also includes simple facilities for storing your r
209 lines (188 loc) • 6.76 kB
JavaScript
var _ = require('lodash');
var he = require('he');
var extend = require('extend');
_.str = require('underscore.string');
var XRegExp = require('xregexp').XRegExp;
/**
* utils
* @augments Augments the apos object with short methods providing constantly used
* functionality when developing Apostrophe, such as escaping HTML, doing global replaces of strings,
* converting a string to a slug, converting a string to a regular expression that searches
* for it, converting a mixed case name to a hyphenated ("CSS") name, etc. Anything more complex
* and/or less frequently needed should be moved out to a separate file. Busting these
* out to separate npm modules in future is a good idea.
*/
module.exports = function(self) {
// Globally replace a string with another string.
// Regular `String.replace` does NOT offer global replace, except
// when using regular expressions, which are great (and fast) but
// sometimes problematic when UTF8 characters may be present.
self.globalReplace = function(haystack, needle, replacement) {
var result = '';
while (true) {
if (!haystack.length) {
return result;
}
var index = haystack.indexOf(needle);
if (index === -1) {
result += haystack;
return result;
}
result += haystack.substr(0, index);
result += replacement;
haystack = haystack.substr(index + needle.length);
}
};
/**
* Truncate a plaintext string at the specified number of
* characters without breaking words if possible, see
* underscore.string's prune function, of which this is
* a copy of (only replacing RegExp with XRegExp for
* better UTF-8 support)
*/
self.truncatePlaintext = function(str, length, pruneStr){
if (str == null) return '';
str = String(str); length = ~~length;
pruneStr = pruneStr != null ? String(pruneStr) : '...';
if (str.length <= length) return str;
var r = '.(?=\W*\w*$)';
var regex = new XRegExp(r, 'g');
var tmpl = function(c){ return c.toUpperCase() !== c.toLowerCase() ? 'A' : ' '; },
template = str.slice(0, length+1);
template = XRegExp.replace(template, regex, tmpl); // 'Hello, world' -> 'HellAA AAAAA'
if (template.slice(template.length-2).match(/\w\w/))
template = template.replace(/\s*\S+$/, '');
else
template = _.str.rtrim(template.slice(0, template.length-1));
return (template+pruneStr).length > str.length ? str : str.slice(0, template.length)+pruneStr;
};
// Escape a plaintext string correctly for use in HTML.
// If { pretty: true } is in the options object,
// newlines become br tags, and URLs become links to
// those URLs. Otherwise we just do basic escaping.
//
// If { single: true } is in the options object,
// single-quotes are escaped, otherwise double-quotes
// are escaped.
//
// For bc, if the second argument is truthy and not an
// object, { pretty: true } is assumed.
self.escapeHtml = function(s, options) {
// for bc
if (options && (typeof(options) !== 'object')) {
options = { pretty: true };
}
options = options || {};
if (s === 'undefined') {
s = '';
}
if (typeof(s) !== 'string') {
s = s + '';
}
s = s.replace(/\&/g, '&').replace(/</g, '<').replace(/\>/g, '>');
if (options.single) {
s = s.replace(/\'/g, ''');
} else {
s = s.replace(/\"/g, '"');
}
if (options.pretty) {
s = s.replace(/\r?\n/g, "<br />");
// URLs to links. Careful, newlines are already <br /> at this point
s = s.replace(/https?\:[^\s<]+/g, function(match) {
match = match.trim();
return '<a href="' + self.escapeHtml(match) + '">' + match + '</a>';
});
}
return s;
};
// Convert HTML to true plaintext, with all entities decoded
self.htmlToPlaintext = function(html) {
// The awesomest HTML renderer ever (look out webkit):
// block element opening tags = newlines, closing tags and non-container tags just gone
html = html.replace(/<\/.*?\>/g, '');
html = html.replace(/<(h1|h2|h3|h4|h5|h6|p|br|blockquote|li|article|address|footer|pre|header|table|tr|td|th|tfoot|thead|div|dl|dt|dd).*?\>/gi, '\n');
html = html.replace(/<.*?\>/g, '');
return he.decode(html);
};
/**
* Capitalize the first letter of a string
*/
self.capitalizeFirst = function(s) {
return s.charAt(0).toUpperCase() + s.substr(1);
};
// Convert other name formats such as underscore and camelCase to a hyphenated css-style
// name. KEEP IN SYNC WITH BROWSER SIDE VERSION in content.js
self.cssName = function(camel) {
// Keep in sync with client side version
var i;
var css = '';
var dash = false;
for (i = 0; (i < camel.length); i++) {
var c = camel.charAt(i);
var lower = ((c >= 'a') && (c <= 'z'));
var upper = ((c >= 'A') && (c <= 'Z'));
var digit = ((c >= '0') && (c <= '9'));
if (!(lower || upper || digit)) {
dash = true;
continue;
}
if (upper) {
if (i > 0) {
dash = true;
}
c = c.toLowerCase();
}
if (dash) {
css += '-';
dash = false;
}
css += c;
}
return css;
};
// Accepts a camel-case type name such as blog and returns a browser-side
// constructor function name such as AposBlog
self.constructorName = function(camel) {
return 'Apos' + camel.charAt(0).toUpperCase() + camel.substring(1);
};
// Convert a name to camel case.
//
// Useful in converting CSV with friendly headings into sensible property names.
//
// Only digits and ASCII letters remain.
//
// Anything that isn't a digit or an ASCII letter prompts the next character
// to be uppercase. Existing uppercase letters also trigger uppercase, unless
// they are the first character; this preserves existing camelCase names.
self.camelName = function(s) {
// Keep in sync with client side version
var i;
var n = '';
var nextUp = false;
for (i = 0; (i < s.length); i++) {
var c = s.charAt(i);
// If the next character is already uppercase, preserve that, unless
// it is the first character
if ((i > 0) && c.match(/[A-Z]/)) {
nextUp = true;
}
if (c.match(/[A-Za-z0-9]/)) {
if (nextUp) {
n += c.toUpperCase();
nextUp = false;
} else {
n += c.toLowerCase();
}
} else {
nextUp = true;
}
}
return n;
};
// Add a slash to a path, but only if it does not already end in a slash
self.addSlashIfNeeded = function(path) {
path += '/';
path = path.replace(/\/\/$/, '/');
return path;
};
};