2012-04-30 11:23:03 +00:00
|
|
|
/*\
|
2012-07-14 14:57:36 +00:00
|
|
|
title: $:/core/modules/utils/utils.js
|
2012-04-30 11:23:03 +00:00
|
|
|
type: application/javascript
|
|
|
|
module-type: utils
|
|
|
|
|
|
|
|
Various static utility functions.
|
|
|
|
|
|
|
|
\*/
|
|
|
|
(function(){
|
|
|
|
|
2012-05-04 17:49:04 +00:00
|
|
|
/*jslint node: true, browser: true */
|
|
|
|
/*global $tw: false */
|
2012-04-30 11:23:03 +00:00
|
|
|
"use strict";
|
|
|
|
|
2012-05-08 14:11:29 +00:00
|
|
|
/*
|
|
|
|
Push entries onto an array, removing them first if they already exist in the array
|
|
|
|
array: array to modify
|
|
|
|
value: a single value to push or an array of values to push
|
|
|
|
*/
|
|
|
|
exports.pushTop = function(array,value) {
|
|
|
|
var t,p;
|
|
|
|
if($tw.utils.isArray(value)) {
|
|
|
|
// Remove any array entries that are duplicated in the new values
|
|
|
|
for(t=0; t<value.length; t++) {
|
|
|
|
p = array.indexOf(value[t]);
|
|
|
|
if(p !== -1) {
|
|
|
|
array.splice(p,1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Push the values on top of the main array
|
|
|
|
array.push.apply(array,value);
|
|
|
|
} else {
|
|
|
|
p = array.indexOf(value);
|
|
|
|
if(p !== -1) {
|
|
|
|
array.splice(p,1);
|
|
|
|
}
|
|
|
|
array.push(value);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Remove entries from an array
|
|
|
|
array: array to modify
|
|
|
|
value: a single value to remove, or an array of values to remove
|
|
|
|
*/
|
|
|
|
exports.removeArrayEntries = function(array,value) {
|
|
|
|
var t,p;
|
|
|
|
if($tw.utils.isArray(value)) {
|
|
|
|
for(t=0; t<value.length; t++) {
|
|
|
|
p = array.indexOf(value[t]);
|
|
|
|
if(p !== -1) {
|
|
|
|
array.splice(p,1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
p = array.indexOf(value);
|
|
|
|
if(p !== -1) {
|
|
|
|
array.splice(p,1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-05-03 20:47:16 +00:00
|
|
|
exports.deepCopy = function(object) {
|
|
|
|
var result,t;
|
|
|
|
if($tw.utils.isArray(object)) {
|
|
|
|
// Copy arrays
|
|
|
|
result = object.slice(0);
|
|
|
|
} else if(typeof object === "object") {
|
|
|
|
result = {};
|
|
|
|
for(t in object) {
|
|
|
|
if(object[t] !== undefined) {
|
|
|
|
result[t] = $tw.utils.deepCopy(object[t]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
result = object;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.extendDeepCopy = function(object,extendedProperties) {
|
|
|
|
var result = $tw.utils.deepCopy(object),t;
|
2012-05-04 17:49:04 +00:00
|
|
|
for(t in extendedProperties) {
|
2012-05-05 10:21:15 +00:00
|
|
|
if(extendedProperties[t] !== undefined) {
|
|
|
|
result[t] = $tw.utils.deepCopy(extendedProperties[t]);
|
2012-05-03 20:47:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2012-04-30 11:23:03 +00:00
|
|
|
exports.formatDateString = function (date,template) {
|
|
|
|
var t = template.replace(/0hh12/g,$tw.utils.pad($tw.utils.getHours12(date)));
|
|
|
|
t = t.replace(/hh12/g,$tw.utils.getHours12(date));
|
|
|
|
t = t.replace(/0hh/g,$tw.utils.pad(date.getHours()));
|
|
|
|
t = t.replace(/hh/g,date.getHours());
|
|
|
|
t = t.replace(/mmm/g,$tw.config.dateFormats.shortMonths[date.getMonth()]);
|
|
|
|
t = t.replace(/0mm/g,$tw.utils.pad(date.getMinutes()));
|
|
|
|
t = t.replace(/mm/g,date.getMinutes());
|
|
|
|
t = t.replace(/0ss/g,$tw.utils.pad(date.getSeconds()));
|
|
|
|
t = t.replace(/ss/g,date.getSeconds());
|
|
|
|
t = t.replace(/[ap]m/g,$tw.utils.getAmPm(date).toLowerCase());
|
|
|
|
t = t.replace(/[AP]M/g,$tw.utils.getAmPm(date).toUpperCase());
|
|
|
|
t = t.replace(/wYYYY/g,$tw.utils.getYearForWeekNo(date));
|
|
|
|
t = t.replace(/wYY/g,$tw.utils.pad($tw.utils.getYearForWeekNo(date)-2000));
|
|
|
|
t = t.replace(/YYYY/g,date.getFullYear());
|
|
|
|
t = t.replace(/YY/g,$tw.utils.pad(date.getFullYear()-2000));
|
|
|
|
t = t.replace(/MMM/g,$tw.config.dateFormats.months[date.getMonth()]);
|
|
|
|
t = t.replace(/0MM/g,$tw.utils.pad(date.getMonth()+1));
|
|
|
|
t = t.replace(/MM/g,date.getMonth()+1);
|
|
|
|
t = t.replace(/0WW/g,$tw.utils.pad($tw.utils.getWeek(date)));
|
|
|
|
t = t.replace(/WW/g,$tw.utils.getWeek(date));
|
|
|
|
t = t.replace(/DDD/g,$tw.config.dateFormats.days[date.getDay()]);
|
|
|
|
t = t.replace(/ddd/g,$tw.config.dateFormats.shortDays[date.getDay()]);
|
|
|
|
t = t.replace(/0DD/g,$tw.utils.pad(date.getDate()));
|
|
|
|
t = t.replace(/DDth/g,date.getDate()+$tw.utils.getDaySuffix(date));
|
|
|
|
t = t.replace(/DD/g,date.getDate());
|
|
|
|
var tz = date.getTimezoneOffset();
|
|
|
|
var atz = Math.abs(tz);
|
|
|
|
t = t.replace(/TZD/g,(tz < 0 ? '+' : '-') + $tw.utils.pad(Math.floor(atz / 60)) + ':' + $tw.utils.pad(atz % 60));
|
|
|
|
t = t.replace(/\\/g,"");
|
|
|
|
return t;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.getAmPm = function(date) {
|
|
|
|
return date.getHours() >= 12 ? $tw.config.dateFormats.pm : $tw.config.dateFormats.am;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.getDaySuffix = function(date) {
|
|
|
|
return $tw.config.dateFormats.daySuffixes[date.getDate()-1];
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.getWeek = function(date) {
|
|
|
|
var dt = new Date(date.getTime());
|
|
|
|
var d = dt.getDay();
|
|
|
|
if(d === 0) d=7;// JavaScript Sun=0, ISO Sun=7
|
|
|
|
dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week to calculate weekNo
|
|
|
|
var n = Math.floor((dt.getTime()-new Date(dt.getFullYear(),0,1)+3600000)/86400000);
|
|
|
|
return Math.floor(n/7)+1;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.getYearForWeekNo = function(date) {
|
|
|
|
var dt = new Date(date.getTime());
|
|
|
|
var d = dt.getDay();
|
|
|
|
if(d === 0) d=7;// JavaScript Sun=0, ISO Sun=7
|
|
|
|
dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week
|
|
|
|
return dt.getFullYear();
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.getHours12 = function(date) {
|
|
|
|
var h = date.getHours();
|
|
|
|
return h > 12 ? h-12 : ( h > 0 ? h : 12 );
|
|
|
|
};
|
|
|
|
|
2012-10-20 13:23:57 +00:00
|
|
|
/*
|
|
|
|
Convert a date delta in milliseconds into a string representation of "23 seconds ago", "27 minutes ago" etc.
|
|
|
|
delta: delta in milliseconds
|
|
|
|
Returns an object with these members:
|
|
|
|
description: string describing the delta period
|
|
|
|
updatePeriod: time in millisecond until the string will be inaccurate
|
|
|
|
*/
|
|
|
|
exports.getRelativeDate = function(delta) {
|
|
|
|
var units = [
|
|
|
|
{name: "years", duration: 365 * 24 * 60 * 60 * 1000},
|
|
|
|
{name: "months", duration: (365/12) * 24 * 60 * 60 * 1000},
|
|
|
|
{name: "days", duration: 24 * 60 * 60 * 1000},
|
|
|
|
{name: "hours", duration: 60 * 60 * 1000},
|
|
|
|
{name: "minutes", duration: 60 * 1000},
|
|
|
|
{name: "seconds", duration: 1000}
|
|
|
|
];
|
|
|
|
for(var t=0; t<units.length; t++) {
|
|
|
|
var result = Math.floor(delta / units[t].duration);
|
|
|
|
if(result >= 2) {
|
|
|
|
return {
|
|
|
|
description: result + " " + units[t].name + " ago",
|
|
|
|
updatePeriod: units[t].duration
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
description: "1 second ago",
|
|
|
|
updatePeriod: 1000
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2012-04-30 11:23:03 +00:00
|
|
|
// Convert & to "&", < to "<", > to ">" and " to """
|
|
|
|
exports.htmlEncode = function(s)
|
|
|
|
{
|
|
|
|
if(s) {
|
|
|
|
return s.toString().replace(/&/mg,"&").replace(/</mg,"<").replace(/>/mg,">").replace(/\"/mg,""");
|
|
|
|
} else {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Converts all HTML entities to their character equivalents
|
|
|
|
exports.entityDecode = function(s) {
|
|
|
|
var e = s.substr(1,s.length-2); // Strip the & and the ;
|
|
|
|
if(e.charAt(0) === "#") {
|
|
|
|
if(e.charAt(1) === "x" || e.charAt(1) === "X") {
|
|
|
|
return String.fromCharCode(parseInt(e.substr(2),16));
|
|
|
|
} else {
|
|
|
|
return String.fromCharCode(parseInt(e.substr(1),10));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var c = $tw.config.htmlEntities[e];
|
|
|
|
if(c) {
|
|
|
|
return String.fromCharCode(c);
|
|
|
|
} else {
|
|
|
|
return s; // Couldn't convert it as an entity, just return it raw
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.unescapeLineBreaks = function(s) {
|
|
|
|
return s.replace(/\\n/mg,"\n").replace(/\\b/mg," ").replace(/\\s/mg,"\\").replace(/\r/mg,"");
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Returns an escape sequence for given character. Uses \x for characters <=
|
|
|
|
* 0xFF to save space, \u for the rest.
|
|
|
|
*
|
|
|
|
* The code needs to be in sync with th code template in the compilation
|
|
|
|
* function for "action" nodes.
|
|
|
|
*/
|
|
|
|
// Copied from peg.js, thanks to David Majda
|
|
|
|
exports.escape = function(ch) {
|
|
|
|
var charCode = ch.charCodeAt(0);
|
|
|
|
if (charCode <= 0xFF) {
|
|
|
|
return '\\x' + $tw.utils.pad(charCode.toString(16).toUpperCase());
|
|
|
|
} else {
|
|
|
|
return '\\u' + $tw.utils.pad(charCode.toString(16).toUpperCase(),4);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Turns a string into a legal JavaScript string
|
|
|
|
// Copied from peg.js, thanks to David Majda
|
|
|
|
exports.stringify = function(s) {
|
|
|
|
/*
|
|
|
|
* ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string
|
|
|
|
* literal except for the closing quote character, backslash, carriage return,
|
|
|
|
* line separator, paragraph separator, and line feed. Any character may
|
|
|
|
* appear in the form of an escape sequence.
|
|
|
|
*
|
|
|
|
* For portability, we also escape escape all non-ASCII characters.
|
|
|
|
*/
|
|
|
|
return s
|
|
|
|
.replace(/\\/g, '\\\\') // backslash
|
|
|
|
.replace(/"/g, '\\"') // double quote character
|
|
|
|
.replace(/'/g, "\\'") // single quote character
|
|
|
|
.replace(/\r/g, '\\r') // carriage return
|
|
|
|
.replace(/\n/g, '\\n') // line feed
|
|
|
|
.replace(/[\x80-\uFFFF]/g, exports.escape); // non-ASCII characters
|
|
|
|
};
|
|
|
|
|
2012-10-17 18:10:48 +00:00
|
|
|
/*
|
|
|
|
Escape the RegExp special characters with a preceding backslash
|
|
|
|
*/
|
|
|
|
exports.escapeRegExp = function(s) {
|
|
|
|
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
|
|
|
|
};
|
|
|
|
|
2012-04-30 11:23:03 +00:00
|
|
|
exports.nextTick = function(fn) {
|
|
|
|
/*global window: false */
|
|
|
|
if(typeof window !== "undefined") {
|
|
|
|
// Apparently it would be faster to use postMessage - http://dbaron.org/log/20100309-faster-timeouts
|
|
|
|
window.setTimeout(fn,4);
|
|
|
|
} else {
|
|
|
|
process.nextTick(fn);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-06-11 09:41:13 +00:00
|
|
|
/*
|
|
|
|
Convert a hyphenated CSS property name into a camel case one
|
|
|
|
*/
|
|
|
|
exports.unHyphenateCss = function(propName) {
|
|
|
|
if($tw.browser.unHyphenateCss) {
|
|
|
|
return propName.replace(/-([a-z])/gi, function(match0,match1) {
|
2012-06-11 11:46:58 +00:00
|
|
|
return match1.toUpperCase();
|
2012-06-11 09:41:13 +00:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return propName;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-05-02 10:02:47 +00:00
|
|
|
/*
|
|
|
|
Extract the version number from the meta tag or from the boot file
|
|
|
|
*/
|
|
|
|
|
2012-05-19 10:29:51 +00:00
|
|
|
if($tw.browser) {
|
2012-05-02 10:02:47 +00:00
|
|
|
|
|
|
|
// Browser version
|
|
|
|
exports.extractVersionInfo = function() {
|
|
|
|
var metatags = document.getElementsByTagName("meta");
|
|
|
|
for(var t=0; t<metatags.length; t++) {
|
|
|
|
var m = metatags[t];
|
|
|
|
if(m.name === "tiddlywiki-version") {
|
2012-07-13 16:38:23 +00:00
|
|
|
return m.content;
|
2012-05-02 10:02:47 +00:00
|
|
|
}
|
|
|
|
}
|
2012-07-13 16:38:23 +00:00
|
|
|
return null;
|
2012-05-02 10:02:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Server version
|
|
|
|
exports.extractVersionInfo = function() {
|
2012-08-02 13:32:38 +00:00
|
|
|
return $tw.packageInfo.version;
|
2012-05-02 10:02:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2012-04-30 11:23:03 +00:00
|
|
|
})();
|