diff options
author | Cristhofer Travieso <cristhofert97@gmail.com> | 2013-11-02 17:15:49 (GMT) |
---|---|---|
committer | Cristhofer Travieso <cristhofert97@gmail.com> | 2013-11-02 17:15:49 (GMT) |
commit | 90a4b10d3d3bb7f0576bd19c675b7b83aa11958b (patch) | |
tree | fb077ddb68a9330436da0be4f214e74083a2e9d5 | |
parent | 99e261d5c2f483842f76d479bd668c6f133ab777 (diff) |
Add css directory ,lib directory and index2.html file. Create toolbar and stop button
Signed-off-by: Cristhofer Travieso <cristhofert97@gmail.com>
47 files changed, 9095 insertions, 13 deletions
diff --git a/activity/activity-install.svg b/activity/activity-install.svg index 6ec19c6..ace9976 100644 --- a/activity/activity-install.svg +++ b/activity/activity-install.svg @@ -7,10 +7,34 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" width="55" height="55" - id="svg2"> + id="svg2" + inkscape:version="0.48.4 r9939" + sodipodi:docname="activity-install.svg"> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1301" + inkscape:window-height="744" + id="namedview9" + showgrid="false" + inkscape:zoom="11.34914" + inkscape:cx="28.746029" + inkscape:cy="9.1360114" + inkscape:window-x="65" + inkscape:window-y="24" + inkscape:window-maximized="1" + inkscape:current-layer="svg2" /> <defs id="defs4" /> <metadata @@ -21,23 +45,36 @@ <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> + <dc:title /> </cc:Work> </rdf:RDF> </metadata> <g - transform="translate(0,-997.36218)" - id="layer1"> + id="g2988" + transform="matrix(1,0,0,1.1310955,-0.23305084,-2.4539613)"> <g - id="g3030"> - <path - d="m 19.904201,1003.2636 0,25.5188 -10.3461383,-2.0948 18.0076243,17.573 16.36146,-15.9666 -9.637329,0.9 0.125119,-25.8083 z" - id="path3004" - style="fill:fill_color;stroke:stroke_color;stroke-width:1.13395655px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - d="m 6.4617358,1044.7775 42.7751092,0" - id="path3010" - style="fill:none;stroke:stroke_color;stroke-width:1.20252109px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#000000;stroke:#ffffff" + id="layer1" + transform="translate(-0.23305083,-996.42998)"> + <g + style="fill:#000000;stroke:#ffffff" + id="g3030"> + <path + inkscape:connector-curvature="0" + style="fill:#000000;stroke:#ffffff;stroke-width:1.13395655px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="path3004" + d="m 19.904201,1003.2636 0,25.5188 -10.3461383,-2.0948 18.0076243,17.573 16.36146,-15.9666 -9.637329,0.9 0.125119,-25.8083 z" /> + <path + inkscape:connector-curvature="0" + style="fill:#000000;stroke:#ffffff;stroke-width:1.20252109px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="path3010" + d="m 6.4617358,1044.7775 42.7751092,0" /> + </g> </g> </g> + <path + style="fill:none;stroke:#000000;stroke-width:0.49429932px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 7.3104978,52.300673 40.2430052,0" + id="path2998" + inkscape:connector-curvature="0" /> </svg> diff --git a/css/activity.css b/css/activity.css new file mode 100644 index 0000000..8a93851 --- /dev/null +++ b/css/activity.css @@ -0,0 +1,47 @@ +#canvas { + margin: 0; + text-align: center; +} + +#clock-container { + position: relative; + margin: 0 auto; +} + +canvas { + position: absolute; + left: 0; + top: 0; +} + +#text-time { + font-family: monospace; + font-size: 35px; + font-weight: bold; + margin: 0; +} + +#text-date { + font-size: 25px; + margin: 0; +} + +#main-toolbar #activity-button { + background-image: url(../activity/activity-clock.svg); +} + +#main-toolbar #simple-clock-button { + background-image: url(../icons/simple-clock.svg); +} + +#main-toolbar #nice-clock-button { + background-image: url(../icons/nice-clock.svg); +} + +#main-toolbar #write-time-button { + background-image: url(../icons/write-time.svg); +} + +#main-toolbar #write-date-button { + background-image: url(../icons/write-date.svg); +} diff --git a/index2.html b/index2.html new file mode 100644 index 0000000..5774204 --- /dev/null +++ b/index2.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +<title>Clock Activity</title> +<link rel="stylesheet" href="lib/sugar-web/graphics/css/sugar.css"> +<link rel="stylesheet" href="css/activity.css"> +<script data-main="js/loader" src="lib/require.js"></script> +</head> + +<body> + <div id="main-toolbar" class="toolbar"> + <button class="toolbutton" id="activity-button" title="My Activity"></button> + <button class="toolbutton pull-right" id="stop-button" title="Stop"></button> + <hr/> + </div> + <div id="canvas"> + <IFRAME + src="http://activities.sugarlabs.org" + marginWidth=0 marginHeight=0 frameBorder=0 + id=IframeASLO + style="width:100%; height:100%;" frameSpacing=0> + </IFRAME> + </div> + </div> +</body> +</html> diff --git a/lib/domReady.js b/lib/domReady.js new file mode 100644 index 0000000..2b54122 --- /dev/null +++ b/lib/domReady.js @@ -0,0 +1,129 @@ +/** + * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/requirejs/domReady for details + */ +/*jslint */ +/*global require: false, define: false, requirejs: false, + window: false, clearInterval: false, document: false, + self: false, setInterval: false */ + + +define(function () { + 'use strict'; + + var isTop, testDiv, scrollIntervalId, + isBrowser = typeof window !== "undefined" && window.document, + isPageLoaded = !isBrowser, + doc = isBrowser ? document : null, + readyCalls = []; + + function runCallbacks(callbacks) { + var i; + for (i = 0; i < callbacks.length; i += 1) { + callbacks[i](doc); + } + } + + function callReady() { + var callbacks = readyCalls; + + if (isPageLoaded) { + //Call the DOM ready callbacks + if (callbacks.length) { + readyCalls = []; + runCallbacks(callbacks); + } + } + } + + /** + * Sets the page as loaded. + */ + function pageLoaded() { + if (!isPageLoaded) { + isPageLoaded = true; + if (scrollIntervalId) { + clearInterval(scrollIntervalId); + } + + callReady(); + } + } + + if (isBrowser) { + if (document.addEventListener) { + //Standards. Hooray! Assumption here that if standards based, + //it knows about DOMContentLoaded. + document.addEventListener("DOMContentLoaded", pageLoaded, false); + window.addEventListener("load", pageLoaded, false); + } else if (window.attachEvent) { + window.attachEvent("onload", pageLoaded); + + testDiv = document.createElement('div'); + try { + isTop = window.frameElement === null; + } catch (e) {} + + //DOMContentLoaded approximation that uses a doScroll, as found by + //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/, + //but modified by other contributors, including jdalton + if (testDiv.doScroll && isTop && window.external) { + scrollIntervalId = setInterval(function () { + try { + testDiv.doScroll(); + pageLoaded(); + } catch (e) {} + }, 30); + } + } + + //Check if document already complete, and if so, just trigger page load + //listeners. Latest webkit browsers also use "interactive", and + //will fire the onDOMContentLoaded before "interactive" but not after + //entering "interactive" or "complete". More details: + //http://dev.w3.org/html5/spec/the-end.html#the-end + //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded + //Hmm, this is more complicated on further use, see "firing too early" + //bug: https://github.com/requirejs/domReady/issues/1 + //so removing the || document.readyState === "interactive" test. + //There is still a window.onload binding that should get fired if + //DOMContentLoaded is missed. + if (document.readyState === "complete") { + pageLoaded(); + } + } + + /** START OF PUBLIC API **/ + + /** + * Registers a callback for DOM ready. If DOM is already ready, the + * callback is called immediately. + * @param {Function} callback + */ + function domReady(callback) { + if (isPageLoaded) { + callback(doc); + } else { + readyCalls.push(callback); + } + return domReady; + } + + domReady.version = '2.0.1'; + + /** + * Loader Plugin API method + */ + domReady.load = function (name, req, onLoad, config) { + if (config.isBuild) { + onLoad(null); + } else { + domReady(onLoad); + } + }; + + /** END OF PUBLIC API **/ + + return domReady; +}); diff --git a/lib/moment.js b/lib/moment.js new file mode 100644 index 0000000..c8a870e --- /dev/null +++ b/lib/moment.js @@ -0,0 +1,1662 @@ +// moment.js +// version : 2.1.0 +// author : Tim Wood +// license : MIT +// momentjs.com + +(function (undefined) { + + /************************************ + Constants + ************************************/ + + var moment, + VERSION = "2.1.0", + round = Math.round, i, + // internal storage for language config files + languages = {}, + + // check for nodeJS + hasModule = (typeof module !== 'undefined' && module.exports), + + // ASP.NET json date format regex + aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, + aspNetTimeSpanJsonRegex = /(\-)?(\d*)?\.?(\d+)\:(\d+)\:(\d+)\.?(\d{3})?/, + + // format tokens + formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|SS?S?|X|zz?|ZZ?|.)/g, + localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g, + + // parsing token regexes + parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 + parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 + parseTokenThreeDigits = /\d{3}/, // 000 - 999 + parseTokenFourDigits = /\d{1,4}/, // 0 - 9999 + parseTokenSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999 + parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic. + parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, // +00:00 -00:00 +0000 -0000 or Z + parseTokenT = /T/i, // T (ISO seperator) + parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 + + // preliminary iso regex + // 0000-00-00 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 + isoRegex = /^\s*\d{4}-\d\d-\d\d((T| )(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/, + isoFormat = 'YYYY-MM-DDTHH:mm:ssZ', + + // iso time formats and regexes + isoTimes = [ + ['HH:mm:ss.S', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/], + ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], + ['HH:mm', /(T| )\d\d:\d\d/], + ['HH', /(T| )\d\d/] + ], + + // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"] + parseTimezoneChunker = /([\+\-]|\d\d)/gi, + + // getter and setter names + proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), + unitMillisecondFactors = { + 'Milliseconds' : 1, + 'Seconds' : 1e3, + 'Minutes' : 6e4, + 'Hours' : 36e5, + 'Days' : 864e5, + 'Months' : 2592e6, + 'Years' : 31536e6 + }, + + unitAliases = { + ms : 'millisecond', + s : 'second', + m : 'minute', + h : 'hour', + d : 'day', + w : 'week', + M : 'month', + y : 'year' + }, + + // format function strings + formatFunctions = {}, + + // tokens to ordinalize and pad + ordinalizeTokens = 'DDD w W M D d'.split(' '), + paddedTokens = 'M D H h m s w W'.split(' '), + + formatTokenFunctions = { + M : function () { + return this.month() + 1; + }, + MMM : function (format) { + return this.lang().monthsShort(this, format); + }, + MMMM : function (format) { + return this.lang().months(this, format); + }, + D : function () { + return this.date(); + }, + DDD : function () { + return this.dayOfYear(); + }, + d : function () { + return this.day(); + }, + dd : function (format) { + return this.lang().weekdaysMin(this, format); + }, + ddd : function (format) { + return this.lang().weekdaysShort(this, format); + }, + dddd : function (format) { + return this.lang().weekdays(this, format); + }, + w : function () { + return this.week(); + }, + W : function () { + return this.isoWeek(); + }, + YY : function () { + return leftZeroFill(this.year() % 100, 2); + }, + YYYY : function () { + return leftZeroFill(this.year(), 4); + }, + YYYYY : function () { + return leftZeroFill(this.year(), 5); + }, + gg : function () { + return leftZeroFill(this.weekYear() % 100, 2); + }, + gggg : function () { + return this.weekYear(); + }, + ggggg : function () { + return leftZeroFill(this.weekYear(), 5); + }, + GG : function () { + return leftZeroFill(this.isoWeekYear() % 100, 2); + }, + GGGG : function () { + return this.isoWeekYear(); + }, + GGGGG : function () { + return leftZeroFill(this.isoWeekYear(), 5); + }, + e : function () { + return this.weekday(); + }, + E : function () { + return this.isoWeekday(); + }, + a : function () { + return this.lang().meridiem(this.hours(), this.minutes(), true); + }, + A : function () { + return this.lang().meridiem(this.hours(), this.minutes(), false); + }, + H : function () { + return this.hours(); + }, + h : function () { + return this.hours() % 12 || 12; + }, + m : function () { + return this.minutes(); + }, + s : function () { + return this.seconds(); + }, + S : function () { + return ~~(this.milliseconds() / 100); + }, + SS : function () { + return leftZeroFill(~~(this.milliseconds() / 10), 2); + }, + SSS : function () { + return leftZeroFill(this.milliseconds(), 3); + }, + Z : function () { + var a = -this.zone(), + b = "+"; + if (a < 0) { + a = -a; + b = "-"; + } + return b + leftZeroFill(~~(a / 60), 2) + ":" + leftZeroFill(~~a % 60, 2); + }, + ZZ : function () { + var a = -this.zone(), + b = "+"; + if (a < 0) { + a = -a; + b = "-"; + } + return b + leftZeroFill(~~(10 * a / 6), 4); + }, + z : function () { + return this.zoneAbbr(); + }, + zz : function () { + return this.zoneName(); + }, + X : function () { + return this.unix(); + } + }; + + function padToken(func, count) { + return function (a) { + return leftZeroFill(func.call(this, a), count); + }; + } + function ordinalizeToken(func, period) { + return function (a) { + return this.lang().ordinal(func.call(this, a), period); + }; + } + + while (ordinalizeTokens.length) { + i = ordinalizeTokens.pop(); + formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i); + } + while (paddedTokens.length) { + i = paddedTokens.pop(); + formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2); + } + formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); + + + /************************************ + Constructors + ************************************/ + + function Language() { + + } + + // Moment prototype object + function Moment(config) { + extend(this, config); + } + + // Duration Constructor + function Duration(duration) { + var years = duration.years || duration.year || duration.y || 0, + months = duration.months || duration.month || duration.M || 0, + weeks = duration.weeks || duration.week || duration.w || 0, + days = duration.days || duration.day || duration.d || 0, + hours = duration.hours || duration.hour || duration.h || 0, + minutes = duration.minutes || duration.minute || duration.m || 0, + seconds = duration.seconds || duration.second || duration.s || 0, + milliseconds = duration.milliseconds || duration.millisecond || duration.ms || 0; + + // store reference to input for deterministic cloning + this._input = duration; + + // representation for dateAddRemove + this._milliseconds = milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 36e5; // 1000 * 60 * 60 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = days + + weeks * 7; + // It is impossible translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = months + + years * 12; + + this._data = {}; + + this._bubble(); + } + + + /************************************ + Helpers + ************************************/ + + + function extend(a, b) { + for (var i in b) { + if (b.hasOwnProperty(i)) { + a[i] = b[i]; + } + } + return a; + } + + function absRound(number) { + if (number < 0) { + return Math.ceil(number); + } else { + return Math.floor(number); + } + } + + // left zero fill a number + // see http://jsperf.com/left-zero-filling for performance comparison + function leftZeroFill(number, targetLength) { + var output = number + ''; + while (output.length < targetLength) { + output = '0' + output; + } + return output; + } + + // helper function for _.addTime and _.subtractTime + function addOrSubtractDurationFromMoment(mom, duration, isAdding, ignoreUpdateOffset) { + var milliseconds = duration._milliseconds, + days = duration._days, + months = duration._months, + minutes, + hours, + currentDate; + + if (milliseconds) { + mom._d.setTime(+mom._d + milliseconds * isAdding); + } + // store the minutes and hours so we can restore them + if (days || months) { + minutes = mom.minute(); + hours = mom.hour(); + } + if (days) { + mom.date(mom.date() + days * isAdding); + } + if (months) { + mom.month(mom.month() + months * isAdding); + } + if (milliseconds && !ignoreUpdateOffset) { + moment.updateOffset(mom); + } + // restore the minutes and hours after possibly changing dst + if (days || months) { + mom.minute(minutes); + mom.hour(hours); + } + } + + // check if is an array + function isArray(input) { + return Object.prototype.toString.call(input) === '[object Array]'; + } + + // compare two arrays, return the number of differences + function compareArrays(array1, array2) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if (~~array1[i] !== ~~array2[i]) { + diffs++; + } + } + return diffs + lengthDiff; + } + + function normalizeUnits(units) { + return units ? unitAliases[units] || units.toLowerCase().replace(/(.)s$/, '$1') : units; + } + + + /************************************ + Languages + ************************************/ + + + Language.prototype = { + set : function (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (typeof prop === 'function') { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + }, + + _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), + months : function (m) { + return this._months[m.month()]; + }, + + _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), + monthsShort : function (m) { + return this._monthsShort[m.month()]; + }, + + monthsParse : function (monthName) { + var i, mom, regex; + + if (!this._monthsParse) { + this._monthsParse = []; + } + + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + if (!this._monthsParse[i]) { + mom = moment([2000, i]); + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (this._monthsParse[i].test(monthName)) { + return i; + } + } + }, + + _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), + weekdays : function (m) { + return this._weekdays[m.day()]; + }, + + _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), + weekdaysShort : function (m) { + return this._weekdaysShort[m.day()]; + }, + + _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), + weekdaysMin : function (m) { + return this._weekdaysMin[m.day()]; + }, + + weekdaysParse : function (weekdayName) { + var i, mom, regex; + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + if (!this._weekdaysParse[i]) { + mom = moment([2000, 1]).day(i); + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + }, + + _longDateFormat : { + LT : "h:mm A", + L : "MM/DD/YYYY", + LL : "MMMM D YYYY", + LLL : "MMMM D YYYY LT", + LLLL : "dddd, MMMM D YYYY LT" + }, + longDateFormat : function (key) { + var output = this._longDateFormat[key]; + if (!output && this._longDateFormat[key.toUpperCase()]) { + output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); + this._longDateFormat[key] = output; + } + return output; + }, + + isPM : function (input) { + return ((input + '').toLowerCase()[0] === 'p'); + }, + + _meridiemParse : /[ap]\.?m?\.?/i, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + }, + + _calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + calendar : function (key, mom) { + var output = this._calendar[key]; + return typeof output === 'function' ? output.apply(mom) : output; + }, + + _relativeTime : { + future : "in %s", + past : "%s ago", + s : "a few seconds", + m : "a minute", + mm : "%d minutes", + h : "an hour", + hh : "%d hours", + d : "a day", + dd : "%d days", + M : "a month", + MM : "%d months", + y : "a year", + yy : "%d years" + }, + relativeTime : function (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (typeof output === 'function') ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); + }, + pastFuture : function (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); + }, + + ordinal : function (number) { + return this._ordinal.replace("%d", number); + }, + _ordinal : "%d", + + preparse : function (string) { + return string; + }, + + postformat : function (string) { + return string; + }, + + week : function (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + }, + _week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } + }; + + // Loads a language definition into the `languages` cache. The function + // takes a key and optionally values. If not in the browser and no values + // are provided, it will load the language file module. As a convenience, + // this function also returns the language values. + function loadLang(key, values) { + values.abbr = key; + if (!languages[key]) { + languages[key] = new Language(); + } + languages[key].set(values); + return languages[key]; + } + + // Determines which language definition to use and returns it. + // + // With no parameters, it will return the global language. If you + // pass in a language key, such as 'en', it will return the + // definition for 'en', so long as 'en' has already been loaded using + // moment.lang. + function getLangDefinition(key) { + if (!key) { + return moment.fn._lang; + } + if (!languages[key] && hasModule) { + try { + require('./lang/' + key); + } catch (e) { + // call with no params to set to default + return moment.fn._lang; + } + } + return languages[key]; + } + + + /************************************ + Formatting + ************************************/ + + + function removeFormattingTokens(input) { + if (input.match(/\[.*\]/)) { + return input.replace(/^\[|\]$/g, ""); + } + return input.replace(/\\/g, ""); + } + + function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = ""; + for (i = 0; i < length; i++) { + output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; + } + return output; + }; + } + + // format date using native date object + function formatMoment(m, format) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return m.lang().longDateFormat(input) || input; + } + + while (i-- && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + } + + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); + } + + return formatFunctions[format](m); + } + + + /************************************ + Parsing + ************************************/ + + + // get the regex to find the next token + function getParseRegexForToken(token, config) { + switch (token) { + case 'DDDD': + return parseTokenThreeDigits; + case 'YYYY': + return parseTokenFourDigits; + case 'YYYYY': + return parseTokenSixDigits; + case 'S': + case 'SS': + case 'SSS': + case 'DDD': + return parseTokenOneToThreeDigits; + case 'MMM': + case 'MMMM': + case 'dd': + case 'ddd': + case 'dddd': + return parseTokenWord; + case 'a': + case 'A': + return getLangDefinition(config._l)._meridiemParse; + case 'X': + return parseTokenTimestampMs; + case 'Z': + case 'ZZ': + return parseTokenTimezone; + case 'T': + return parseTokenT; + case 'MM': + case 'DD': + case 'YY': + case 'HH': + case 'hh': + case 'mm': + case 'ss': + case 'M': + case 'D': + case 'd': + case 'H': + case 'h': + case 'm': + case 's': + return parseTokenOneOrTwoDigits; + default : + return new RegExp(token.replace('\\', '')); + } + } + + function timezoneMinutesFromString(string) { + var tzchunk = (parseTokenTimezone.exec(string) || [])[0], + parts = (tzchunk + '').match(parseTimezoneChunker) || ['-', 0, 0], + minutes = +(parts[1] * 60) + ~~parts[2]; + + return parts[0] === '+' ? -minutes : minutes; + } + + // function to convert string input to date + function addTimeToArrayFromToken(token, input, config) { + var a, datePartArray = config._a; + + switch (token) { + // MONTH + case 'M' : // fall through to MM + case 'MM' : + datePartArray[1] = (input == null) ? 0 : ~~input - 1; + break; + case 'MMM' : // fall through to MMMM + case 'MMMM' : + a = getLangDefinition(config._l).monthsParse(input); + // if we didn't find a month name, mark the date as invalid. + if (a != null) { + datePartArray[1] = a; + } else { + config._isValid = false; + } + break; + // DAY OF MONTH + case 'D' : // fall through to DDDD + case 'DD' : // fall through to DDDD + case 'DDD' : // fall through to DDDD + case 'DDDD' : + if (input != null) { + datePartArray[2] = ~~input; + } + break; + // YEAR + case 'YY' : + datePartArray[0] = ~~input + (~~input > 68 ? 1900 : 2000); + break; + case 'YYYY' : + case 'YYYYY' : + datePartArray[0] = ~~input; + break; + // AM / PM + case 'a' : // fall through to A + case 'A' : + config._isPm = getLangDefinition(config._l).isPM(input); + break; + // 24 HOUR + case 'H' : // fall through to hh + case 'HH' : // fall through to hh + case 'h' : // fall through to hh + case 'hh' : + datePartArray[3] = ~~input; + break; + // MINUTE + case 'm' : // fall through to mm + case 'mm' : + datePartArray[4] = ~~input; + break; + // SECOND + case 's' : // fall through to ss + case 'ss' : + datePartArray[5] = ~~input; + break; + // MILLISECOND + case 'S' : + case 'SS' : + case 'SSS' : + datePartArray[6] = ~~ (('0.' + input) * 1000); + break; + // UNIX TIMESTAMP WITH MS + case 'X': + config._d = new Date(parseFloat(input) * 1000); + break; + // TIMEZONE + case 'Z' : // fall through to ZZ + case 'ZZ' : + config._useUTC = true; + config._tzm = timezoneMinutesFromString(input); + break; + } + + // if the input is null, the date is not valid + if (input == null) { + config._isValid = false; + } + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function dateFromArray(config) { + var i, date, input = []; + + if (config._d) { + return; + } + + for (i = 0; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // add the offsets to the time to be parsed so that we can have a clean array for checking isValid + input[3] += ~~((config._tzm || 0) / 60); + input[4] += ~~((config._tzm || 0) % 60); + + date = new Date(0); + + if (config._useUTC) { + date.setUTCFullYear(input[0], input[1], input[2]); + date.setUTCHours(input[3], input[4], input[5], input[6]); + } else { + date.setFullYear(input[0], input[1], input[2]); + date.setHours(input[3], input[4], input[5], input[6]); + } + + config._d = date; + } + + // date from string and format string + function makeDateFromStringAndFormat(config) { + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var tokens = config._f.match(formattingTokens), + string = config._i, + i, parsedInput; + + config._a = []; + + for (i = 0; i < tokens.length; i++) { + parsedInput = (getParseRegexForToken(tokens[i], config).exec(string) || [])[0]; + if (parsedInput) { + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + } + // don't parse if its not a known token + if (formatTokenFunctions[tokens[i]]) { + addTimeToArrayFromToken(tokens[i], parsedInput, config); + } + } + + // add remaining unparsed input to the string + if (string) { + config._il = string; + } + + // handle am pm + if (config._isPm && config._a[3] < 12) { + config._a[3] += 12; + } + // if is 12 am, change hours to 0 + if (config._isPm === false && config._a[3] === 12) { + config._a[3] = 0; + } + // return + dateFromArray(config); + } + + // date from string and array of format strings + function makeDateFromStringAndArray(config) { + var tempConfig, + tempMoment, + bestMoment, + + scoreToBeat = 99, + i, + currentScore; + + for (i = 0; i < config._f.length; i++) { + tempConfig = extend({}, config); + tempConfig._f = config._f[i]; + makeDateFromStringAndFormat(tempConfig); + tempMoment = new Moment(tempConfig); + + currentScore = compareArrays(tempConfig._a, tempMoment.toArray()); + + // if there is any input that was not parsed + // add a penalty for that format + if (tempMoment._il) { + currentScore += tempMoment._il.length; + } + + if (currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempMoment; + } + } + + extend(config, bestMoment); + } + + // date from iso format + function makeDateFromString(config) { + var i, + string = config._i, + match = isoRegex.exec(string); + + if (match) { + // match[2] should be "T" or undefined + config._f = 'YYYY-MM-DD' + (match[2] || " "); + for (i = 0; i < 4; i++) { + if (isoTimes[i][1].exec(string)) { + config._f += isoTimes[i][0]; + break; + } + } + if (parseTokenTimezone.exec(string)) { + config._f += " Z"; + } + makeDateFromStringAndFormat(config); + } else { + config._d = new Date(string); + } + } + + function makeDateFromInput(config) { + var input = config._i, + matched = aspNetJsonRegex.exec(input); + + if (input === undefined) { + config._d = new Date(); + } else if (matched) { + config._d = new Date(+matched[1]); + } else if (typeof input === 'string') { + makeDateFromString(config); + } else if (isArray(input)) { + config._a = input.slice(0); + dateFromArray(config); + } else { + config._d = input instanceof Date ? new Date(+input) : new Date(input); + } + } + + + /************************************ + Relative Time + ************************************/ + + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) { + return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function relativeTime(milliseconds, withoutSuffix, lang) { + var seconds = round(Math.abs(milliseconds) / 1000), + minutes = round(seconds / 60), + hours = round(minutes / 60), + days = round(hours / 24), + years = round(days / 365), + args = seconds < 45 && ['s', seconds] || + minutes === 1 && ['m'] || + minutes < 45 && ['mm', minutes] || + hours === 1 && ['h'] || + hours < 22 && ['hh', hours] || + days === 1 && ['d'] || + days <= 25 && ['dd', days] || + days <= 45 && ['M'] || + days < 345 && ['MM', round(days / 30)] || + years === 1 && ['y'] || ['yy', years]; + args[2] = withoutSuffix; + args[3] = milliseconds > 0; + args[4] = lang; + return substituteTimeAgo.apply({}, args); + } + + + /************************************ + Week of Year + ************************************/ + + + // firstDayOfWeek 0 = sun, 6 = sat + // the day of the week that starts the week + // (usually sunday or monday) + // firstDayOfWeekOfYear 0 = sun, 6 = sat + // the first week is the week that contains the first + // of this day of the week + // (eg. ISO weeks use thursday (4)) + function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { + var end = firstDayOfWeekOfYear - firstDayOfWeek, + daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), + adjustedMoment; + + + if (daysToDayOfWeek > end) { + daysToDayOfWeek -= 7; + } + + if (daysToDayOfWeek < end - 7) { + daysToDayOfWeek += 7; + } + + adjustedMoment = moment(mom).add('d', daysToDayOfWeek); + return { + week: Math.ceil(adjustedMoment.dayOfYear() / 7), + year: adjustedMoment.year() + }; + } + + + /************************************ + Top Level Functions + ************************************/ + + function makeMoment(config) { + var input = config._i, + format = config._f; + + if (input === null || input === '') { + return null; + } + + if (typeof input === 'string') { + config._i = input = getLangDefinition().preparse(input); + } + + if (moment.isMoment(input)) { + config = extend({}, input); + config._d = new Date(+input._d); + } else if (format) { + if (isArray(format)) { + makeDateFromStringAndArray(config); + } else { + makeDateFromStringAndFormat(config); + } + } else { + makeDateFromInput(config); + } + + return new Moment(config); + } + + moment = function (input, format, lang) { + return makeMoment({ + _i : input, + _f : format, + _l : lang, + _isUTC : false + }); + }; + + // creating with utc + moment.utc = function (input, format, lang) { + return makeMoment({ + _useUTC : true, + _isUTC : true, + _l : lang, + _i : input, + _f : format + }); + }; + + // creating with unix timestamp (in seconds) + moment.unix = function (input) { + return moment(input * 1000); + }; + + // duration + moment.duration = function (input, key) { + var isDuration = moment.isDuration(input), + isNumber = (typeof input === 'number'), + duration = (isDuration ? input._input : (isNumber ? {} : input)), + matched = aspNetTimeSpanJsonRegex.exec(input), + sign, + ret; + + if (isNumber) { + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } else if (matched) { + sign = (matched[1] === "-") ? -1 : 1; + duration = { + y: 0, + d: ~~matched[2] * sign, + h: ~~matched[3] * sign, + m: ~~matched[4] * sign, + s: ~~matched[5] * sign, + ms: ~~matched[6] * sign + }; + } + + ret = new Duration(duration); + + if (isDuration && input.hasOwnProperty('_lang')) { + ret._lang = input._lang; + } + + return ret; + }; + + // version number + moment.version = VERSION; + + // default format + moment.defaultFormat = isoFormat; + + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + moment.updateOffset = function () {}; + + // This function will load languages and then set the global language. If + // no arguments are passed in, it will simply return the current global + // language key. + moment.lang = function (key, values) { + if (!key) { + return moment.fn._lang._abbr; + } + if (values) { + loadLang(key, values); + } else if (!languages[key]) { + getLangDefinition(key); + } + moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key); + }; + + // returns language data + moment.langData = function (key) { + if (key && key._lang && key._lang._abbr) { + key = key._lang._abbr; + } + return getLangDefinition(key); + }; + + // compare moment object + moment.isMoment = function (obj) { + return obj instanceof Moment; + }; + + // for typechecking Duration objects + moment.isDuration = function (obj) { + return obj instanceof Duration; + }; + + + /************************************ + Moment Prototype + ************************************/ + + + moment.fn = Moment.prototype = { + + clone : function () { + return moment(this); + }, + + valueOf : function () { + return +this._d + ((this._offset || 0) * 60000); + }, + + unix : function () { + return Math.floor(+this / 1000); + }, + + toString : function () { + return this.format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ"); + }, + + toDate : function () { + return this._offset ? new Date(+this) : this._d; + }, + + toISOString : function () { + return formatMoment(moment(this).utc(), 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + }, + + toArray : function () { + var m = this; + return [ + m.year(), + m.month(), + m.date(), + m.hours(), + m.minutes(), + m.seconds(), + m.milliseconds() + ]; + }, + + isValid : function () { + if (this._isValid == null) { + if (this._a) { + this._isValid = !compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()); + } else { + this._isValid = !isNaN(this._d.getTime()); + } + } + return !!this._isValid; + }, + + utc : function () { + return this.zone(0); + }, + + local : function () { + this.zone(0); + this._isUTC = false; + return this; + }, + + format : function (inputString) { + var output = formatMoment(this, inputString || moment.defaultFormat); + return this.lang().postformat(output); + }, + + add : function (input, val) { + var dur; + // switch args to support add('s', 1) and add(1, 's') + if (typeof input === 'string') { + dur = moment.duration(+val, input); + } else { + dur = moment.duration(input, val); + } + addOrSubtractDurationFromMoment(this, dur, 1); + return this; + }, + + subtract : function (input, val) { + var dur; + // switch args to support subtract('s', 1) and subtract(1, 's') + if (typeof input === 'string') { + dur = moment.duration(+val, input); + } else { + dur = moment.duration(input, val); + } + addOrSubtractDurationFromMoment(this, dur, -1); + return this; + }, + + diff : function (input, units, asFloat) { + var that = this._isUTC ? moment(input).zone(this._offset || 0) : moment(input).local(), + zoneDiff = (this.zone() - that.zone()) * 6e4, + diff, output; + + units = normalizeUnits(units); + + if (units === 'year' || units === 'month') { + // average number of days in the months in the given dates + diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2 + // difference in months + output = ((this.year() - that.year()) * 12) + (this.month() - that.month()); + // adjust by taking difference in days, average number of days + // and dst in the given months. + output += ((this - moment(this).startOf('month')) - + (that - moment(that).startOf('month'))) / diff; + // same as above but with zones, to negate all dst + output -= ((this.zone() - moment(this).startOf('month').zone()) - + (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff; + if (units === 'year') { + output = output / 12; + } + } else { + diff = (this - that); + output = units === 'second' ? diff / 1e3 : // 1000 + units === 'minute' ? diff / 6e4 : // 1000 * 60 + units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 + units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst + units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst + diff; + } + return asFloat ? output : absRound(output); + }, + + from : function (time, withoutSuffix) { + return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix); + }, + + fromNow : function (withoutSuffix) { + return this.from(moment(), withoutSuffix); + }, + + calendar : function () { + var diff = this.diff(moment().startOf('day'), 'days', true), + format = diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; + return this.format(this.lang().calendar(format, this)); + }, + + isLeapYear : function () { + var year = this.year(); + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + }, + + isDST : function () { + return (this.zone() < this.clone().month(0).zone() || + this.zone() < this.clone().month(5).zone()); + }, + + day : function (input) { + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + if (typeof input === 'string') { + input = this.lang().weekdaysParse(input); + if (typeof input !== 'number') { + return this; + } + } + return this.add({ d : input - day }); + } else { + return day; + } + }, + + month : function (input) { + var utc = this._isUTC ? 'UTC' : '', + dayOfMonth, + daysInMonth; + + if (input != null) { + if (typeof input === 'string') { + input = this.lang().monthsParse(input); + if (typeof input !== 'number') { + return this; + } + } + + dayOfMonth = this.date(); + this.date(1); + this._d['set' + utc + 'Month'](input); + this.date(Math.min(dayOfMonth, this.daysInMonth())); + + moment.updateOffset(this); + return this; + } else { + return this._d['get' + utc + 'Month'](); + } + }, + + startOf: function (units) { + units = normalizeUnits(units); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'day': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + /* falls through */ + } + + // weeks are a special case + if (units === 'week') { + this.weekday(0); + } + + return this; + }, + + endOf: function (units) { + return this.startOf(units).add(units, 1).subtract('ms', 1); + }, + + isAfter: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) > +moment(input).startOf(units); + }, + + isBefore: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) < +moment(input).startOf(units); + }, + + isSame: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) === +moment(input).startOf(units); + }, + + min: function (other) { + other = moment.apply(null, arguments); + return other < this ? this : other; + }, + + max: function (other) { + other = moment.apply(null, arguments); + return other > this ? this : other; + }, + + zone : function (input) { + var offset = this._offset || 0; + if (input != null) { + if (typeof input === "string") { + input = timezoneMinutesFromString(input); + } + if (Math.abs(input) < 16) { + input = input * 60; + } + this._offset = input; + this._isUTC = true; + if (offset !== input) { + addOrSubtractDurationFromMoment(this, moment.duration(offset - input, 'm'), 1, true); + } + } else { + return this._isUTC ? offset : this._d.getTimezoneOffset(); + } + return this; + }, + + zoneAbbr : function () { + return this._isUTC ? "UTC" : ""; + }, + + zoneName : function () { + return this._isUTC ? "Coordinated Universal Time" : ""; + }, + + daysInMonth : function () { + return moment.utc([this.year(), this.month() + 1, 0]).date(); + }, + + dayOfYear : function (input) { + var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add("d", (input - dayOfYear)); + }, + + weekYear : function (input) { + var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year; + return input == null ? year : this.add("y", (input - year)); + }, + + isoWeekYear : function (input) { + var year = weekOfYear(this, 1, 4).year; + return input == null ? year : this.add("y", (input - year)); + }, + + week : function (input) { + var week = this.lang().week(this); + return input == null ? week : this.add("d", (input - week) * 7); + }, + + isoWeek : function (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add("d", (input - week) * 7); + }, + + weekday : function (input) { + var weekday = (this._d.getDay() + 7 - this.lang()._week.dow) % 7; + return input == null ? weekday : this.add("d", input - weekday); + }, + + isoWeekday : function (input) { + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); + }, + + // If passed a language key, it will set the language for this + // instance. Otherwise, it will return the language configuration + // variables for this instance. + lang : function (key) { + if (key === undefined) { + return this._lang; + } else { + this._lang = getLangDefinition(key); + return this; + } + } + }; + + // helper for adding shortcuts + function makeGetterAndSetter(name, key) { + moment.fn[name] = moment.fn[name + 's'] = function (input) { + var utc = this._isUTC ? 'UTC' : ''; + if (input != null) { + this._d['set' + utc + key](input); + moment.updateOffset(this); + return this; + } else { + return this._d['get' + utc + key](); + } + }; + } + + // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds) + for (i = 0; i < proxyGettersAndSetters.length; i ++) { + makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase().replace(/s$/, ''), proxyGettersAndSetters[i]); + } + + // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear') + makeGetterAndSetter('year', 'FullYear'); + + // add plural methods + moment.fn.days = moment.fn.day; + moment.fn.months = moment.fn.month; + moment.fn.weeks = moment.fn.week; + moment.fn.isoWeeks = moment.fn.isoWeek; + + // add aliased format methods + moment.fn.toJSON = moment.fn.toISOString; + + /************************************ + Duration Prototype + ************************************/ + + + moment.duration.fn = Duration.prototype = { + _bubble : function () { + var milliseconds = this._milliseconds, + days = this._days, + months = this._months, + data = this._data, + seconds, minutes, hours, years; + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absRound(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absRound(seconds / 60); + data.minutes = minutes % 60; + + hours = absRound(minutes / 60); + data.hours = hours % 24; + + days += absRound(hours / 24); + data.days = days % 30; + + months += absRound(days / 30); + data.months = months % 12; + + years = absRound(months / 12); + data.years = years; + }, + + weeks : function () { + return absRound(this.days() / 7); + }, + + valueOf : function () { + return this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + ~~(this._months / 12) * 31536e6; + }, + + humanize : function (withSuffix) { + var difference = +this, + output = relativeTime(difference, !withSuffix, this.lang()); + + if (withSuffix) { + output = this.lang().pastFuture(difference, output); + } + + return this.lang().postformat(output); + }, + + add : function (input, val) { + // supports only 2.0-style add(1, 's') or add(moment) + var dur = moment.duration(input, val); + + this._milliseconds += dur._milliseconds; + this._days += dur._days; + this._months += dur._months; + + this._bubble(); + + return this; + }, + + subtract : function (input, val) { + var dur = moment.duration(input, val); + + this._milliseconds -= dur._milliseconds; + this._days -= dur._days; + this._months -= dur._months; + + this._bubble(); + + return this; + }, + + get : function (units) { + units = normalizeUnits(units); + return this[units.toLowerCase() + 's'](); + }, + + as : function (units) { + units = normalizeUnits(units); + return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's'](); + }, + + lang : moment.fn.lang + }; + + function makeDurationGetter(name) { + moment.duration.fn[name] = function () { + return this._data[name]; + }; + } + + function makeDurationAsGetter(name, factor) { + moment.duration.fn['as' + name] = function () { + return +this / factor; + }; + } + + for (i in unitMillisecondFactors) { + if (unitMillisecondFactors.hasOwnProperty(i)) { + makeDurationAsGetter(i, unitMillisecondFactors[i]); + makeDurationGetter(i.toLowerCase()); + } + } + + makeDurationAsGetter('Weeks', 6048e5); + moment.duration.fn.asMonths = function () { + return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12; + }; + + + /************************************ + Default Lang + ************************************/ + + + // Set default language, other languages will inherit from English. + moment.lang('en', { + ordinal : function (number) { + var b = number % 10, + output = (~~ (number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); + + + /************************************ + Exposing Moment + ************************************/ + + + // CommonJS module is defined + if (hasModule) { + module.exports = moment; + } + /*global ender:false */ + if (typeof ender === 'undefined') { + // here, `this` means `window` in the browser, or `global` on the server + // add `moment` as a global object via a string identifier, + // for Closure Compiler "advanced" mode + this['moment'] = moment; + } + /*global define:false */ + if (typeof define === "function" && define.amd) { + define("moment", [], function () { + return moment; + }); + } +}).call(this); diff --git a/lib/mustache.js b/lib/mustache.js new file mode 100644 index 0000000..932052b --- /dev/null +++ b/lib/mustache.js @@ -0,0 +1,610 @@ +/*! + * mustache.js - Logic-less {{mustache}} templates with JavaScript + * http://github.com/janl/mustache.js + */ + +/*global define: false*/ + +(function (root, factory) { + if (typeof exports === "object" && exports) { + module.exports = factory; // CommonJS + } else if (typeof define === "function" && define.amd) { + define(factory); // AMD + } else { + root.Mustache = factory; // <script> + } +}(this, (function () { + + var exports = {}; + + exports.name = "mustache.js"; + exports.version = "0.7.2"; + exports.tags = ["{{", "}}"]; + + exports.Scanner = Scanner; + exports.Context = Context; + exports.Writer = Writer; + + var whiteRe = /\s*/; + var spaceRe = /\s+/; + var nonSpaceRe = /\S/; + var eqRe = /\s*=/; + var curlyRe = /\s*\}/; + var tagRe = /#|\^|\/|>|\{|&|=|!/; + + // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577 + // See https://github.com/janl/mustache.js/issues/189 + function testRe(re, string) { + return RegExp.prototype.test.call(re, string); + } + + function isWhitespace(string) { + return !testRe(nonSpaceRe, string); + } + + var isArray = Array.isArray || function (obj) { + return Object.prototype.toString.call(obj) === "[object Array]"; + }; + + function escapeRe(string) { + return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); + } + + var entityMap = { + "&": "&", + "<": "<", + ">": ">", + '"': '"', + "'": ''', + "/": '/' + }; + + function escapeHtml(string) { + return String(string).replace(/[&<>"'\/]/g, function (s) { + return entityMap[s]; + }); + } + + // Export the escaping function so that the user may override it. + // See https://github.com/janl/mustache.js/issues/244 + exports.escape = escapeHtml; + + function Scanner(string) { + this.string = string; + this.tail = string; + this.pos = 0; + } + + /** + * Returns `true` if the tail is empty (end of string). + */ + Scanner.prototype.eos = function () { + return this.tail === ""; + }; + + /** + * Tries to match the given regular expression at the current position. + * Returns the matched text if it can match, the empty string otherwise. + */ + Scanner.prototype.scan = function (re) { + var match = this.tail.match(re); + + if (match && match.index === 0) { + this.tail = this.tail.substring(match[0].length); + this.pos += match[0].length; + return match[0]; + } + + return ""; + }; + + /** + * Skips all text until the given regular expression can be matched. Returns + * the skipped string, which is the entire tail if no match can be made. + */ + Scanner.prototype.scanUntil = function (re) { + var match, pos = this.tail.search(re); + + switch (pos) { + case -1: + match = this.tail; + this.pos += this.tail.length; + this.tail = ""; + break; + case 0: + match = ""; + break; + default: + match = this.tail.substring(0, pos); + this.tail = this.tail.substring(pos); + this.pos += pos; + } + + return match; + }; + + function Context(view, parent) { + this.view = view; + this.parent = parent; + this.clearCache(); + } + + Context.make = function (view) { + return (view instanceof Context) ? view : new Context(view); + }; + + Context.prototype.clearCache = function () { + this._cache = {}; + }; + + Context.prototype.push = function (view) { + return new Context(view, this); + }; + + Context.prototype.lookup = function (name) { + var value = this._cache[name]; + + if (!value) { + if (name === ".") { + value = this.view; + } else { + var context = this; + + while (context) { + if (name.indexOf(".") > 0) { + var names = name.split("."), i = 0; + + value = context.view; + + while (value && i < names.length) { + value = value[names[i++]]; + } + } else { + value = context.view[name]; + } + + if (value != null) { + break; + } + + context = context.parent; + } + } + + this._cache[name] = value; + } + + if (typeof value === "function") { + value = value.call(this.view); + } + + return value; + }; + + function Writer() { + this.clearCache(); + } + + Writer.prototype.clearCache = function () { + this._cache = {}; + this._partialCache = {}; + }; + + Writer.prototype.compile = function (template, tags) { + var fn = this._cache[template]; + + if (!fn) { + var tokens = exports.parse(template, tags); + fn = this._cache[template] = this.compileTokens(tokens, template); + } + + return fn; + }; + + Writer.prototype.compilePartial = function (name, template, tags) { + var fn = this.compile(template, tags); + this._partialCache[name] = fn; + return fn; + }; + + Writer.prototype.compileTokens = function (tokens, template) { + var fn = compileTokens(tokens); + var self = this; + + return function (view, partials) { + if (partials) { + if (typeof partials === "function") { + self._loadPartial = partials; + } else { + for (var name in partials) { + self.compilePartial(name, partials[name]); + } + } + } + + return fn(self, Context.make(view), template); + }; + }; + + Writer.prototype.render = function (template, view, partials) { + return this.compile(template)(view, partials); + }; + + Writer.prototype._section = function (name, context, text, callback) { + var value = context.lookup(name); + + switch (typeof value) { + case "object": + if (isArray(value)) { + var buffer = ""; + + for (var i = 0, len = value.length; i < len; ++i) { + buffer += callback(this, context.push(value[i])); + } + + return buffer; + } + + return value ? callback(this, context.push(value)) : ""; + case "function": + var self = this; + var scopedRender = function (template) { + return self.render(template, context); + }; + + var result = value.call(context.view, text, scopedRender); + return result != null ? result : ""; + default: + if (value) { + return callback(this, context); + } + } + + return ""; + }; + + Writer.prototype._inverted = function (name, context, callback) { + var value = context.lookup(name); + + // Use JavaScript's definition of falsy. Include empty arrays. + // See https://github.com/janl/mustache.js/issues/186 + if (!value || (isArray(value) && value.length === 0)) { + return callback(this, context); + } + + return ""; + }; + + Writer.prototype._partial = function (name, context) { + if (!(name in this._partialCache) && this._loadPartial) { + this.compilePartial(name, this._loadPartial(name)); + } + + var fn = this._partialCache[name]; + + return fn ? fn(context) : ""; + }; + + Writer.prototype._name = function (name, context) { + var value = context.lookup(name); + + if (typeof value === "function") { + value = value.call(context.view); + } + + return (value == null) ? "" : String(value); + }; + + Writer.prototype._escaped = function (name, context) { + return exports.escape(this._name(name, context)); + }; + + /** + * Low-level function that compiles the given `tokens` into a function + * that accepts three arguments: a Writer, a Context, and the template. + */ + function compileTokens(tokens) { + var subRenders = {}; + + function subRender(i, tokens, template) { + if (!subRenders[i]) { + var fn = compileTokens(tokens); + subRenders[i] = function (writer, context) { + return fn(writer, context, template); + }; + } + + return subRenders[i]; + } + + return function (writer, context, template) { + var buffer = ""; + var token, sectionText; + + for (var i = 0, len = tokens.length; i < len; ++i) { + token = tokens[i]; + + switch (token[0]) { + case "#": + sectionText = template.slice(token[3], token[5]); + buffer += writer._section(token[1], context, sectionText, subRender(i, token[4], template)); + break; + case "^": + buffer += writer._inverted(token[1], context, subRender(i, token[4], template)); + break; + case ">": + buffer += writer._partial(token[1], context); + break; + case "&": + buffer += writer._name(token[1], context); + break; + case "name": + buffer += writer._escaped(token[1], context); + break; + case "text": + buffer += token[1]; + break; + } + } + + return buffer; + }; + } + + /** + * Forms the given array of `tokens` into a nested tree structure where + * tokens that represent a section have two additional items: 1) an array of + * all tokens that appear in that section and 2) the index in the original + * template that represents the end of that section. + */ + function nestTokens(tokens) { + var tree = []; + var collector = tree; + var sections = []; + + var token; + for (var i = 0, len = tokens.length; i < len; ++i) { + token = tokens[i]; + switch (token[0]) { + case '#': + case '^': + sections.push(token); + collector.push(token); + collector = token[4] = []; + break; + case '/': + var section = sections.pop(); + section[5] = token[2]; + collector = sections.length > 0 ? sections[sections.length - 1][4] : tree; + break; + default: + collector.push(token); + } + } + + return tree; + } + + /** + * Combines the values of consecutive text tokens in the given `tokens` array + * to a single token. + */ + function squashTokens(tokens) { + var squashedTokens = []; + + var token, lastToken; + for (var i = 0, len = tokens.length; i < len; ++i) { + token = tokens[i]; + if (token[0] === 'text' && lastToken && lastToken[0] === 'text') { + lastToken[1] += token[1]; + lastToken[3] = token[3]; + } else { + lastToken = token; + squashedTokens.push(token); + } + } + + return squashedTokens; + } + + function escapeTags(tags) { + return [ + new RegExp(escapeRe(tags[0]) + "\\s*"), + new RegExp("\\s*" + escapeRe(tags[1])) + ]; + } + + /** + * Breaks up the given `template` string into a tree of token objects. If + * `tags` is given here it must be an array with two string values: the + * opening and closing tags used in the template (e.g. ["<%", "%>"]). Of + * course, the default is to use mustaches (i.e. Mustache.tags). + */ + exports.parse = function (template, tags) { + template = template || ''; + tags = tags || exports.tags; + + if (typeof tags === 'string') tags = tags.split(spaceRe); + if (tags.length !== 2) { + throw new Error('Invalid tags: ' + tags.join(', ')); + } + + var tagRes = escapeTags(tags); + var scanner = new Scanner(template); + + var sections = []; // Stack to hold section tokens + var tokens = []; // Buffer to hold the tokens + var spaces = []; // Indices of whitespace tokens on the current line + var hasTag = false; // Is there a {{tag}} on the current line? + var nonSpace = false; // Is there a non-space char on the current line? + + // Strips all whitespace tokens array for the current line + // if there was a {{#tag}} on it and otherwise only space. + function stripSpace() { + if (hasTag && !nonSpace) { + while (spaces.length) { + tokens.splice(spaces.pop(), 1); + } + } else { + spaces = []; + } + + hasTag = false; + nonSpace = false; + } + + var start, type, value, chr; + while (!scanner.eos()) { + start = scanner.pos; + value = scanner.scanUntil(tagRes[0]); + + if (value) { + for (var i = 0, len = value.length; i < len; ++i) { + chr = value.charAt(i); + + if (isWhitespace(chr)) { + spaces.push(tokens.length); + } else { + nonSpace = true; + } + + tokens.push(["text", chr, start, start + 1]); + start += 1; + + if (chr === "\n") { + stripSpace(); // Check for whitespace on the current line. + } + } + } + + start = scanner.pos; + + // Match the opening tag. + if (!scanner.scan(tagRes[0])) { + break; + } + + hasTag = true; + type = scanner.scan(tagRe) || "name"; + + // Skip any whitespace between tag and value. + scanner.scan(whiteRe); + + // Extract the tag value. + if (type === "=") { + value = scanner.scanUntil(eqRe); + scanner.scan(eqRe); + scanner.scanUntil(tagRes[1]); + } else if (type === "{") { + var closeRe = new RegExp("\\s*" + escapeRe("}" + tags[1])); + value = scanner.scanUntil(closeRe); + scanner.scan(curlyRe); + scanner.scanUntil(tagRes[1]); + type = "&"; + } else { + value = scanner.scanUntil(tagRes[1]); + } + + // Match the closing tag. + if (!scanner.scan(tagRes[1])) { + throw new Error('Unclosed tag at ' + scanner.pos); + } + + // Check section nesting. + if (type === '/') { + if (sections.length === 0) { + throw new Error('Unopened section "' + value + '" at ' + start); + } + + var section = sections.pop(); + + if (section[1] !== value) { + throw new Error('Unclosed section "' + section[1] + '" at ' + start); + } + } + + var token = [type, value, start, scanner.pos]; + tokens.push(token); + + if (type === '#' || type === '^') { + sections.push(token); + } else if (type === "name" || type === "{" || type === "&") { + nonSpace = true; + } else if (type === "=") { + // Set the tags for the next time around. + tags = value.split(spaceRe); + + if (tags.length !== 2) { + throw new Error('Invalid tags at ' + start + ': ' + tags.join(', ')); + } + + tagRes = escapeTags(tags); + } + } + + // Make sure there are no open sections when we're done. + var section = sections.pop(); + if (section) { + throw new Error('Unclosed section "' + section[1] + '" at ' + scanner.pos); + } + + return nestTokens(squashTokens(tokens)); + }; + + // The high-level clearCache, compile, compilePartial, and render functions + // use this default writer. + var _writer = new Writer(); + + /** + * Clears all cached templates and partials in the default writer. + */ + exports.clearCache = function () { + return _writer.clearCache(); + }; + + /** + * Compiles the given `template` to a reusable function using the default + * writer. + */ + exports.compile = function (template, tags) { + return _writer.compile(template, tags); + }; + + /** + * Compiles the partial with the given `name` and `template` to a reusable + * function using the default writer. + */ + exports.compilePartial = function (name, template, tags) { + return _writer.compilePartial(name, template, tags); + }; + + /** + * Compiles the given array of tokens (the output of a parse) to a reusable + * function using the default writer. + */ + exports.compileTokens = function (tokens, template) { + return _writer.compileTokens(tokens, template); + }; + + /** + * Renders the `template` with the given `view` and `partials` using the + * default writer. + */ + exports.render = function (template, view, partials) { + return _writer.render(template, view, partials); + }; + + // This is here for backwards compatibility with 0.4.x. + exports.to_html = function (template, view, partials, send) { + var result = exports.render(template, view, partials); + + if (typeof send === "function") { + send(result); + } else { + return result; + } + }; + + return exports; + +}()))); diff --git a/lib/require.js b/lib/require.js new file mode 100644 index 0000000..5b26875 --- /dev/null +++ b/lib/require.js @@ -0,0 +1,2000 @@ +/** vim: et:ts=4:sw=4:sts=4 + * @license RequireJS 2.1.4 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/jrburke/requirejs for details + */ +//Not using strict: uneven strict support in browsers, #392, and causes +//problems with requirejs.exec()/transpiler plugins that may not be strict. +/*jslint regexp: true, nomen: true, sloppy: true */ +/*global window, navigator, document, importScripts, setTimeout, opera */ + +var requirejs, require, define; +(function (global) { + var req, s, head, baseElement, dataMain, src, + interactiveScript, currentlyAddingScript, mainScript, subPath, + version = '2.1.4', + commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg, + cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g, + jsSuffixRegExp = /\.js$/, + currDirRegExp = /^\.\//, + op = Object.prototype, + ostring = op.toString, + hasOwn = op.hasOwnProperty, + ap = Array.prototype, + apsp = ap.splice, + isBrowser = !!(typeof window !== 'undefined' && navigator && document), + isWebWorker = !isBrowser && typeof importScripts !== 'undefined', + //PS3 indicates loaded and complete, but need to wait for complete + //specifically. Sequence is 'loading', 'loaded', execution, + // then 'complete'. The UA check is unfortunate, but not sure how + //to feature test w/o causing perf issues. + readyRegExp = isBrowser && navigator.platform === 'PLAYSTATION 3' ? + /^complete$/ : /^(complete|loaded)$/, + defContextName = '_', + //Oh the tragedy, detecting opera. See the usage of isOpera for reason. + isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]', + contexts = {}, + cfg = {}, + globalDefQueue = [], + useInteractive = false; + + function isFunction(it) { + return ostring.call(it) === '[object Function]'; + } + + function isArray(it) { + return ostring.call(it) === '[object Array]'; + } + + /** + * Helper function for iterating over an array. If the func returns + * a true value, it will break out of the loop. + */ + function each(ary, func) { + if (ary) { + var i; + for (i = 0; i < ary.length; i += 1) { + if (ary[i] && func(ary[i], i, ary)) { + break; + } + } + } + } + + /** + * Helper function for iterating over an array backwards. If the func + * returns a true value, it will break out of the loop. + */ + function eachReverse(ary, func) { + if (ary) { + var i; + for (i = ary.length - 1; i > -1; i -= 1) { + if (ary[i] && func(ary[i], i, ary)) { + break; + } + } + } + } + + function hasProp(obj, prop) { + return hasOwn.call(obj, prop); + } + + function getOwn(obj, prop) { + return hasProp(obj, prop) && obj[prop]; + } + + /** + * Cycles over properties in an object and calls a function for each + * property value. If the function returns a truthy value, then the + * iteration is stopped. + */ + function eachProp(obj, func) { + var prop; + for (prop in obj) { + if (hasProp(obj, prop)) { + if (func(obj[prop], prop)) { + break; + } + } + } + } + + /** + * Simple function to mix in properties from source into target, + * but only if target does not already have a property of the same name. + */ + function mixin(target, source, force, deepStringMixin) { + if (source) { + eachProp(source, function (value, prop) { + if (force || !hasProp(target, prop)) { + if (deepStringMixin && typeof value !== 'string') { + if (!target[prop]) { + target[prop] = {}; + } + mixin(target[prop], value, force, deepStringMixin); + } else { + target[prop] = value; + } + } + }); + } + return target; + } + + //Similar to Function.prototype.bind, but the 'this' object is specified + //first, since it is easier to read/figure out what 'this' will be. + function bind(obj, fn) { + return function () { + return fn.apply(obj, arguments); + }; + } + + function scripts() { + return document.getElementsByTagName('script'); + } + + //Allow getting a global that expressed in + //dot notation, like 'a.b.c'. + function getGlobal(value) { + if (!value) { + return value; + } + var g = global; + each(value.split('.'), function (part) { + g = g[part]; + }); + return g; + } + + /** + * Constructs an error with a pointer to an URL with more information. + * @param {String} id the error ID that maps to an ID on a web page. + * @param {String} message human readable error. + * @param {Error} [err] the original error, if there is one. + * + * @returns {Error} + */ + function makeError(id, msg, err, requireModules) { + var e = new Error(msg + '\nhttp://requirejs.org/docs/errors.html#' + id); + e.requireType = id; + e.requireModules = requireModules; + if (err) { + e.originalError = err; + } + return e; + } + + if (typeof define !== 'undefined') { + //If a define is already in play via another AMD loader, + //do not overwrite. + return; + } + + if (typeof requirejs !== 'undefined') { + if (isFunction(requirejs)) { + //Do not overwrite and existing requirejs instance. + return; + } + cfg = requirejs; + requirejs = undefined; + } + + //Allow for a require config object + if (typeof require !== 'undefined' && !isFunction(require)) { + //assume it is a config object. + cfg = require; + require = undefined; + } + + function newContext(contextName) { + var inCheckLoaded, Module, context, handlers, + checkLoadedTimeoutId, + config = { + waitSeconds: 7, + baseUrl: './', + paths: {}, + pkgs: {}, + shim: {}, + map: {}, + config: {} + }, + registry = {}, + undefEvents = {}, + defQueue = [], + defined = {}, + urlFetched = {}, + requireCounter = 1, + unnormalizedCounter = 1; + + /** + * Trims the . and .. from an array of path segments. + * It will keep a leading path segment if a .. will become + * the first path segment, to help with module name lookups, + * which act like paths, but can be remapped. But the end result, + * all paths that use this function should look normalized. + * NOTE: this method MODIFIES the input array. + * @param {Array} ary the array of path segments. + */ + function trimDots(ary) { + var i, part; + for (i = 0; ary[i]; i += 1) { + part = ary[i]; + if (part === '.') { + ary.splice(i, 1); + i -= 1; + } else if (part === '..') { + if (i === 1 && (ary[2] === '..' || ary[0] === '..')) { + //End of the line. Keep at least one non-dot + //path segment at the front so it can be mapped + //correctly to disk. Otherwise, there is likely + //no path mapping for a path starting with '..'. + //This can still fail, but catches the most reasonable + //uses of .. + break; + } else if (i > 0) { + ary.splice(i - 1, 2); + i -= 2; + } + } + } + } + + /** + * Given a relative module name, like ./something, normalize it to + * a real name that can be mapped to a path. + * @param {String} name the relative name + * @param {String} baseName a real name that the name arg is relative + * to. + * @param {Boolean} applyMap apply the map config to the value. Should + * only be done if this normalization is for a dependency ID. + * @returns {String} normalized name + */ + function normalize(name, baseName, applyMap) { + var pkgName, pkgConfig, mapValue, nameParts, i, j, nameSegment, + foundMap, foundI, foundStarMap, starI, + baseParts = baseName && baseName.split('/'), + normalizedBaseParts = baseParts, + map = config.map, + starMap = map && map['*']; + + //Adjust any relative paths. + if (name && name.charAt(0) === '.') { + //If have a base name, try to normalize against it, + //otherwise, assume it is a top-level require that will + //be relative to baseUrl in the end. + if (baseName) { + if (getOwn(config.pkgs, baseName)) { + //If the baseName is a package name, then just treat it as one + //name to concat the name with. + normalizedBaseParts = baseParts = [baseName]; + } else { + //Convert baseName to array, and lop off the last part, + //so that . matches that 'directory' and not name of the baseName's + //module. For instance, baseName of 'one/two/three', maps to + //'one/two/three.js', but we want the directory, 'one/two' for + //this normalization. + normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); + } + + name = normalizedBaseParts.concat(name.split('/')); + trimDots(name); + + //Some use of packages may use a . path to reference the + //'main' module name, so normalize for that. + pkgConfig = getOwn(config.pkgs, (pkgName = name[0])); + name = name.join('/'); + if (pkgConfig && name === pkgName + '/' + pkgConfig.main) { + name = pkgName; + } + } else if (name.indexOf('./') === 0) { + // No baseName, so this is ID is resolved relative + // to baseUrl, pull off the leading dot. + name = name.substring(2); + } + } + + //Apply map config if available. + if (applyMap && (baseParts || starMap) && map) { + nameParts = name.split('/'); + + for (i = nameParts.length; i > 0; i -= 1) { + nameSegment = nameParts.slice(0, i).join('/'); + + if (baseParts) { + //Find the longest baseName segment match in the config. + //So, do joins on the biggest to smallest lengths of baseParts. + for (j = baseParts.length; j > 0; j -= 1) { + mapValue = getOwn(map, baseParts.slice(0, j).join('/')); + + //baseName segment has config, find if it has one for + //this name. + if (mapValue) { + mapValue = getOwn(mapValue, nameSegment); + if (mapValue) { + //Match, update name to the new value. + foundMap = mapValue; + foundI = i; + break; + } + } + } + } + + if (foundMap) { + break; + } + + //Check for a star map match, but just hold on to it, + //if there is a shorter segment match later in a matching + //config, then favor over this star map. + if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) { + foundStarMap = getOwn(starMap, nameSegment); + starI = i; + } + } + + if (!foundMap && foundStarMap) { + foundMap = foundStarMap; + foundI = starI; + } + + if (foundMap) { + nameParts.splice(0, foundI, foundMap); + name = nameParts.join('/'); + } + } + + return name; + } + + function removeScript(name) { + if (isBrowser) { + each(scripts(), function (scriptNode) { + if (scriptNode.getAttribute('data-requiremodule') === name && + scriptNode.getAttribute('data-requirecontext') === context.contextName) { + scriptNode.parentNode.removeChild(scriptNode); + return true; + } + }); + } + } + + function hasPathFallback(id) { + var pathConfig = getOwn(config.paths, id); + if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) { + removeScript(id); + //Pop off the first array value, since it failed, and + //retry + pathConfig.shift(); + context.require.undef(id); + context.require([id]); + return true; + } + } + + //Turns a plugin!resource to [plugin, resource] + //with the plugin being undefined if the name + //did not have a plugin prefix. + function splitPrefix(name) { + var prefix, + index = name ? name.indexOf('!') : -1; + if (index > -1) { + prefix = name.substring(0, index); + name = name.substring(index + 1, name.length); + } + return [prefix, name]; + } + + /** + * Creates a module mapping that includes plugin prefix, module + * name, and path. If parentModuleMap is provided it will + * also normalize the name via require.normalize() + * + * @param {String} name the module name + * @param {String} [parentModuleMap] parent module map + * for the module name, used to resolve relative names. + * @param {Boolean} isNormalized: is the ID already normalized. + * This is true if this call is done for a define() module ID. + * @param {Boolean} applyMap: apply the map config to the ID. + * Should only be true if this map is for a dependency. + * + * @returns {Object} + */ + function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) { + var url, pluginModule, suffix, nameParts, + prefix = null, + parentName = parentModuleMap ? parentModuleMap.name : null, + originalName = name, + isDefine = true, + normalizedName = ''; + + //If no name, then it means it is a require call, generate an + //internal name. + if (!name) { + isDefine = false; + name = '_@r' + (requireCounter += 1); + } + + nameParts = splitPrefix(name); + prefix = nameParts[0]; + name = nameParts[1]; + + if (prefix) { + prefix = normalize(prefix, parentName, applyMap); + pluginModule = getOwn(defined, prefix); + } + + //Account for relative paths if there is a base name. + if (name) { + if (prefix) { + if (pluginModule && pluginModule.normalize) { + //Plugin is loaded, use its normalize method. + normalizedName = pluginModule.normalize(name, function (name) { + return normalize(name, parentName, applyMap); + }); + } else { + normalizedName = normalize(name, parentName, applyMap); + } + } else { + //A regular module. + normalizedName = normalize(name, parentName, applyMap); + + //Normalized name may be a plugin ID due to map config + //application in normalize. The map config values must + //already be normalized, so do not need to redo that part. + nameParts = splitPrefix(normalizedName); + prefix = nameParts[0]; + normalizedName = nameParts[1]; + isNormalized = true; + + url = context.nameToUrl(normalizedName); + } + } + + //If the id is a plugin id that cannot be determined if it needs + //normalization, stamp it with a unique ID so two matching relative + //ids that may conflict can be separate. + suffix = prefix && !pluginModule && !isNormalized ? + '_unnormalized' + (unnormalizedCounter += 1) : + ''; + + return { + prefix: prefix, + name: normalizedName, + parentMap: parentModuleMap, + unnormalized: !!suffix, + url: url, + originalName: originalName, + isDefine: isDefine, + id: (prefix ? + prefix + '!' + normalizedName : + normalizedName) + suffix + }; + } + + function getModule(depMap) { + var id = depMap.id, + mod = getOwn(registry, id); + + if (!mod) { + mod = registry[id] = new context.Module(depMap); + } + + return mod; + } + + function on(depMap, name, fn) { + var id = depMap.id, + mod = getOwn(registry, id); + + if (hasProp(defined, id) && + (!mod || mod.defineEmitComplete)) { + if (name === 'defined') { + fn(defined[id]); + } + } else { + getModule(depMap).on(name, fn); + } + } + + function onError(err, errback) { + var ids = err.requireModules, + notified = false; + + if (errback) { + errback(err); + } else { + each(ids, function (id) { + var mod = getOwn(registry, id); + if (mod) { + //Set error on module, so it skips timeout checks. + mod.error = err; + if (mod.events.error) { + notified = true; + mod.emit('error', err); + } + } + }); + + if (!notified) { + req.onError(err); + } + } + } + + /** + * Internal method to transfer globalQueue items to this context's + * defQueue. + */ + function takeGlobalQueue() { + //Push all the globalDefQueue items into the context's defQueue + if (globalDefQueue.length) { + //Array splice in the values since the context code has a + //local var ref to defQueue, so cannot just reassign the one + //on context. + apsp.apply(defQueue, + [defQueue.length - 1, 0].concat(globalDefQueue)); + globalDefQueue = []; + } + } + + handlers = { + 'require': function (mod) { + if (mod.require) { + return mod.require; + } else { + return (mod.require = context.makeRequire(mod.map)); + } + }, + 'exports': function (mod) { + mod.usingExports = true; + if (mod.map.isDefine) { + if (mod.exports) { + return mod.exports; + } else { + return (mod.exports = defined[mod.map.id] = {}); + } + } + }, + 'module': function (mod) { + if (mod.module) { + return mod.module; + } else { + return (mod.module = { + id: mod.map.id, + uri: mod.map.url, + config: function () { + return (config.config && getOwn(config.config, mod.map.id)) || {}; + }, + exports: defined[mod.map.id] + }); + } + } + }; + + function cleanRegistry(id) { + //Clean up machinery used for waiting modules. + delete registry[id]; + } + + function breakCycle(mod, traced, processed) { + var id = mod.map.id; + + if (mod.error) { + mod.emit('error', mod.error); + } else { + traced[id] = true; + each(mod.depMaps, function (depMap, i) { + var depId = depMap.id, + dep = getOwn(registry, depId); + + //Only force things that have not completed + //being defined, so still in the registry, + //and only if it has not been matched up + //in the module already. + if (dep && !mod.depMatched[i] && !processed[depId]) { + if (getOwn(traced, depId)) { + mod.defineDep(i, defined[depId]); + mod.check(); //pass false? + } else { + breakCycle(dep, traced, processed); + } + } + }); + processed[id] = true; + } + } + + function checkLoaded() { + var map, modId, err, usingPathFallback, + waitInterval = config.waitSeconds * 1000, + //It is possible to disable the wait interval by using waitSeconds of 0. + expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(), + noLoads = [], + reqCalls = [], + stillLoading = false, + needCycleCheck = true; + + //Do not bother if this call was a result of a cycle break. + if (inCheckLoaded) { + return; + } + + inCheckLoaded = true; + + //Figure out the state of all the modules. + eachProp(registry, function (mod) { + map = mod.map; + modId = map.id; + + //Skip things that are not enabled or in error state. + if (!mod.enabled) { + return; + } + + if (!map.isDefine) { + reqCalls.push(mod); + } + + if (!mod.error) { + //If the module should be executed, and it has not + //been inited and time is up, remember it. + if (!mod.inited && expired) { + if (hasPathFallback(modId)) { + usingPathFallback = true; + stillLoading = true; + } else { + noLoads.push(modId); + removeScript(modId); + } + } else if (!mod.inited && mod.fetched && map.isDefine) { + stillLoading = true; + if (!map.prefix) { + //No reason to keep looking for unfinished + //loading. If the only stillLoading is a + //plugin resource though, keep going, + //because it may be that a plugin resource + //is waiting on a non-plugin cycle. + return (needCycleCheck = false); + } + } + } + }); + + if (expired && noLoads.length) { + //If wait time expired, throw error of unloaded modules. + err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads); + err.contextName = context.contextName; + return onError(err); + } + + //Not expired, check for a cycle. + if (needCycleCheck) { + each(reqCalls, function (mod) { + breakCycle(mod, {}, {}); + }); + } + + //If still waiting on loads, and the waiting load is something + //other than a plugin resource, or there are still outstanding + //scripts, then just try back later. + if ((!expired || usingPathFallback) && stillLoading) { + //Something is still waiting to load. Wait for it, but only + //if a timeout is not already in effect. + if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) { + checkLoadedTimeoutId = setTimeout(function () { + checkLoadedTimeoutId = 0; + checkLoaded(); + }, 50); + } + } + + inCheckLoaded = false; + } + + Module = function (map) { + this.events = getOwn(undefEvents, map.id) || {}; + this.map = map; + this.shim = getOwn(config.shim, map.id); + this.depExports = []; + this.depMaps = []; + this.depMatched = []; + this.pluginMaps = {}; + this.depCount = 0; + + /* this.exports this.factory + this.depMaps = [], + this.enabled, this.fetched + */ + }; + + Module.prototype = { + init: function (depMaps, factory, errback, options) { + options = options || {}; + + //Do not do more inits if already done. Can happen if there + //are multiple define calls for the same module. That is not + //a normal, common case, but it is also not unexpected. + if (this.inited) { + return; + } + + this.factory = factory; + + if (errback) { + //Register for errors on this module. + this.on('error', errback); + } else if (this.events.error) { + //If no errback already, but there are error listeners + //on this module, set up an errback to pass to the deps. + errback = bind(this, function (err) { + this.emit('error', err); + }); + } + + //Do a copy of the dependency array, so that + //source inputs are not modified. For example + //"shim" deps are passed in here directly, and + //doing a direct modification of the depMaps array + //would affect that config. + this.depMaps = depMaps && depMaps.slice(0); + + this.errback = errback; + + //Indicate this module has be initialized + this.inited = true; + + this.ignore = options.ignore; + + //Could have option to init this module in enabled mode, + //or could have been previously marked as enabled. However, + //the dependencies are not known until init is called. So + //if enabled previously, now trigger dependencies as enabled. + if (options.enabled || this.enabled) { + //Enable this module and dependencies. + //Will call this.check() + this.enable(); + } else { + this.check(); + } + }, + + defineDep: function (i, depExports) { + //Because of cycles, defined callback for a given + //export can be called more than once. + if (!this.depMatched[i]) { + this.depMatched[i] = true; + this.depCount -= 1; + this.depExports[i] = depExports; + } + }, + + fetch: function () { + if (this.fetched) { + return; + } + this.fetched = true; + + context.startTime = (new Date()).getTime(); + + var map = this.map; + + //If the manager is for a plugin managed resource, + //ask the plugin to load it now. + if (this.shim) { + context.makeRequire(this.map, { + enableBuildCallback: true + })(this.shim.deps || [], bind(this, function () { + return map.prefix ? this.callPlugin() : this.load(); + })); + } else { + //Regular dependency. + return map.prefix ? this.callPlugin() : this.load(); + } + }, + + load: function () { + var url = this.map.url; + + //Regular dependency. + if (!urlFetched[url]) { + urlFetched[url] = true; + context.load(this.map.id, url); + } + }, + + /** + * Checks is the module is ready to define itself, and if so, + * define it. + */ + check: function () { + if (!this.enabled || this.enabling) { + return; + } + + var err, cjsModule, + id = this.map.id, + depExports = this.depExports, + exports = this.exports, + factory = this.factory; + + if (!this.inited) { + this.fetch(); + } else if (this.error) { + this.emit('error', this.error); + } else if (!this.defining) { + //The factory could trigger another require call + //that would result in checking this module to + //define itself again. If already in the process + //of doing that, skip this work. + this.defining = true; + + if (this.depCount < 1 && !this.defined) { + if (isFunction(factory)) { + //If there is an error listener, favor passing + //to that instead of throwing an error. + if (this.events.error) { + try { + exports = context.execCb(id, factory, depExports, exports); + } catch (e) { + err = e; + } + } else { + exports = context.execCb(id, factory, depExports, exports); + } + + if (this.map.isDefine) { + //If setting exports via 'module' is in play, + //favor that over return value and exports. After that, + //favor a non-undefined return value over exports use. + cjsModule = this.module; + if (cjsModule && + cjsModule.exports !== undefined && + //Make sure it is not already the exports value + cjsModule.exports !== this.exports) { + exports = cjsModule.exports; + } else if (exports === undefined && this.usingExports) { + //exports already set the defined value. + exports = this.exports; + } + } + + if (err) { + err.requireMap = this.map; + err.requireModules = [this.map.id]; + err.requireType = 'define'; + return onError((this.error = err)); + } + + } else { + //Just a literal value + exports = factory; + } + + this.exports = exports; + + if (this.map.isDefine && !this.ignore) { + defined[id] = exports; + + if (req.onResourceLoad) { + req.onResourceLoad(context, this.map, this.depMaps); + } + } + + //Clean up + delete registry[id]; + + this.defined = true; + } + + //Finished the define stage. Allow calling check again + //to allow define notifications below in the case of a + //cycle. + this.defining = false; + + if (this.defined && !this.defineEmitted) { + this.defineEmitted = true; + this.emit('defined', this.exports); + this.defineEmitComplete = true; + } + + } + }, + + callPlugin: function () { + var map = this.map, + id = map.id, + //Map already normalized the prefix. + pluginMap = makeModuleMap(map.prefix); + + //Mark this as a dependency for this plugin, so it + //can be traced for cycles. + this.depMaps.push(pluginMap); + + on(pluginMap, 'defined', bind(this, function (plugin) { + var load, normalizedMap, normalizedMod, + name = this.map.name, + parentName = this.map.parentMap ? this.map.parentMap.name : null, + localRequire = context.makeRequire(map.parentMap, { + enableBuildCallback: true + }); + + //If current map is not normalized, wait for that + //normalized name to load instead of continuing. + if (this.map.unnormalized) { + //Normalize the ID if the plugin allows it. + if (plugin.normalize) { + name = plugin.normalize(name, function (name) { + return normalize(name, parentName, true); + }) || ''; + } + + //prefix and name should already be normalized, no need + //for applying map config again either. + normalizedMap = makeModuleMap(map.prefix + '!' + name, + this.map.parentMap); + on(normalizedMap, + 'defined', bind(this, function (value) { + this.init([], function () { return value; }, null, { + enabled: true, + ignore: true + }); + })); + + normalizedMod = getOwn(registry, normalizedMap.id); + if (normalizedMod) { + //Mark this as a dependency for this plugin, so it + //can be traced for cycles. + this.depMaps.push(normalizedMap); + + if (this.events.error) { + normalizedMod.on('error', bind(this, function (err) { + this.emit('error', err); + })); + } + normalizedMod.enable(); + } + + return; + } + + load = bind(this, function (value) { + this.init([], function () { return value; }, null, { + enabled: true + }); + }); + + load.error = bind(this, function (err) { + this.inited = true; + this.error = err; + err.requireModules = [id]; + + //Remove temp unnormalized modules for this module, + //since they will never be resolved otherwise now. + eachProp(registry, function (mod) { + if (mod.map.id.indexOf(id + '_unnormalized') === 0) { + cleanRegistry(mod.map.id); + } + }); + + onError(err); + }); + + //Allow plugins to load other code without having to know the + //context or how to 'complete' the load. + load.fromText = bind(this, function (text, textAlt) { + /*jslint evil: true */ + var moduleName = map.name, + moduleMap = makeModuleMap(moduleName), + hasInteractive = useInteractive; + + //As of 2.1.0, support just passing the text, to reinforce + //fromText only being called once per resource. Still + //support old style of passing moduleName but discard + //that moduleName in favor of the internal ref. + if (textAlt) { + text = textAlt; + } + + //Turn off interactive script matching for IE for any define + //calls in the text, then turn it back on at the end. + if (hasInteractive) { + useInteractive = false; + } + + //Prime the system by creating a module instance for + //it. + getModule(moduleMap); + + //Transfer any config to this other module. + if (hasProp(config.config, id)) { + config.config[moduleName] = config.config[id]; + } + + try { + req.exec(text); + } catch (e) { + return onError(makeError('fromtexteval', + 'fromText eval for ' + id + + ' failed: ' + e, + e, + [id])); + } + + if (hasInteractive) { + useInteractive = true; + } + + //Mark this as a dependency for the plugin + //resource + this.depMaps.push(moduleMap); + + //Support anonymous modules. + context.completeLoad(moduleName); + + //Bind the value of that module to the value for this + //resource ID. + localRequire([moduleName], load); + }); + + //Use parentName here since the plugin's name is not reliable, + //could be some weird string with no path that actually wants to + //reference the parentName's path. + plugin.load(map.name, localRequire, load, config); + })); + + context.enable(pluginMap, this); + this.pluginMaps[pluginMap.id] = pluginMap; + }, + + enable: function () { + this.enabled = true; + + //Set flag mentioning that the module is enabling, + //so that immediate calls to the defined callbacks + //for dependencies do not trigger inadvertent load + //with the depCount still being zero. + this.enabling = true; + + //Enable each dependency + each(this.depMaps, bind(this, function (depMap, i) { + var id, mod, handler; + + if (typeof depMap === 'string') { + //Dependency needs to be converted to a depMap + //and wired up to this module. + depMap = makeModuleMap(depMap, + (this.map.isDefine ? this.map : this.map.parentMap), + false, + !this.skipMap); + this.depMaps[i] = depMap; + + handler = getOwn(handlers, depMap.id); + + if (handler) { + this.depExports[i] = handler(this); + return; + } + + this.depCount += 1; + + on(depMap, 'defined', bind(this, function (depExports) { + this.defineDep(i, depExports); + this.check(); + })); + + if (this.errback) { + on(depMap, 'error', this.errback); + } + } + + id = depMap.id; + mod = registry[id]; + + //Skip special modules like 'require', 'exports', 'module' + //Also, don't call enable if it is already enabled, + //important in circular dependency cases. + if (!hasProp(handlers, id) && mod && !mod.enabled) { + context.enable(depMap, this); + } + })); + + //Enable each plugin that is used in + //a dependency + eachProp(this.pluginMaps, bind(this, function (pluginMap) { + var mod = getOwn(registry, pluginMap.id); + if (mod && !mod.enabled) { + context.enable(pluginMap, this); + } + })); + + this.enabling = false; + + this.check(); + }, + + on: function (name, cb) { + var cbs = this.events[name]; + if (!cbs) { + cbs = this.events[name] = []; + } + cbs.push(cb); + }, + + emit: function (name, evt) { + each(this.events[name], function (cb) { + cb(evt); + }); + if (name === 'error') { + //Now that the error handler was triggered, remove + //the listeners, since this broken Module instance + //can stay around for a while in the registry. + delete this.events[name]; + } + } + }; + + function callGetModule(args) { + //Skip modules already defined. + if (!hasProp(defined, args[0])) { + getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]); + } + } + + function removeListener(node, func, name, ieName) { + //Favor detachEvent because of IE9 + //issue, see attachEvent/addEventListener comment elsewhere + //in this file. + if (node.detachEvent && !isOpera) { + //Probably IE. If not it will throw an error, which will be + //useful to know. + if (ieName) { + node.detachEvent(ieName, func); + } + } else { + node.removeEventListener(name, func, false); + } + } + + /** + * Given an event from a script node, get the requirejs info from it, + * and then removes the event listeners on the node. + * @param {Event} evt + * @returns {Object} + */ + function getScriptData(evt) { + //Using currentTarget instead of target for Firefox 2.0's sake. Not + //all old browsers will be supported, but this one was easy enough + //to support and still makes sense. + var node = evt.currentTarget || evt.srcElement; + + //Remove the listeners once here. + removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange'); + removeListener(node, context.onScriptError, 'error'); + + return { + node: node, + id: node && node.getAttribute('data-requiremodule') + }; + } + + function intakeDefines() { + var args; + + //Any defined modules in the global queue, intake them now. + takeGlobalQueue(); + + //Make sure any remaining defQueue items get properly processed. + while (defQueue.length) { + args = defQueue.shift(); + if (args[0] === null) { + return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1])); + } else { + //args are id, deps, factory. Should be normalized by the + //define() function. + callGetModule(args); + } + } + } + + context = { + config: config, + contextName: contextName, + registry: registry, + defined: defined, + urlFetched: urlFetched, + defQueue: defQueue, + Module: Module, + makeModuleMap: makeModuleMap, + nextTick: req.nextTick, + + /** + * Set a configuration for the context. + * @param {Object} cfg config object to integrate. + */ + configure: function (cfg) { + //Make sure the baseUrl ends in a slash. + if (cfg.baseUrl) { + if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') { + cfg.baseUrl += '/'; + } + } + + //Save off the paths and packages since they require special processing, + //they are additive. + var pkgs = config.pkgs, + shim = config.shim, + objs = { + paths: true, + config: true, + map: true + }; + + eachProp(cfg, function (value, prop) { + if (objs[prop]) { + if (prop === 'map') { + mixin(config[prop], value, true, true); + } else { + mixin(config[prop], value, true); + } + } else { + config[prop] = value; + } + }); + + //Merge shim + if (cfg.shim) { + eachProp(cfg.shim, function (value, id) { + //Normalize the structure + if (isArray(value)) { + value = { + deps: value + }; + } + if ((value.exports || value.init) && !value.exportsFn) { + value.exportsFn = context.makeShimExports(value); + } + shim[id] = value; + }); + config.shim = shim; + } + + //Adjust packages if necessary. + if (cfg.packages) { + each(cfg.packages, function (pkgObj) { + var location; + + pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj; + location = pkgObj.location; + + //Create a brand new object on pkgs, since currentPackages can + //be passed in again, and config.pkgs is the internal transformed + //state for all package configs. + pkgs[pkgObj.name] = { + name: pkgObj.name, + location: location || pkgObj.name, + //Remove leading dot in main, so main paths are normalized, + //and remove any trailing .js, since different package + //envs have different conventions: some use a module name, + //some use a file name. + main: (pkgObj.main || 'main') + .replace(currDirRegExp, '') + .replace(jsSuffixRegExp, '') + }; + }); + + //Done with modifications, assing packages back to context config + config.pkgs = pkgs; + } + + //If there are any "waiting to execute" modules in the registry, + //update the maps for them, since their info, like URLs to load, + //may have changed. + eachProp(registry, function (mod, id) { + //If module already has init called, since it is too + //late to modify them, and ignore unnormalized ones + //since they are transient. + if (!mod.inited && !mod.map.unnormalized) { + mod.map = makeModuleMap(id); + } + }); + + //If a deps array or a config callback is specified, then call + //require with those args. This is useful when require is defined as a + //config object before require.js is loaded. + if (cfg.deps || cfg.callback) { + context.require(cfg.deps || [], cfg.callback); + } + }, + + makeShimExports: function (value) { + function fn() { + var ret; + if (value.init) { + ret = value.init.apply(global, arguments); + } + return ret || (value.exports && getGlobal(value.exports)); + } + return fn; + }, + + makeRequire: function (relMap, options) { + options = options || {}; + + function localRequire(deps, callback, errback) { + var id, map, requireMod; + + if (options.enableBuildCallback && callback && isFunction(callback)) { + callback.__requireJsBuild = true; + } + + if (typeof deps === 'string') { + if (isFunction(callback)) { + //Invalid call + return onError(makeError('requireargs', 'Invalid require call'), errback); + } + + //If require|exports|module are requested, get the + //value for them from the special handlers. Caveat: + //this only works while module is being defined. + if (relMap && hasProp(handlers, deps)) { + return handlers[deps](registry[relMap.id]); + } + + //Synchronous access to one module. If require.get is + //available (as in the Node adapter), prefer that. + if (req.get) { + return req.get(context, deps, relMap); + } + + //Normalize module name, if it contains . or .. + map = makeModuleMap(deps, relMap, false, true); + id = map.id; + + if (!hasProp(defined, id)) { + return onError(makeError('notloaded', 'Module name "' + + id + + '" has not been loaded yet for context: ' + + contextName + + (relMap ? '' : '. Use require([])'))); + } + return defined[id]; + } + + //Grab defines waiting in the global queue. + intakeDefines(); + + //Mark all the dependencies as needing to be loaded. + context.nextTick(function () { + //Some defines could have been added since the + //require call, collect them. + intakeDefines(); + + requireMod = getModule(makeModuleMap(null, relMap)); + + //Store if map config should be applied to this require + //call for dependencies. + requireMod.skipMap = options.skipMap; + + requireMod.init(deps, callback, errback, { + enabled: true + }); + + checkLoaded(); + }); + + return localRequire; + } + + mixin(localRequire, { + isBrowser: isBrowser, + + /** + * Converts a module name + .extension into an URL path. + * *Requires* the use of a module name. It does not support using + * plain URLs like nameToUrl. + */ + toUrl: function (moduleNamePlusExt) { + var ext, url, + index = moduleNamePlusExt.lastIndexOf('.'), + segment = moduleNamePlusExt.split('/')[0], + isRelative = segment === '.' || segment === '..'; + + //Have a file extension alias, and it is not the + //dots from a relative path. + if (index !== -1 && (!isRelative || index > 1)) { + ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length); + moduleNamePlusExt = moduleNamePlusExt.substring(0, index); + } + + url = context.nameToUrl(normalize(moduleNamePlusExt, + relMap && relMap.id, true), ext || '.fake'); + return ext ? url : url.substring(0, url.length - 5); + }, + + defined: function (id) { + return hasProp(defined, makeModuleMap(id, relMap, false, true).id); + }, + + specified: function (id) { + id = makeModuleMap(id, relMap, false, true).id; + return hasProp(defined, id) || hasProp(registry, id); + } + }); + + //Only allow undef on top level require calls + if (!relMap) { + localRequire.undef = function (id) { + //Bind any waiting define() calls to this context, + //fix for #408 + takeGlobalQueue(); + + var map = makeModuleMap(id, relMap, true), + mod = getOwn(registry, id); + + delete defined[id]; + delete urlFetched[map.url]; + delete undefEvents[id]; + + if (mod) { + //Hold on to listeners in case the + //module will be attempted to be reloaded + //using a different config. + if (mod.events.defined) { + undefEvents[id] = mod.events; + } + + cleanRegistry(id); + } + }; + } + + return localRequire; + }, + + /** + * Called to enable a module if it is still in the registry + * awaiting enablement. A second arg, parent, the parent module, + * is passed in for context, when this method is overriden by + * the optimizer. Not shown here to keep code compact. + */ + enable: function (depMap) { + var mod = getOwn(registry, depMap.id); + if (mod) { + getModule(depMap).enable(); + } + }, + + /** + * Internal method used by environment adapters to complete a load event. + * A load event could be a script load or just a load pass from a synchronous + * load call. + * @param {String} moduleName the name of the module to potentially complete. + */ + completeLoad: function (moduleName) { + var found, args, mod, + shim = getOwn(config.shim, moduleName) || {}, + shExports = shim.exports; + + takeGlobalQueue(); + + while (defQueue.length) { + args = defQueue.shift(); + if (args[0] === null) { + args[0] = moduleName; + //If already found an anonymous module and bound it + //to this name, then this is some other anon module + //waiting for its completeLoad to fire. + if (found) { + break; + } + found = true; + } else if (args[0] === moduleName) { + //Found matching define call for this script! + found = true; + } + + callGetModule(args); + } + + //Do this after the cycle of callGetModule in case the result + //of those calls/init calls changes the registry. + mod = getOwn(registry, moduleName); + + if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) { + if (config.enforceDefine && (!shExports || !getGlobal(shExports))) { + if (hasPathFallback(moduleName)) { + return; + } else { + return onError(makeError('nodefine', + 'No define call for ' + moduleName, + null, + [moduleName])); + } + } else { + //A script that does not call define(), so just simulate + //the call for it. + callGetModule([moduleName, (shim.deps || []), shim.exportsFn]); + } + } + + checkLoaded(); + }, + + /** + * Converts a module name to a file path. Supports cases where + * moduleName may actually be just an URL. + * Note that it **does not** call normalize on the moduleName, + * it is assumed to have already been normalized. This is an + * internal API, not a public one. Use toUrl for the public API. + */ + nameToUrl: function (moduleName, ext) { + var paths, pkgs, pkg, pkgPath, syms, i, parentModule, url, + parentPath; + + //If a colon is in the URL, it indicates a protocol is used and it is just + //an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?) + //or ends with .js, then assume the user meant to use an url and not a module id. + //The slash is important for protocol-less URLs as well as full paths. + if (req.jsExtRegExp.test(moduleName)) { + //Just a plain path, not module name lookup, so just return it. + //Add extension if it is included. This is a bit wonky, only non-.js things pass + //an extension, this method probably needs to be reworked. + url = moduleName + (ext || ''); + } else { + //A module that needs to be converted to a path. + paths = config.paths; + pkgs = config.pkgs; + + syms = moduleName.split('/'); + //For each module name segment, see if there is a path + //registered for it. Start with most specific name + //and work up from it. + for (i = syms.length; i > 0; i -= 1) { + parentModule = syms.slice(0, i).join('/'); + pkg = getOwn(pkgs, parentModule); + parentPath = getOwn(paths, parentModule); + if (parentPath) { + //If an array, it means there are a few choices, + //Choose the one that is desired + if (isArray(parentPath)) { + parentPath = parentPath[0]; + } + syms.splice(0, i, parentPath); + break; + } else if (pkg) { + //If module name is just the package name, then looking + //for the main module. + if (moduleName === pkg.name) { + pkgPath = pkg.location + '/' + pkg.main; + } else { + pkgPath = pkg.location; + } + syms.splice(0, i, pkgPath); + break; + } + } + + //Join the path parts together, then figure out if baseUrl is needed. + url = syms.join('/'); + url += (ext || (/\?/.test(url) ? '' : '.js')); + url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url; + } + + return config.urlArgs ? url + + ((url.indexOf('?') === -1 ? '?' : '&') + + config.urlArgs) : url; + }, + + //Delegates to req.load. Broken out as a separate function to + //allow overriding in the optimizer. + load: function (id, url) { + req.load(context, id, url); + }, + + /** + * Executes a module callack function. Broken out as a separate function + * solely to allow the build system to sequence the files in the built + * layer in the right sequence. + * + * @private + */ + execCb: function (name, callback, args, exports) { + return callback.apply(exports, args); + }, + + /** + * callback for script loads, used to check status of loading. + * + * @param {Event} evt the event from the browser for the script + * that was loaded. + */ + onScriptLoad: function (evt) { + //Using currentTarget instead of target for Firefox 2.0's sake. Not + //all old browsers will be supported, but this one was easy enough + //to support and still makes sense. + if (evt.type === 'load' || + (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) { + //Reset interactive script so a script node is not held onto for + //to long. + interactiveScript = null; + + //Pull out the name of the module and the context. + var data = getScriptData(evt); + context.completeLoad(data.id); + } + }, + + /** + * Callback for script errors. + */ + onScriptError: function (evt) { + var data = getScriptData(evt); + if (!hasPathFallback(data.id)) { + return onError(makeError('scripterror', 'Script error', evt, [data.id])); + } + } + }; + + context.require = context.makeRequire(); + return context; + } + + /** + * Main entry point. + * + * If the only argument to require is a string, then the module that + * is represented by that string is fetched for the appropriate context. + * + * If the first argument is an array, then it will be treated as an array + * of dependency string names to fetch. An optional function callback can + * be specified to execute when all of those dependencies are available. + * + * Make a local req variable to help Caja compliance (it assumes things + * on a require that are not standardized), and to give a short + * name for minification/local scope use. + */ + req = requirejs = function (deps, callback, errback, optional) { + + //Find the right context, use default + var context, config, + contextName = defContextName; + + // Determine if have config object in the call. + if (!isArray(deps) && typeof deps !== 'string') { + // deps is a config object + config = deps; + if (isArray(callback)) { + // Adjust args if there are dependencies + deps = callback; + callback = errback; + errback = optional; + } else { + deps = []; + } + } + + if (config && config.context) { + contextName = config.context; + } + + context = getOwn(contexts, contextName); + if (!context) { + context = contexts[contextName] = req.s.newContext(contextName); + } + + if (config) { + context.configure(config); + } + + return context.require(deps, callback, errback); + }; + + /** + * Support require.config() to make it easier to cooperate with other + * AMD loaders on globally agreed names. + */ + req.config = function (config) { + return req(config); + }; + + /** + * Execute something after the current tick + * of the event loop. Override for other envs + * that have a better solution than setTimeout. + * @param {Function} fn function to execute later. + */ + req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) { + setTimeout(fn, 4); + } : function (fn) { fn(); }; + + /** + * Export require as a global, but only if it does not already exist. + */ + if (!require) { + require = req; + } + + req.version = version; + + //Used to filter out dependencies that are already paths. + req.jsExtRegExp = /^\/|:|\?|\.js$/; + req.isBrowser = isBrowser; + s = req.s = { + contexts: contexts, + newContext: newContext + }; + + //Create default context. + req({}); + + //Exports some context-sensitive methods on global require. + each([ + 'toUrl', + 'undef', + 'defined', + 'specified' + ], function (prop) { + //Reference from contexts instead of early binding to default context, + //so that during builds, the latest instance of the default context + //with its config gets used. + req[prop] = function () { + var ctx = contexts[defContextName]; + return ctx.require[prop].apply(ctx, arguments); + }; + }); + + if (isBrowser) { + head = s.head = document.getElementsByTagName('head')[0]; + //If BASE tag is in play, using appendChild is a problem for IE6. + //When that browser dies, this can be removed. Details in this jQuery bug: + //http://dev.jquery.com/ticket/2709 + baseElement = document.getElementsByTagName('base')[0]; + if (baseElement) { + head = s.head = baseElement.parentNode; + } + } + + /** + * Any errors that require explicitly generates will be passed to this + * function. Intercept/override it if you want custom error handling. + * @param {Error} err the error object. + */ + req.onError = function (err) { + throw err; + }; + + /** + * Does the request to load a module for the browser case. + * Make this a separate function to allow other environments + * to override it. + * + * @param {Object} context the require context to find state. + * @param {String} moduleName the name of the module. + * @param {Object} url the URL to the module. + */ + req.load = function (context, moduleName, url) { + var config = (context && context.config) || {}, + node; + if (isBrowser) { + //In the browser so use a script tag + node = config.xhtml ? + document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') : + document.createElement('script'); + node.type = config.scriptType || 'text/javascript'; + node.charset = 'utf-8'; + node.async = true; + + node.setAttribute('data-requirecontext', context.contextName); + node.setAttribute('data-requiremodule', moduleName); + + //Set up load listener. Test attachEvent first because IE9 has + //a subtle issue in its addEventListener and script onload firings + //that do not match the behavior of all other browsers with + //addEventListener support, which fire the onload event for a + //script right after the script execution. See: + //https://connect.microsoft.com/IE/feedback/details/648057/script-onload-event-is-not-fired-immediately-after-script-execution + //UNFORTUNATELY Opera implements attachEvent but does not follow the script + //script execution mode. + if (node.attachEvent && + //Check if node.attachEvent is artificially added by custom script or + //natively supported by browser + //read https://github.com/jrburke/requirejs/issues/187 + //if we can NOT find [native code] then it must NOT natively supported. + //in IE8, node.attachEvent does not have toString() + //Note the test for "[native code" with no closing brace, see: + //https://github.com/jrburke/requirejs/issues/273 + !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) && + !isOpera) { + //Probably IE. IE (at least 6-8) do not fire + //script onload right after executing the script, so + //we cannot tie the anonymous define call to a name. + //However, IE reports the script as being in 'interactive' + //readyState at the time of the define call. + useInteractive = true; + + node.attachEvent('onreadystatechange', context.onScriptLoad); + //It would be great to add an error handler here to catch + //404s in IE9+. However, onreadystatechange will fire before + //the error handler, so that does not help. If addEvenListener + //is used, then IE will fire error before load, but we cannot + //use that pathway given the connect.microsoft.com issue + //mentioned above about not doing the 'script execute, + //then fire the script load event listener before execute + //next script' that other browsers do. + //Best hope: IE10 fixes the issues, + //and then destroys all installs of IE 6-9. + //node.attachEvent('onerror', context.onScriptError); + } else { + node.addEventListener('load', context.onScriptLoad, false); + node.addEventListener('error', context.onScriptError, false); + } + node.src = url; + + //For some cache cases in IE 6-8, the script executes before the end + //of the appendChild execution, so to tie an anonymous define + //call to the module name (which is stored on the node), hold on + //to a reference to this node, but clear after the DOM insertion. + currentlyAddingScript = node; + if (baseElement) { + head.insertBefore(node, baseElement); + } else { + head.appendChild(node); + } + currentlyAddingScript = null; + + return node; + } else if (isWebWorker) { + //In a web worker, use importScripts. This is not a very + //efficient use of importScripts, importScripts will block until + //its script is downloaded and evaluated. However, if web workers + //are in play, the expectation that a build has been done so that + //only one script needs to be loaded anyway. This may need to be + //reevaluated if other use cases become common. + importScripts(url); + + //Account for anonymous modules + context.completeLoad(moduleName); + } + }; + + function getInteractiveScript() { + if (interactiveScript && interactiveScript.readyState === 'interactive') { + return interactiveScript; + } + + eachReverse(scripts(), function (script) { + if (script.readyState === 'interactive') { + return (interactiveScript = script); + } + }); + return interactiveScript; + } + + //Look for a data-main script attribute, which could also adjust the baseUrl. + if (isBrowser) { + //Figure out baseUrl. Get it from the script tag with require.js in it. + eachReverse(scripts(), function (script) { + //Set the 'head' where we can append children by + //using the script's parent. + if (!head) { + head = script.parentNode; + } + + //Look for a data-main attribute to set main script for the page + //to load. If it is there, the path to data main becomes the + //baseUrl, if it is not already set. + dataMain = script.getAttribute('data-main'); + if (dataMain) { + //Set final baseUrl if there is not already an explicit one. + if (!cfg.baseUrl) { + //Pull off the directory of data-main for use as the + //baseUrl. + src = dataMain.split('/'); + mainScript = src.pop(); + subPath = src.length ? src.join('/') + '/' : './'; + + cfg.baseUrl = subPath; + dataMain = mainScript; + } + + //Strip off any trailing .js since dataMain is now + //like a module name. + dataMain = dataMain.replace(jsSuffixRegExp, ''); + + //Put the data-main script in the files to load. + cfg.deps = cfg.deps ? cfg.deps.concat(dataMain) : [dataMain]; + + return true; + } + }); + } + + /** + * The function that handles definitions of modules. Differs from + * require() in that a string for the module should be the first argument, + * and the function to execute after dependencies are loaded should + * return a value to define the module corresponding to the first argument's + * name. + */ + define = function (name, deps, callback) { + var node, context; + + //Allow for anonymous modules + if (typeof name !== 'string') { + //Adjust args appropriately + callback = deps; + deps = name; + name = null; + } + + //This module may not have dependencies + if (!isArray(deps)) { + callback = deps; + deps = []; + } + + //If no name, and callback is a function, then figure out if it a + //CommonJS thing with dependencies. + if (!deps.length && isFunction(callback)) { + //Remove comments from the callback string, + //look for require calls, and pull them into the dependencies, + //but only if there are function args. + if (callback.length) { + callback + .toString() + .replace(commentRegExp, '') + .replace(cjsRequireRegExp, function (match, dep) { + deps.push(dep); + }); + + //May be a CommonJS thing even without require calls, but still + //could use exports, and module. Avoid doing exports and module + //work though if it just needs require. + //REQUIRES the function to expect the CommonJS variables in the + //order listed below. + deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps); + } + } + + //If in IE 6-8 and hit an anonymous define() call, do the interactive + //work. + if (useInteractive) { + node = currentlyAddingScript || getInteractiveScript(); + if (node) { + if (!name) { + name = node.getAttribute('data-requiremodule'); + } + context = contexts[node.getAttribute('data-requirecontext')]; + } + } + + //Always save off evaluating the def call until the script onload handler. + //This allows multiple modules to be in a file without prematurely + //tracing dependencies, and allows for anonymous module support, + //where the module name is not known until the script onload event + //occurs. If no context, use the global queue, and get it processed + //in the onscript load callback. + (context ? context.defQueue : globalDefQueue).push([name, deps, callback]); + }; + + define.amd = { + jQuery: true + }; + + + /** + * Executes the text. Normally just uses eval, but can be modified + * to use a better, environment-specific call. Only used for transpiling + * loader plugins, not for plain JS modules. + * @param {String} text the text to execute/evaluate. + */ + req.exec = function (text) { + /*jslint evil: true */ + return eval(text); + }; + + //Set up with config info. + req(cfg); +}(this)); diff --git a/lib/sugar-web/LICENSE b/lib/sugar-web/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/lib/sugar-web/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/lib/sugar-web/README.md b/lib/sugar-web/README.md new file mode 100644 index 0000000..de87a72 --- /dev/null +++ b/lib/sugar-web/README.md @@ -0,0 +1,7 @@ +Sugar Web +========= + +These are the tools that a developer can use to make a web +activity. + +For details see: http://developer.sugarlabs.org/ diff --git a/lib/sugar-web/activity/activity.js b/lib/sugar-web/activity/activity.js new file mode 100644 index 0000000..9a407d9 --- /dev/null +++ b/lib/sugar-web/activity/activity.js @@ -0,0 +1,114 @@ +define(["webL10n", + "sugar-web/activity/shortcut", + "sugar-web/bus", + "sugar-web/env", + "sugar-web/datastore", + "sugar-web/graphics/icon", + "sugar-web/graphics/activitypalette"], function ( + l10n, shortcut, bus, env, datastore, icon, activitypalette) { + + var datastoreObject = null; + + var activity = {}; + + activity.setup = function () { + bus.listen(); + + l10n.start(); + + function onPause() { + activity.write(function () {}); + } + + function onStop() { + function onDataStored(error, result) { + activity.close(function () {}); + } + activity.write(onDataStored); + } + + bus.onNotification("activity.pause", onPause); + bus.onNotification("activity.stop", onStop); + + datastoreObject = new datastore.DatastoreObject(); + + var activityButton = document.getElementById("activity-button"); + + var activityPalette = new activitypalette.ActivityPalette( + activityButton, datastoreObject); + + // Colorize the activity icon. + activity.getXOColor(function (error, colors) { + icon.colorize(activityButton, colors); + invokerElem = + document.querySelector("#activity-palette .palette-invoker"); + icon.colorize(invokerElem, colors); + }); + + // Make the activity stop with the stop button. + var stopButton = document.getElementById("stop-button"); + stopButton.addEventListener('click', onStop); + + shortcut.add("Ctrl", "Q", this.close); + + env.getEnvironment(function (error, environment) { + if (!environment.objectId) { + datastoreObject.setMetadata({ + "title": environment.activityName + " Activity", + "title_set_by_user": "0", + "activity": environment.bundleId, + "activity_id": environment.activityId + }); + } + datastoreObject.save(function () { + datastoreObject.getMetadata(function (error, metadata) { + activityPalette.setTitleDescription(metadata); + }); + }); + }); + }; + + activity.getDatastoreObject = function () { + return datastoreObject; + }; + + activity.getXOColor = function (callback) { + function onResponseReceived(error, result) { + if (error === null) { + callback(null, { + stroke: result[0][0], + fill: result[0][1] + }); + } else { + callback(null, { + stroke: "#00A0FF", + fill: "#8BFF7A" + }); + } + } + + bus.sendMessage("activity.get_xo_color", [], onResponseReceived); + }; + + // Activities should override this function in order to store + // data. + activity.write = function (callback) { + setTimeout(function () { + callback(null); + }, 0); + }; + + activity.close = function (callback) { + function onResponseReceived(error, result) { + if (error === null) { + callback(null); + } else { + console.log("activity.close called"); + } + } + + bus.sendMessage("activity.close", [], onResponseReceived); + }; + + return activity; +}); diff --git a/lib/sugar-web/activity/shortcut.js b/lib/sugar-web/activity/shortcut.js new file mode 100644 index 0000000..403af03 --- /dev/null +++ b/lib/sugar-web/activity/shortcut.js @@ -0,0 +1,57 @@ +define(function () { + var shortcut = {}; + + shortcut._allShortcuts = []; + + shortcut.add = function (modifiersString, key, callback) { + // Parse the modifiers. For example "Ctrl+Alt" will become + // {'ctrlKey': true, 'altKey': true, 'shiftKey': false} + var modifiersList = modifiersString.toLowerCase().split("+"); + var modifiers = { + 'ctrlKey': modifiersList.indexOf('ctrl') >= 0, + 'altKey': modifiersList.indexOf('alt') >= 0, + 'shiftKey': modifiersList.indexOf('shift') >= 0 + }; + + this._allShortcuts.push({ + 'modifiers': modifiers, + 'key': key.toLowerCase(), + 'callback': callback + }); + }; + + document.onkeypress = function (e) { + e = e || window.event; + + var modifiers = { + 'ctrlKey': e.ctrlKey, + 'altKey': e.altKey, + 'shiftKey': e.shiftKey + }; + + // Obtain the key + var charCode; + if (typeof e.which == "number") { + charCode = e.which; + } else { + charCode = e.keyCode; + } + var key = String.fromCharCode(charCode).toLowerCase(); + + // Search for a matching shortcut + for (i = 0; i < shortcut._allShortcuts.length; i += 1) { + var currentShortcut = shortcut._allShortcuts[i]; + + var match = currentShortcut.key == key && + currentShortcut.modifiers.ctrlKey == modifiers.ctrlKey && + currentShortcut.modifiers.altKey == modifiers.altKey && + currentShortcut.modifiers.shiftKey == modifiers.shiftKey; + if (match) { + currentShortcut.callback(); + return; + } + } + }; + + return shortcut; +}); diff --git a/lib/sugar-web/bus.js b/lib/sugar-web/bus.js new file mode 100644 index 0000000..79fe9dd --- /dev/null +++ b/lib/sugar-web/bus.js @@ -0,0 +1,219 @@ +define(["sugar-web/env"], function (env) { + var lastId = 0; + var callbacks = {}; + var notificationCallbacks = {}; + var client = null; + var inputStreams = []; + + function WebSocketClient(environment) { + this.queue = []; + this.socket = null; + + var that = this; + + env.getEnvironment(function (error, environment) { + var port = environment.apiSocketPort; + var socket = new WebSocket("ws://localhost:" + port); + + socket.binaryType = "arraybuffer"; + + socket.onopen = function () { + var params = [environment.activityId, + environment.apiSocketKey]; + + socket.send(JSON.stringify({ + "method": "authenticate", + "id": "authenticate", + "params": params + })); + + while (that.queue.length > 0) { + socket.send(that.queue.shift()); + } + }; + + socket.onmessage = function (message) { + that.onMessage(message); + }; + + that.socket = socket; + }); + } + + WebSocketClient.prototype.send = function (data) { + if (this.socket && this.socket.readyState == WebSocket.OPEN) { + this.socket.send(data); + } else { + this.queue.push(data); + } + }; + + WebSocketClient.prototype.close = function () { + this.socket.close(); + }; + + var bus = {}; + + function InputStream() { + this.streamId = null; + this.readCallback = null; + } + + InputStream.prototype.open = function (callback) { + var that = this; + bus.sendMessage("open_stream", [], function (error, result) { + that.streamId = result[0]; + inputStreams[that.streamId] = that; + callback(error); + }); + }; + + InputStream.prototype.read = function (count, callback) { + if (this.readCallback) { + throw Error("Read already in progress"); + } + + this.readCallback = callback; + + var buffer = new ArrayBuffer(8); + + var headerView = new Uint8Array(buffer, 0, 1); + headerView[0] = this.streamId; + + var bodyView = new Uint32Array(buffer, 4, 1); + bodyView[0] = count; + + bus.sendBinary(buffer); + }; + + InputStream.prototype.gotData = function (buffer) { + var callback = this.readCallback; + + this.readCallback = null; + + callback(null, buffer); + }; + + InputStream.prototype.close = function (callback) { + var that = this; + + function onStreamClosed(error, result) { + if (callback) { + callback(error); + } + delete inputStreams[that.streamId]; + } + + bus.sendMessage("close_stream", [this.streamId], onStreamClosed); + }; + + function OutputStream() { + this.streamId = null; + } + + OutputStream.prototype.open = function (callback) { + var that = this; + bus.sendMessage("open_stream", [], function (error, result) { + that.streamId = result[0]; + callback(error); + }); + }; + + OutputStream.prototype.write = function (data) { + var buffer = new ArrayBuffer(data.byteLength + 1); + + var bufferView = new Uint8Array(buffer); + bufferView[0] = this.streamId; + bufferView.set(new Uint8Array(data), 1); + + bus.sendBinary(buffer); + }; + + OutputStream.prototype.close = function (callback) { + bus.sendMessage("close_stream", [this.streamId], callback); + }; + + bus.createInputStream = function (callback) { + return new InputStream(); + }; + + bus.createOutputStream = function (callback) { + return new OutputStream(); + }; + + bus.sendMessage = function (method, params, callback) { + var message = { + "method": method, + "id": lastId, + "params": params + }; + + if (callback) { + callbacks[lastId] = callback; + } + + client.send(JSON.stringify(message)); + + lastId++; + }; + + bus.onNotification = function (method, callback) { + notificationCallbacks[method] = callback; + }; + + bus.sendBinary = function (buffer, callback) { + client.send(buffer); + }; + + bus.listen = function (customClient) { + if (customClient) { + client = customClient; + } else { + client = new WebSocketClient(); + } + + client.onMessage = function (message) { + if (typeof message.data != "string") { + var dataView = new Uint8Array(message.data); + var streamId = dataView[0]; + + if (streamId in inputStreams) { + var inputStream = inputStreams[streamId]; + inputStream.gotData(message.data.slice(1)); + } + + return; + } + + var parsed = JSON.parse(message.data); + var responseId = parsed.id; + + if (parsed.method) { + var notificationCallback = notificationCallbacks[parsed.method]; + if (notificationCallback !== undefined) { + notificationCallback(parsed.params); + } + return; + } + + if (responseId in callbacks) { + var callback = callbacks[responseId]; + + if (parsed.error === null) { + callback(null, parsed.result); + } else { + callback(new Error(parsed.error), null); + } + + delete callbacks[responseId]; + } + }; + }; + + bus.close = function () { + client.close(); + client = null; + }; + + return bus; +}); diff --git a/lib/sugar-web/datastore.js b/lib/sugar-web/datastore.js new file mode 100644 index 0000000..9291779 --- /dev/null +++ b/lib/sugar-web/datastore.js @@ -0,0 +1,221 @@ +define(["sugar-web/bus", "sugar-web/env"], function (bus, env) { + var datastore = {}; + + function DatastoreObject(objectId) { + this.objectId = objectId; + this.newMetadata = {}; + + this.ensureObjectId = function (callback) { + var that = this; + + env.getEnvironment(function (error, environment) { + if (environment.objectId !== null) { + that.objectId = environment.objectId; + } + callback(); + }); + }; + + this.blobToText = function (blob, callback) { + var reader = new FileReader(); + reader.onload = function (e) { + callback(e.target.result); + }; + reader.readAsText(blob); + }; + + this.blobToArrayBuffer = function (blob, callback) { + var reader = new FileReader(); + reader.onload = function (e) { + callback(e.target.result); + }; + reader.readAsArrayBuffer(blob); + }; + + this.saveText = function (metadata, callback) { + var that = this; + + function onSaved(error, outputStream) { + var blob = new Blob([that.newDataAsText]); + + that.blobToArrayBuffer(blob, function (buffer) { + outputStream.write(buffer); + outputStream.close(callback); + }); + } + + datastore.save(this.objectId, metadata, onSaved); + }; + + this.applyChanges = function (metadata, callback) { + for (var key in this.newMetadata) { + metadata[key] = this.newMetadata[key]; + } + + if (this.newDataAsText !== undefined) { + this.saveText(metadata, callback); + } else { + datastore.setMetadata(this.objectId, metadata, callback); + } + }; + } + + DatastoreObject.prototype.getMetadata = function (callback) { + var that = this; + + this.ensureObjectId(function () { + datastore.getMetadata(that.objectId, callback); + }); + }; + + DatastoreObject.prototype.loadAsText = function (callback) { + var that = this; + var inputStream = null; + var arrayBuffers = []; + var metadata = null; + + function onRead(error, data) { + if (data.byteLength === 0) { + var blob = new Blob(arrayBuffers); + + that.blobToText(blob, function (text) { + callback(null, metadata, text); + }); + + inputStream.close(); + + return; + } + + arrayBuffers.push(data); + + inputStream.read(8192, onRead); + } + + function onLoad(error, loadedMetadata, loadedInputStream) { + metadata = loadedMetadata; + inputStream = loadedInputStream; + + inputStream.read(8192, onRead); + } + + this.ensureObjectId(function () { + datastore.load(that.objectId, onLoad); + }); + }; + + DatastoreObject.prototype.setMetadata = function (metadata) { + for (var key in metadata) { + this.newMetadata[key] = metadata[key]; + } + }; + + DatastoreObject.prototype.setDataAsText = function (text) { + this.newDataAsText = text; + }; + + DatastoreObject.prototype.save = function (callback) { + if (callback === undefined) { + callback = function () {}; + } + + var that = this; + + function onCreated(error, objectId) { + that.objectId = objectId; + that.applyChanges({}, callback); + } + + function onGotMetadata(error, metadata) { + that.applyChanges(metadata, callback); + } + + this.ensureObjectId(function () { + if (that.objectId === undefined) { + datastore.create(that.newMetadata, onCreated); + } else { + datastore.getMetadata(that.objectId, onGotMetadata); + } + }); + }; + + datastore.DatastoreObject = DatastoreObject; + + + datastore.setMetadata = function (objectId, metadata, callback) { + function onResponseReceived(error, result) { + if (callback) { + if (error === null) { + callback(null); + } else { + callback(error); + } + } + } + + var params = [objectId, metadata]; + bus.sendMessage("datastore.set_metadata", params, onResponseReceived); + }; + + datastore.getMetadata = function (objectId, callback) { + function onResponseReceived(error, result) { + if (error === null) { + callback(null, result[0]); + } else { + callback(error, null); + } + } + + var params = [objectId]; + bus.sendMessage("datastore.get_metadata", params, onResponseReceived); + }; + + datastore.load = function (objectId, callback) { + inputStream = bus.createInputStream(); + + inputStream.open(function (error) { + function onResponseReceived(responseError, result) { + if (responseError === null) { + callback(null, result[0], inputStream); + } else { + callback(responseError, null, null); + } + } + + var params = [objectId, inputStream.streamId]; + bus.sendMessage("datastore.load", params, onResponseReceived); + }); + }; + + datastore.create = function (metadata, callback) { + function onResponseReceived(responseError, result) { + if (responseError === null) { + callback(null, result[0]); + } else { + callback(responseError, null); + } + } + + var params = [metadata]; + bus.sendMessage("datastore.create", params, onResponseReceived); + }; + + datastore.save = function (objectId, metadata, callback) { + outputStream = bus.createOutputStream(); + + outputStream.open(function (error) { + function onResponseReceived(responseError, result) { + if (responseError === null) { + callback(null, outputStream); + } else { + callback(responseError, null); + } + } + + var params = [objectId, metadata, outputStream.streamId]; + bus.sendMessage("datastore.save", params, onResponseReceived); + }); + }; + + return datastore; +}); diff --git a/lib/sugar-web/env.js b/lib/sugar-web/env.js new file mode 100644 index 0000000..1a948eb --- /dev/null +++ b/lib/sugar-web/env.js @@ -0,0 +1,27 @@ +define(function () { + + var env = {}; + + env.getEnvironment = function (callback) { + var sugar; + + if (window.top.sugar) { + sugar = window.top.sugar; + } else { + sugar = {}; + window.top.sugar = sugar; + } + + if (sugar.environment) { + setTimeout(function () { + callback(null, sugar.environment); + }, 0); + } else { + sugar.onEnvironmentSet = function () { + callback(null, sugar.environment); + }; + } + }; + + return env; +}); diff --git a/lib/sugar-web/graphics/README.md b/lib/sugar-web/graphics/README.md new file mode 100644 index 0000000..e3b7091 --- /dev/null +++ b/lib/sugar-web/graphics/README.md @@ -0,0 +1,39 @@ +Sugar Graphics +============== + +Sugar widgets and graphics, implementing the [Sugar Interface +Guidelines](http://wiki.sugarlabs.org/go/Human_Interface_Guidelines). + +Modifying the CSS +----------------- + +We use [LESS](http://lesscss.org) and then compile the CSS. This is +to be able to use calculations and variables for colors and measures. +And to be able to output different CSS files for different screen +resolutions, which is planned. + +To compile the CSS do: + + lessc graphics/css/sugar.less graphics/css/sugar.css + +Be sure to compile it before commit. + +The grid helper +--------------- + +The grid is a visual debug tool that allows you to check if the +elements have the correct size. To activate the grid, open the +inspector console and paste the following. + +If RequireJS is not in the page head (ie. in the sugar-web-samples), +load it: + + var script = document.createElement('script'); + script.src = 'lib/require.js'; + document.head.appendChild(script); + + requirejs.config({ baseUrl: "lib" }); + +Then do: + + require(["sugar-web/graphics/grid"], function (grid) { grid.addGrid(11) }); diff --git a/lib/sugar-web/graphics/activitypalette.js b/lib/sugar-web/graphics/activitypalette.js new file mode 100644 index 0000000..aca036e --- /dev/null +++ b/lib/sugar-web/graphics/activitypalette.js @@ -0,0 +1,70 @@ +define(["sugar-web/graphics/palette"], function (palette) { + + var activitypalette = {}; + + activitypalette.ActivityPalette = function (activityButton, + datastoreObject) { + + palette.Palette.call(this, activityButton); + + var activityTitle; + var descriptionLabel; + var descriptionBox; + + this.getPalette().id = "activity-palette"; + + this.template = + '<div class="row">' + + '<input type="text" id="title" class="expand">' + + '</div>' + + '<div class="row small">' + + '<label>Description:</label>' + + '</div>' + + '<div class="row expand">' + + '<textarea rows="8" id="description" class="expand"></textarea>' + + '</div>'; + + var containerElem = document.createElement('div'); + containerElem.innerHTML = this.template; + this.setContent([containerElem]); + + this.titleElem = containerElem.querySelector('#title'); + this.descriptionElem = containerElem.querySelector('#description'); + + this.titleElem.onblur = function () { + datastoreObject.setMetadata({ + "title": this.value, + "title_set_by_user": "1" + }); + datastoreObject.save(); + }; + + this.descriptionElem.onblur = function () { + datastoreObject.setMetadata({ + "description": this.value + }); + datastoreObject.save(); + }; + }; + + // Fill the text inputs with the received metadata. + var setTitleDescription = function (metadata) { + this.titleElem.value = metadata.title; + + if (metadata.description !== undefined) { + this.descriptionElem.value = metadata.description; + } + }; + + activitypalette.ActivityPalette.prototype = + Object.create(palette.Palette.prototype, { + setTitleDescription: { + value: setTitleDescription, + enumerable: true, + configurable: true, + writable: true + } + }); + + return activitypalette; +}); diff --git a/lib/sugar-web/graphics/css/sugar.css b/lib/sugar-web/graphics/css/sugar.css new file mode 100644 index 0000000..cc1419d --- /dev/null +++ b/lib/sugar-web/graphics/css/sugar.css @@ -0,0 +1,456 @@ +* { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +html { + height: 100%; +} +body { + margin: 0; + height: 100%; + background-color: #c0c0c0; + position: relative; + font-family: sans-serif; + font-size: 10pt; +} +.unselectable { + -moz-user-select: none; + -webkit-user-select: none; +} +.pull-right { + float: right; +} +a { + color: #0076c3; + text-decoration: none; +} +/* Toolbar */ +.toolbar { + color: white; + background-color: #282828; + padding: 0 57px; + height: 55px; + -moz-user-select: none; + -webkit-user-select: none; +} +/* Toolbar separator */ +.toolbar hr { + display: inline-block; + height: 33px; + border: 1px solid #808080; + margin: 2.5px; + margin-bottom: -17px; +} +/* Toolbar toolbutton */ +.toolbar .toolbutton { + background-color: transparent; + background-position: center; + background-size: contain; + background-repeat: no-repeat; + color: white; + color: transparent; + border: 0; + border-radius: 5.5px; + margin: 4px 2px; + width: 47px; + height: 47px; + position: relative; +} +.toolbar .toolbutton:hover { + background-color: black; +} +.toolbar .toolbutton:active, +.toolbar .toolbutton.active { + background-color: #808080; +} +.toolbar .toolbutton img { + width: 100%; + height: 100%; +} +.toolbar .toolbutton:before { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: 55px; + height: 11px; + bottom: -4px; + right: -4px; +} +.toolbar .toolbutton.invoker:before { + background-image: url('../icons/emblems/arrow-down.svg'); +} +.toolbar #stop-button { + background-image: url('../icons/actions/activity-stop.svg'); +} +/* Canvas */ +#canvas { + color: black; + background-color: #c0c0c0; + position: absolute; + bottom: 0; + top: 55px; + overflow-y: auto; + width: 100%; +} +/* Button */ +button { + background-color: #808080; + color: white; + border: 2px solid transparent; + border-radius: 22px; + line-height: 22px; + padding: 2px 4px; + -moz-user-select: none; + -webkit-user-select: none; +} +button:hover { + background-color: #a2a2a2; + border-color: #a2a2a2; +} +button:active { + background-color: white; + color: black; + border-color: #808080; +} +button:focus { + border-color: white; +} +.toolbar button { + margin-top: 12.5px; +} +/* Button with icon */ +button.icon { + position: relative; + padding-left: 26px; +} +button.icon span.ok { + background-image: url(../icons/actions/dialog-ok.svg); +} +button.icon:active span.ok { + background-image: url(../icons/actions/dialog-ok-active.svg); +} +button.icon span.cancel { + background-image: url(../icons/actions/dialog-cancel.svg); +} +button.icon:active span.cancel { + background-image: url(../icons/actions/dialog-cancel-active.svg); +} +button.icon span { + display: inline-block; + width: 22px; + height: 22px; + background-color: transparent; + background-position: center; + background-size: 22px 22px; + background-repeat: no-repeat; + position: absolute; +} +button.icon span { + top: 2px; + left: 2px; +} +/* One line text input */ +input[type='text'] { + background-color: #e5e5e5; + border: 2px solid #e5e5e5; + border-radius: 22px; + padding: 4px; + width: 165px; + line-height: 22px; + outline: 0; +} +input[type='text']:focus { + background-color: white; +} +input[type='text']:disabled { + border-color: #808080; + background-color: #808080; +} +.toolbar input[type='text'], +.palette .row input[type='text'] { + margin-top: 10.5px; +} +.palette .row input[type='text']:nth-last-child(1) { + margin-top: 8.5px; +} +input[type='text'].expand { + width: calc(100% - 12px); +} +/* One line text input with buttons inside */ +.icon-input { + display: inline-block; + position: relative; +} +.icon-input input[type='text'] { + width: 135px; + padding-right: 34px; +} +.icon-input.expand { + width: 100%; +} +.icon-input.expand input[type='text'] { + width: calc(100% - 42px); +} +.icon-input button { + width: 34px; + height: 34px; + padding: 0; + position: absolute; + background-size: 22px 22px; +} +.icon-input button.right { + margin: 0 0 0 -34px; + border-radius: 0 22px 22px 0; + right: 0; +} +.icon-input button { + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + border: 0; +} +.icon-input button { + top: 2px; +} +.toolbar .icon-input button:hover { + background-color: transparent; +} +.toolbar .icon-input button { + top: 10.5px; +} +button.cancel { + background-image: url(../icons/actions/entry-cancel.svg); +} +button.cancel:active { + background-image: url(../icons/actions/entry-cancel-active.svg); +} +button.cancel:disabled { + background-image: url(../icons/actions/entry-cancel-disabled.svg); +} +/* Slider */ +/* FIXME this is not fully Sugarized yet */ +input[type='range'] { + -webkit-appearance: none !important; + background-color: #808080; + border-radius: 22px; + height: 11px; + cursor: pointer; +} +input[type='range']::-webkit-slider-thumb { + -webkit-appearance: none !important; + background-color: #c0c0c0; + border: 4px solid #808080; + border-radius: 11px; + height: 22px; + width: 22px; +} +input[type='range']::-webkit-slider-thumb:hover { + background-color: #e2e2e2; + border-color: #a2a2a2; +} +.toolbar input[type='range'] { + margin-top: 22px; +} +/* Label */ +label { + -moz-user-select: none; + -webkit-user-select: none; +} +/* Palette */ +.palette { + color: white; + background-color: transparent; + position: absolute; + pointer-events: none; +} +.palette-invoker { + width: 51px; + height: 53px; + background-color: black; + border: 2px solid #808080; + border-bottom: 0; + background-position: 2px 2px; + background-size: 47px; + background-repeat: no-repeat; +} +.palette-invoker:after { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: 55px; + height: 11px; + top: 44px; + left: 0; + background-image: url('../icons/emblems/arrow-up.svg'); +} +.palette-invoker:before { + content: ""; + background-color: black; + position: absolute; + display: block; + top: 55px; + width: 51px; + left: 2px; + height: 2px; +} +.palette .wrapper { + background-color: black; + border: 2px solid #808080; + max-width: 271px; + min-width: 161px; + min-height: 51px; + pointer-events: auto; +} +.palette .header { + height: 51px; + line-height: 51px; + -moz-user-select: none; + -webkit-user-select: none; + margin: 0 5.5px; + font-weight: bold; +} +.palette hr { + border: 1px solid #808080; +} +.palette hr.header-separator { + margin-top: 0; +} +.palette .row { + height: 55px; + margin: 0 5.5px; +} +.palette .row:nth-last-child(1) { + height: 51px; +} +.palette .row.small { + height: 22px; +} +.palette .row.expand { + height: auto; +} +/* Palette menu */ +.palette .menu { + list-style-type: none; + padding: 0; + margin-top: 11px; + margin-bottom: 11px; +} +.palette .menu button { + background-color: transparent; + border-radius: 0; + border: 0; + width: 100%; + text-align: left; + line-height: 33px; + padding-top: 0; + padding-bottom: 0; + margin-top: 0; + margin-bottom: 0; +} +.palette .menu button:hover { + background-color: #808080; + color: white; +} +.palette .menu button.icon { + padding-left: 37px; +} +.palette .menu button.icon span { + top: 0; + left: 0; + width: 33px; + height: 33px; +} +/* Scrollbar */ +::-webkit-scrollbar { + background-color: #808080; + width: 11px; +} +::-webkit-scrollbar-thumb { + background-color: white; + border: 2px solid #dddddd; + border-radius: 11px; +} +/* Grid for visual debugging and layout */ +.grid { + background-color: transparent; + position: absolute; + top: 0; + left: 0; +} +/* Checkbox and radio */ +input[type='checkbox'], +input[type='radio'] { + background-position: center; + background-size: contain; + width: 22px; + height: 22px; + margin: 2px 2px 4px 2px; + vertical-align: middle; + -moz-appearance: none; + appearance: none; + -webkit-appearance: none; +} +.toolbar input[type='checkbox'], +.toolbar input[type='radio'] { + margin-top: 14.5px; + margin-bottom: 18.5px; +} +input[type='checkbox'] { + background-image: url(../icons/actions/checkbox-unchecked.svg); +} +input[type='checkbox']:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} +input[type='checkbox']:checked:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} +input[type='checkbox']:checked { + background-image: url(../icons/actions/checkbox-checked.svg); +} +input[type='radio'] { + background-image: url(../icons/actions/radio.svg); +} +input[type='radio']:active { + background-image: url(../icons/actions/radio-selected.svg); +} +input[type='radio']:checked:active { + background-image: url(../icons/actions/radio-active-selected.svg); +} +input[type='radio']:checked { + background-image: url(../icons/actions/radio-active.svg); +} +/* Textarea */ +textarea { + border: 2px solid #808080; + margin: 2px; +} +textarea.expand { + width: calc(100% - 12px); +} +/* Lists */ +ul.flat-list { + list-style-type: none; + padding: 0; + margin: 0; +} +ul.flat-list li { + border-bottom: 2px dotted #c0c0c0; + background-color: white; + height: 31px; + line-height: 31px; +} +ul.flat-list li:nth-last-child(1) { + border-bottom: none; +} +ul.flat-list.big li { + height: 42px; + line-height: 42px; +} +ul.flat-list.striped li:nth-child(odd) { + background-color: #e5e5e5; +} +/* ActivityPalette */ +#activity-palette .wrapper { + width: 271px; +} diff --git a/lib/sugar-web/graphics/css/sugar.less b/lib/sugar-web/graphics/css/sugar.less new file mode 100644 index 0000000..8fa9766 --- /dev/null +++ b/lib/sugar-web/graphics/css/sugar.less @@ -0,0 +1,589 @@ +@toolbar-grey: #282828; +@button-grey: #808080; +@panel-grey: #C0C0C0; +@text-field-grey: #E5E5E5; +@link-blue: #0076C3; + +@line-width: 2px; +@subcell-size: 11px; +@cell-size: 5 * @subcell-size; +@font-size: 10pt; + +@toolbar-height: @cell-size; +@icon-small-size: 2 * @subcell-size; + +@toolbutton-size: @toolbar-height - (4 * @line-width); +@toolbar-button-margin-top: (@toolbar-height / 2) - (2 * @line-width) - + (@icon-small-size / 2); + +@input-text-padding: (2 * @line-width); +@input-text-line-height: 2 * @subcell-size; +@input-text-width: 3 * @cell-size; +@toolbar-input-height: (@toolbar-height / 2) - (@input-text-line-height / 2) - + (@input-text-padding + @line-width); + +@button-small-size: @input-text-line-height + (2 * @input-text-padding) + + (2 * @line-width); + +* { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +html { + height: 100%; +} + +body { + margin: 0; + height: 100%; + background-color: @panel-grey; + position: relative; + font-family: sans-serif; + font-size: @font-size; +} + +.unselectable { + -moz-user-select: none; + -webkit-user-select: none; +} + +.pull-right { + float: right; +} + +a { + color: @link-blue; + text-decoration: none; +} + +/* Toolbar */ + +.toolbar { + color: white; + background-color: @toolbar-grey; + padding: 0 @toolbutton-size + (5 * @line-width); + height: @toolbar-height; + .unselectable; +} + +/* Toolbar separator */ + +.toolbar hr { + display: inline-block; + height: 3 * @subcell-size; + border: (@line-width / 2) solid @button-grey; + margin: (@subcell-size - (@line-width * 3)) / 2; + margin-bottom: -1 * (@subcell-size + (@line-width * 3)); +} + +/* Toolbar toolbutton */ + +.toolbar .toolbutton { + background-color: transparent; + background-position: center; + background-size: contain; + background-repeat: no-repeat; + color: white; + color: transparent; + border: 0; + border-radius: @subcell-size / 2; + margin: (2 * @line-width) @line-width; + width: @toolbutton-size; + height: @toolbutton-size; + position: relative; +} + +.toolbar .toolbutton:hover { + background-color: black; +} + +.toolbar .toolbutton:active, .toolbar .toolbutton.active { + background-color: @button-grey; +} + +.toolbar .toolbutton img { + width: 100%; + height: 100%; +} + +.toolbar .toolbutton:before { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: @cell-size; + height: @subcell-size; + bottom: -2 * @line-width; + right: -2 * @line-width; +} + +.toolbar .toolbutton.invoker:before { + background-image: url('../icons/emblems/arrow-down.svg'); +} + +.toolbar #stop-button { + background-image: url('../icons/actions/activity-stop.svg'); +} + +/* Canvas */ + +#canvas { + color: black; + background-color: @panel-grey; + position: absolute; + bottom: 0; + top: @toolbar-height; + overflow-y: auto; + width: 100%; +} + +/* Button */ + +button { + background-color: @button-grey; + color: white; + border: @line-width solid transparent; + border-radius: 2 * @subcell-size; + line-height: @icon-small-size; + padding: @line-width (2 * @line-width); + .unselectable; +} + +button:hover { + background-color: @button-grey + #222; + border-color: @button-grey + #222; +} + +button:active { + background-color: white; + color: black; + border-color: @button-grey; +} + +button:focus { + border-color: white; +} + +.toolbar button { + margin-top: @toolbar-button-margin-top; +} + +/* Button with icon */ + +button.icon { + position: relative; + padding-left: @icon-small-size + (2 * @line-width); +} + +button.icon span.ok { + background-image: url(../icons/actions/dialog-ok.svg); +} + +button.icon:active span.ok { + background-image: url(../icons/actions/dialog-ok-active.svg); +} + +button.icon span.cancel { + background-image: url(../icons/actions/dialog-cancel.svg); +} + +button.icon:active span.cancel { + background-image: url(../icons/actions/dialog-cancel-active.svg); +} + +button.icon span { + display: inline-block; + width: @icon-small-size; + height: @icon-small-size; + background-color: transparent; + background-position: center; + background-size: @icon-small-size @icon-small-size; + background-repeat: no-repeat; + position: absolute; +} + +button.icon span { + top: @line-width; + left: @line-width; +} + +/* One line text input */ + +input[type='text'] { + background-color: @text-field-grey; + border: @line-width solid @text-field-grey; + border-radius: 2 * @subcell-size; + padding: @input-text-padding; + width: @input-text-width; + line-height: @input-text-line-height; + outline: 0; +} + +input[type='text']:focus { + background-color: white; +} + +input[type='text']:disabled { + border-color: @button-grey; + background-color: @button-grey; +} + +.toolbar input[type='text'], .palette .row input[type='text'] { + margin-top: @toolbar-input-height; +} + +.palette .row input[type='text']:nth-last-child(1) { + margin-top: @toolbar-input-height - @line-width; +} + +input[type='text'].expand { + width: calc(~"100%" - (2 * @input-text-padding + 2 * @line-width)); +} + +/* One line text input with buttons inside */ + +.icon-input { + display: inline-block; + position: relative; +} + +.icon-input input[type='text'] { + width: @input-text-width + @input-text-padding - @button-small-size; + padding-right: @button-small-size; +} + +.icon-input.expand { + width: 100%; +} + +.icon-input.expand input[type='text'] { + width: calc(~"100%" - (@input-text-padding + 2 * @line-width + + @button-small-size)); +} + +.icon-input button { + width: @button-small-size; + height: @button-small-size; + padding: 0; + position: absolute; + background-size: @icon-small-size @icon-small-size; +} + +.icon-input button.right { + margin: 0 0 0 -@button-small-size; + border-radius: 0 (2 * @subcell-size) (2 * @subcell-size) 0; + right: 0; +} + +.icon-input button { + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + border: 0; +} + +.icon-input button { + top: @line-width; +} + +.toolbar .icon-input button:hover { + background-color: transparent; +} + +.toolbar .icon-input button { + top: @toolbar-input-height; +} + +button.cancel { + background-image: url(../icons/actions/entry-cancel.svg); +} + +button.cancel:active { + background-image: url(../icons/actions/entry-cancel-active.svg); +} + +button.cancel:disabled { + background-image: url(../icons/actions/entry-cancel-disabled.svg); +} + +/* Slider */ +/* FIXME this is not fully Sugarized yet */ + +input[type='range'] { + -webkit-appearance: none !important; + background-color: @button-grey; + border-radius: 2 * @subcell-size; + height: @subcell-size; + cursor: pointer; +} + +input[type='range']::-webkit-slider-thumb { + -webkit-appearance: none !important; + background-color: @panel-grey; + border: (2 * @line-width) solid @button-grey; + border-radius: @subcell-size; + height: @icon-small-size; + width: @icon-small-size; +} + +input[type='range']::-webkit-slider-thumb:hover { + background-color: @panel-grey + #222; + border-color: @button-grey + #222; +} + +.toolbar input[type='range'] { + margin-top: (@toolbar-height / 2) - (@subcell-size / 2); +} + +/* Label */ + +label { + .unselectable; +} + +/* Palette */ + +.palette { + color: white; + background-color: transparent; + position: absolute; + pointer-events: none; +} + +.palette-invoker { + width: @cell-size - 2 * @line-width; + height: @cell-size - @line-width; + background-color: black; + border: @line-width solid @button-grey; + border-bottom: 0; + background-position: @line-width @line-width; + background-size: @toolbutton-size; + background-repeat: no-repeat; +} + +.palette-invoker:after { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: @cell-size; + height: @subcell-size; + top: 4 * @subcell-size; + left: 0; + background-image: url('../icons/emblems/arrow-up.svg'); +} + +.palette-invoker:before { + content: ""; + background-color: black; + position: absolute; + display: block; + top: @cell-size; + width: @cell-size - 2 * @line-width; + left: @line-width; + height: @line-width; +} + +.palette .wrapper { + background-color: black; + border: @line-width solid @button-grey; + max-width: 5 * @cell-size - 2 * @line-width; + min-width: 3 * @cell-size - 2 * @line-width; + min-height: @cell-size - 2 * @line-width; + pointer-events: auto; +} + +.palette .header { + height: @cell-size - 2 * @line-width; + line-height: @cell-size - 2 * @line-width; + .unselectable; + margin: 0 (@subcell-size / 2); + font-weight: bold; +} + +.palette hr { + border: (@line-width / 2) solid @button-grey; +} + +.palette hr.header-separator { + margin-top: 0; +} + +.palette .row { + height: @cell-size; + margin: 0 (@subcell-size / 2); +} + +.palette .row:nth-last-child(1) { + height: @cell-size - 2 * @line-width; +} + +.palette .row.small { + height: 2 * @subcell-size; +} + +.palette .row.expand { + height: auto; +} + +/* Palette menu */ + +.palette .menu { + list-style-type: none; + padding: 0; + margin-top: @subcell-size; + margin-bottom: @subcell-size; +} + +.palette .menu button { + background-color: transparent; + border-radius: 0; + border: 0; + width: 100%; + text-align: left; + line-height: 3 * @subcell-size; + padding-top: 0; + padding-bottom: 0; + margin-top: 0; + margin-bottom: 0; +} + +.palette .menu button:hover { + background-color: @button-grey; + color: white; +} + +.palette .menu button.icon { + padding-left: 3 * @subcell-size + 2 * @line-width; +} + +.palette .menu button.icon span { + top: 0; + left: 0; + width: 3 * @subcell-size; + height: 3 * @subcell-size; +} + +/* Scrollbar */ + +::-webkit-scrollbar { + background-color: @button-grey; + width: @subcell-size; +} + +::-webkit-scrollbar-thumb { + background-color: white; + border: @line-width solid #ddd; + border-radius: @subcell-size; +} + +/* Grid for visual debugging and layout */ + +.grid { + background-color: transparent; + position: absolute; + top: 0; + left: 0; +} + +.appearance(@value) { + -moz-appearance: @value; + appearance: @value; + -webkit-appearance: @value; +} + +/* Checkbox and radio */ + +input[type='checkbox'], +input[type='radio'] { + background-position: center; + background-size: contain; + width: @icon-small-size; + height: @icon-small-size; + margin: @line-width @line-width (2 * @line-width) @line-width; + vertical-align: middle; + .appearance(none); +} + +.toolbar input[type='checkbox'], +.toolbar input[type='radio'] { + margin-top: (@toolbar-height - @icon-small-size) / 2 - @line-width; + margin-bottom: (@toolbar-height - @icon-small-size) / 2 + @line-width; +} + +input[type='checkbox'] { + background-image: url(../icons/actions/checkbox-unchecked.svg); +} + +input[type='checkbox']:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} + +input[type='checkbox']:checked:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} + +input[type='checkbox']:checked { + background-image: url(../icons/actions/checkbox-checked.svg); +} + +input[type='radio'] { + background-image: url(../icons/actions/radio.svg); +} + +input[type='radio']:active { + background-image: url(../icons/actions/radio-selected.svg); +} + +input[type='radio']:checked:active { + background-image: url(../icons/actions/radio-active-selected.svg); +} + +input[type='radio']:checked { + background-image: url(../icons/actions/radio-active.svg); +} + +/* Textarea */ + +textarea { + border: @line-width solid @button-grey; + margin: @line-width; +} + +textarea.expand { + width: calc(~"100%" - (6 * (@line-width))); +} + +/* Lists */ + +ul.flat-list { + list-style-type: none; + padding: 0; + margin: 0; +} + +ul.flat-list li { + border-bottom: @line-width dotted @panel-grey; + background-color: white; + height: 3 * @subcell-size - @line-width; + line-height: 3 * @subcell-size - @line-width; +} + +ul.flat-list li:nth-last-child(1) { + border-bottom: none; +} + +ul.flat-list.big li { + height: 4 * @subcell-size - @line-width; + line-height: 4 * @subcell-size - @line-width; +} + +ul.flat-list.striped li:nth-child(odd) { + background-color: @text-field-grey; +} + +/* ActivityPalette */ + +#activity-palette .wrapper { + width: 5 * @cell-size - 2 * @line-width; +}
\ No newline at end of file diff --git a/lib/sugar-web/graphics/grid.js b/lib/sugar-web/graphics/grid.js new file mode 100644 index 0000000..503713a --- /dev/null +++ b/lib/sugar-web/graphics/grid.js @@ -0,0 +1,54 @@ +define(function () { + var grid = {}; + + // Add a grid overlay with lines spaced by subcellSize, for visual + // debugging. This is useful while doing the activity layout or + // while developing widgets. + grid.addGrid = function (subcellSize) { + var canvas = document.createElement('canvas'); + canvas.className = "grid"; + document.body.appendChild(canvas); + + var updateGrid = function () { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + + var ctx = canvas.getContext("2d"); + ctx.strokeStyle = "#00FFFF"; + + var subcellsVertical = window.innerHeight / subcellSize; + for (i = 0; i < subcellsVertical; i++) { + if ((i + 1) % 5 === 0) { + ctx.lineWidth = 1; + } else { + ctx.lineWidth = 0.5; + } + ctx.beginPath(); + ctx.moveTo(0, subcellSize * (i + 1)); + ctx.lineTo(canvas.width, subcellSize * (i + 1)); + ctx.stroke(); + } + + var subcellsHorizontal = window.innerWidth / subcellSize; + for (i = 0; i < subcellsHorizontal; i++) { + if ((i + 1) % 5 === 0) { + ctx.lineWidth = 1; + } else { + ctx.lineWidth = 0.5; + } + ctx.beginPath(); + ctx.moveTo(subcellSize * (i + 1), 0); + ctx.lineTo(subcellSize * (i + 1), canvas.height); + ctx.stroke(); + } + }; + + updateGrid(); + + window.onresize = function (event) { + updateGrid(); + }; + }; + + return grid; +}); diff --git a/lib/sugar-web/graphics/icon.js b/lib/sugar-web/graphics/icon.js new file mode 100644 index 0000000..f2c0791 --- /dev/null +++ b/lib/sugar-web/graphics/icon.js @@ -0,0 +1,81 @@ +define(function () { + var icon = {}; + + function changeColors(iconData, fillColor, strokeColor) { + var re; + + if (fillColor) { + re = /(<!ENTITY fill_color ")(.*)(">)/; + iconData = iconData.replace(re, "$1" + fillColor + "$3"); + } + + if (strokeColor) { + re = /(<!ENTITY stroke_color ")(.*)(">)/; + iconData = iconData.replace(re, "$1" + strokeColor + "$3"); + } + + return iconData; + } + + icon.load = function (iconInfo, callback) { + var source; + var dataHeader = "data:image/svg+xml,"; + + if ("uri" in iconInfo) { + source = iconInfo.uri; + } else if ("name" in iconInfo) { + source = "lib/graphics/icons/" + iconInfo.name + ".svg"; + } + + var fillColor = iconInfo.fillColor; + var strokeColor = iconInfo.strokeColor; + + // If source is already a data uri, read it instead of doing + // the XMLHttpRequest + if (source.substring(0, 4) == 'data') { + var iconData = unescape(source.slice(dataHeader.length)); + var newData = changeColors(iconData, fillColor, strokeColor); + callback(dataHeader + escape(newData)); + return; + } + + var client = new XMLHttpRequest(); + + client.onload = function () { + var iconData = this.responseText; + var newData = changeColors(iconData, fillColor, strokeColor); + callback(dataHeader + escape(newData)); + }; + + client.open("GET", source); + client.send(); + }; + + function getBackgroundURL(elem) { + var style = elem.currentStyle || window.getComputedStyle(elem, ''); + // Remove prefix 'url(' and suffix ')' before return + return style.backgroundImage.slice(4, -1); + } + + function setBackgroundURL(elem, url) { + elem.style.backgroundImage = "url('" + url + "')"; + } + + icon.colorize = function (elem, colors, callback) { + var iconInfo = { + "uri": getBackgroundURL(elem), + "strokeColor": colors.stroke, + "fillColor": colors.fill + }; + + icon.load(iconInfo, function (url) { + setBackgroundURL(elem, url); + if (callback) { + callback(); + } + }); + + }; + + return icon; +}); diff --git a/lib/sugar-web/graphics/icons/actions/activity-stop.svg b/lib/sugar-web/graphics/icons/actions/activity-stop.svg new file mode 100644 index 0000000..11b82e8 --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/activity-stop.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#010101"> + <!ENTITY fill_color "#FFFFFF"> +]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="activity-stop"> + <path d="M36.822,5H18.181L5.002,18.182V36.82L18.181,50h18.642l13.176-13.18V18.182L36.822,5z M35.75,35.414h-15.5v-15.5h15.5V35.414z" display="inline" fill="&fill_color;"/> +</g></svg>
\ No newline at end of file diff --git a/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg b/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg new file mode 100644 index 0000000..8ec1223 --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY fill_color "#E5E5E5"> + <!ENTITY stroke_color "#010101"> +]> +<svg + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="26" + height="26" + id="svg814"> + <g + transform="translate(0,-1026.3622)" + id="layer1"> + <rect + width="23.999523" + height="23.999525" + x="1" + y="1027.3627" + id="rect3268" + style="color:&stroke_color;;fill:&fill_color;;fill-opacity:1;stroke:#808080;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <path + d="m 5.6810941,1039.239 4.7885489,4.7885 9.330512,-9.3305" + id="path3438" + style="fill:none;stroke:&stroke_color;;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + </g> +</svg> diff --git a/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg b/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg new file mode 100644 index 0000000..3cfce18 --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY fill_color "#FFFFFF"> + <!ENTITY stroke_color "#010101"> +]> +<svg + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="26" + height="26" + id="svg814"> + <g + transform="translate(0,-1026.3622)" + id="layer1"> + <rect + width="23.999523" + height="23.999525" + x="1" + y="1027.3627" + id="rect3268" + style="color:&stroke_color;;fill:&fill_color;;fill-opacity:1;stroke:#808080;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <path + d="m 5.6810941,1039.239 4.7885489,4.7885 9.330512,-9.3305" + id="path3438" + style="fill:none;stroke:&stroke_color;;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + </g> +</svg> diff --git a/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg b/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg new file mode 100644 index 0000000..2263279 --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY fill_color "#E5E5E5"> +]> +<svg + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="26" + height="26" + id="svg814"> + <g + transform="translate(0,-1026.3622)" + id="layer1"> + <rect + width="23.999523" + height="23.999525" + x="1" + y="1027.3627" + id="rect3268" + style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:#808080;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + </g> +</svg> diff --git a/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg b/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg new file mode 100644 index 0000000..e158782 --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY fill_color "#FFFFFF"> +]> +<svg + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="26" + height="26" + id="svg814"> + <g + transform="translate(0,-1026.3622)" + id="layer1"> + <rect + width="23.999523" + height="23.999525" + x="1" + y="1027.3627" + id="rect3268" + style="color:#000000;fill:&fill_color;;stroke:#808080;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + </g> +</svg> diff --git a/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg b/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg new file mode 100644 index 0000000..dc0d688 --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#FFFFFF"> + <!ENTITY fill_color "#808080"> +]><svg enable-background="new 0 0 55.125 55" height="55px" version="1.1" viewBox="0 0 55.125 55" width="55.125px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="dialog-cancel"> + <path d="M27.557,5.053c-12.43,0-22.5,10.076-22.5,22.497c0,12.432,10.07,22.503,22.5,22.503 c12.431,0,22.5-10.071,22.5-22.503C50.057,15.129,39.987,5.053,27.557,5.053z M37.756,33.212c1.254,1.256,1.257,3.291,0,4.545 c-0.628,0.629-1.451,0.943-2.274,0.943c-0.822,0-1.644-0.314-2.27-0.94l-5.76-5.761l-5.76,5.761 c-0.627,0.626-1.449,0.94-2.271,0.94c-0.823,0-1.647-0.314-2.275-0.943c-1.254-1.254-1.254-3.289,0.004-4.545l5.758-5.758 l-5.758-5.758c-1.258-1.254-1.258-3.292-0.004-4.546c1.255-1.254,3.292-1.259,4.546,0l5.76,5.759l5.76-5.759 c1.252-1.259,3.288-1.254,4.544,0c1.257,1.254,1.254,3.292,0,4.546l-5.758,5.758L37.756,33.212z" display="inline" fill="&fill_color;"/> +</g></svg> diff --git a/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg b/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg new file mode 100644 index 0000000..dab4ae2 --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#010101"> + <!ENTITY fill_color "#FFFFFF"> +]><svg enable-background="new 0 0 55.125 55" height="55px" version="1.1" viewBox="0 0 55.125 55" width="55.125px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="dialog-cancel"> + <path d="M27.557,5.053c-12.43,0-22.5,10.076-22.5,22.497c0,12.432,10.07,22.503,22.5,22.503 c12.431,0,22.5-10.071,22.5-22.503C50.057,15.129,39.987,5.053,27.557,5.053z M37.756,33.212c1.254,1.256,1.257,3.291,0,4.545 c-0.628,0.629-1.451,0.943-2.274,0.943c-0.822,0-1.644-0.314-2.27-0.94l-5.76-5.761l-5.76,5.761 c-0.627,0.626-1.449,0.94-2.271,0.94c-0.823,0-1.647-0.314-2.275-0.943c-1.254-1.254-1.254-3.289,0.004-4.545l5.758-5.758 l-5.758-5.758c-1.258-1.254-1.258-3.292-0.004-4.546c1.255-1.254,3.292-1.259,4.546,0l5.76,5.759l5.76-5.759 c1.252-1.259,3.288-1.254,4.544,0c1.257,1.254,1.254,3.292,0,4.546l-5.758,5.758L37.756,33.212z" display="inline" fill="&fill_color;"/> +</g></svg>
\ No newline at end of file diff --git a/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg b/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg new file mode 100644 index 0000000..45de840 --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#FFFFFF"> + <!ENTITY fill_color "#808080"> +]><svg enable-background="new 0 0 55.125 55" height="55px" version="1.1" viewBox="0 0 55.125 55" width="55.125px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="dialog-ok"> + <path d="M27.498,5C15.071,5,5,15.071,5,27.498C5,39.923,15.071,50,27.498,50 C39.927,50,50,39.923,50,27.498C50,15.071,39.927,5,27.498,5z M41.419,19.886L24.706,40.208L13.789,29.29 c-1.258-1.254-1.258-3.292-0.003-4.546c1.254-1.255,3.292-1.255,4.547,0l5.908,5.904L36.452,15.8 c1.127-1.368,3.153-1.568,4.525-0.438C42.347,16.489,42.546,18.515,41.419,19.886z" display="inline" fill="&fill_color;"/> +</g></svg> diff --git a/lib/sugar-web/graphics/icons/actions/dialog-ok.svg b/lib/sugar-web/graphics/icons/actions/dialog-ok.svg new file mode 100644 index 0000000..69e5a2a --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/dialog-ok.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#010101"> + <!ENTITY fill_color "#FFFFFF"> +]><svg enable-background="new 0 0 55.125 55" height="55px" version="1.1" viewBox="0 0 55.125 55" width="55.125px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="dialog-ok"> + <path d="M27.498,5C15.071,5,5,15.071,5,27.498C5,39.923,15.071,50,27.498,50 C39.927,50,50,39.923,50,27.498C50,15.071,39.927,5,27.498,5z M41.419,19.886L24.706,40.208L13.789,29.29 c-1.258-1.254-1.258-3.292-0.003-4.546c1.254-1.255,3.292-1.255,4.547,0l5.908,5.904L36.452,15.8 c1.127-1.368,3.153-1.568,4.525-0.438C42.347,16.489,42.546,18.515,41.419,19.886z" display="inline" fill="&fill_color;"/> +</g></svg>
\ No newline at end of file diff --git a/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg b/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg new file mode 100644 index 0000000..467509e --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY fill_color "#010101"> + <!ENTITY stroke_color "#FFFFFF"> +]> +<svg + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="22" + height="22" + viewBox="0 0 22 22" + id="svg5047" + xml:space="preserve"><g + transform="matrix(0.48888889,0,0,0.48888889,-2.4723111,-2.4703556)" + id="dialog-cancel"><path + d="m 37.756,33.212 c 1.254,1.256 1.257,3.291 0,4.545 -0.628,0.629 -1.451,0.943 -2.274,0.943 -0.822,0 -1.644,-0.314 -2.27,-0.94 l -5.76,-5.761 -5.76,5.761 c -0.627,0.626 -1.449,0.94 -2.271,0.94 -0.823,0 -1.647,-0.314 -2.275,-0.943 -1.254,-1.254 -1.254,-3.289 0.004,-4.545 l 5.758,-5.758 -5.758,-5.758 c -1.258,-1.254 -1.258,-3.292 -0.004,-4.546 1.255,-1.254 3.292,-1.259 4.546,0 l 5.76,5.759 5.76,-5.759 c 1.252,-1.259 3.288,-1.254 4.544,0 1.257,1.254 1.254,3.292 0,4.546 l -5.758,5.758 z" + id="path5050" /></g><g + transform="matrix(0.48888889,0,0,0.48888889,-2.4723111,-2.4703556)" + id="g3084" + style="fill:#ffffff;stroke-width:4.090909;stroke-miterlimit:4;stroke-dasharray:none"><path + d="m 27.557,5.053 c -12.43,0 -22.5,10.076 -22.5,22.497 0,12.432 10.07,22.503 22.5,22.503 12.431,0 22.5,-10.071 22.5,-22.503 0,-12.421 -10.07,-22.497 -22.5,-22.497 z m 10.199,28.159 c 1.254,1.256 1.257,3.291 0,4.545 -0.628,0.629 -1.451,0.943 -2.274,0.943 -0.822,0 -1.644,-0.314 -2.27,-0.94 l -5.76,-5.761 -5.76,5.761 c -0.627,0.626 -1.449,0.94 -2.271,0.94 -0.823,0 -1.647,-0.314 -2.275,-0.943 -1.254,-1.254 -1.254,-3.289 0.004,-4.545 l 5.758,-5.758 -5.758,-5.758 c -1.258,-1.254 -1.258,-3.292 -0.004,-4.546 1.255,-1.254 3.292,-1.259 4.546,0 l 5.76,5.759 5.76,-5.759 c 1.252,-1.259 3.288,-1.254 4.544,0 1.257,1.254 1.254,3.292 0,4.546 l -5.758,5.758 5.758,5.758 z" + id="path3086" + style="fill:#ffffff;stroke-width:4.090909;stroke-miterlimit:4;stroke-dasharray:none" /></g></svg> diff --git a/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg b/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg new file mode 100644 index 0000000..55b4cdb --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY fill_color "#C0C0C0"> + <!ENTITY stroke_color "#FFFFFF"> +]> +<svg + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="22" + height="22" + viewBox="0 0 22 22" + id="svg5047" + xml:space="preserve"><g + transform="matrix(0.48888889,0,0,0.48888889,-2.4723111,-2.4703556)" + id="dialog-cancel" + style="fill:&fill_color;;display:block"> + <path + d="m 27.557,5.053 c -12.43,0 -22.5,10.076 -22.5,22.497 0,12.432 10.07,22.503 22.5,22.503 12.431,0 22.5,-10.071 22.5,-22.503 0,-12.421 -10.07,-22.497 -22.5,-22.497 z m 10.199,28.159 c 1.254,1.256 1.257,3.291 0,4.545 -0.628,0.629 -1.451,0.943 -2.274,0.943 -0.822,0 -1.644,-0.314 -2.27,-0.94 l -5.76,-5.761 -5.76,5.761 c -0.627,0.626 -1.449,0.94 -2.271,0.94 -0.823,0 -1.647,-0.314 -2.275,-0.943 -1.254,-1.254 -1.254,-3.289 0.004,-4.545 l 5.758,-5.758 -5.758,-5.758 c -1.258,-1.254 -1.258,-3.292 -0.004,-4.546 1.255,-1.254 3.292,-1.259 4.546,0 l 5.76,5.759 5.76,-5.759 c 1.252,-1.259 3.288,-1.254 4.544,0 1.257,1.254 1.254,3.292 0,4.546 l -5.758,5.758 5.758,5.758 z" + id="path5050" + style="fill:&fill_color;;display:inline" /> +</g></svg> diff --git a/lib/sugar-web/graphics/icons/actions/entry-cancel.svg b/lib/sugar-web/graphics/icons/actions/entry-cancel.svg new file mode 100644 index 0000000..5339a7e --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/entry-cancel.svg @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY fill_color "#010101"> + <!ENTITY stroke_color "#FFFFFF"> +]> +<svg + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="22" + height="22" + viewBox="0 0 22 22" + id="svg5047" + xml:space="preserve"><g + transform="matrix(0.48888889,0,0,0.48888889,-2.4723111,-2.4703556)" + id="dialog-cancel" + style="fill:&fill_color;;display:block"> + <path + d="m 27.557,5.053 c -12.43,0 -22.5,10.076 -22.5,22.497 0,12.432 10.07,22.503 22.5,22.503 12.431,0 22.5,-10.071 22.5,-22.503 0,-12.421 -10.07,-22.497 -22.5,-22.497 z m 10.199,28.159 c 1.254,1.256 1.257,3.291 0,4.545 -0.628,0.629 -1.451,0.943 -2.274,0.943 -0.822,0 -1.644,-0.314 -2.27,-0.94 l -5.76,-5.761 -5.76,5.761 c -0.627,0.626 -1.449,0.94 -2.271,0.94 -0.823,0 -1.647,-0.314 -2.275,-0.943 -1.254,-1.254 -1.254,-3.289 0.004,-4.545 l 5.758,-5.758 -5.758,-5.758 c -1.258,-1.254 -1.258,-3.292 -0.004,-4.546 1.255,-1.254 3.292,-1.259 4.546,0 l 5.76,5.759 5.76,-5.759 c 1.252,-1.259 3.288,-1.254 4.544,0 1.257,1.254 1.254,3.292 0,4.546 l -5.758,5.758 5.758,5.758 z" + id="path5050" + style="fill:&fill_color;;display:inline" /> +</g></svg> diff --git a/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg b/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg new file mode 100644 index 0000000..c1d1085 --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY fill_color "#E5E5E5"> + <!ENTITY stroke_color "#808080"> +]> +<svg + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="26" + height="26" + id="svg27352"> + <g + transform="translate(0,10)" + id="layer1"> + <path + d="M 13,0 C 5.8202983,0 0,5.8202983 0,13 0,20.179702 5.8202983,26 13,26 20.179702,26 26,20.179702 26,13 26,5.8202983 20.179702,0 13,0 z m 0,1 C 19.627417,1 25,6.372583 25,13 25,19.627417 19.627417,25 13,25 6.372583,25 1,19.627417 1,13 1,6.372583 6.372583,1 13,1 z" + transform="translate(0,-10)" + id="path3002" + style="color:#000000;fill:&stroke_color;;fill-opacity:1;stroke:none;stroke-width:0.82332432;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <path + d="m 11.276603,5.8201666 a 5.3516083,5.3516083 0 1 1 -10.70321633,0 5.3516083,5.3516083 0 1 1 10.70321633,0 z" + transform="matrix(2.2423166,0,0,2.2423166,-0.28571452,-10.050656)" + id="path4079" + style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:none;stroke-width:0.82325572;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <path + d="m 14.100023,7.3515821 a 5.9129128,5.9129128 0 1 1 -11.8258259,0 5.9129128,5.9129128 0 1 1 11.8258259,0 z" + transform="matrix(0.84560692,0,0,0.84560692,6.0769232,-3.2165487)" + id="path4754" + style="color:#000000;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.84599996;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + </g> +</svg> diff --git a/lib/sugar-web/graphics/icons/actions/radio-active.svg b/lib/sugar-web/graphics/icons/actions/radio-active.svg new file mode 100644 index 0000000..a5fe591 --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/radio-active.svg @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY fill_color "#FFFFFF"> + <!ENTITY stroke_color "#808080"> +]> +<svg + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="26" + height="26" + id="svg27352"> + <g + transform="translate(0,10)" + id="layer1"> + <path + d="M 13,0 C 5.8202983,0 0,5.8202983 0,13 0,20.179702 5.8202983,26 13,26 20.179702,26 26,20.179702 26,13 26,5.8202983 20.179702,0 13,0 z m 0,1 C 19.627417,1 25,6.372583 25,13 25,19.627417 19.627417,25 13,25 6.372583,25 1,19.627417 1,13 1,6.372583 6.372583,1 13,1 z" + transform="translate(0,-10)" + id="path3002" + style="color:#000000;fill:&stroke_color;;fill-opacity:1;stroke:none;stroke-width:0.82332432;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <path + d="m 11.276603,5.8201666 a 5.3516083,5.3516083 0 1 1 -10.70321633,0 5.3516083,5.3516083 0 1 1 10.70321633,0 z" + transform="matrix(2.2423166,0,0,2.2423166,-0.28571452,-10.050656)" + id="path4079" + style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:none;stroke-width:0.82325572;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <path + d="m 14.100023,7.3515821 a 5.9129128,5.9129128 0 1 1 -11.8258259,0 5.9129128,5.9129128 0 1 1 11.8258259,0 z" + transform="matrix(0.84560692,0,0,0.84560692,6.0769232,-3.2165487)" + id="path4754" + style="color:#000000;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.84599996;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + </g> +</svg> diff --git a/lib/sugar-web/graphics/icons/actions/radio-selected.svg b/lib/sugar-web/graphics/icons/actions/radio-selected.svg new file mode 100644 index 0000000..a24b97e --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/radio-selected.svg @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY fill_color "#E5E5E5"> + <!ENTITY stroke_color "#808080"> +]> +<svg + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="26" + height="26" + id="svg27352"> + <g + transform="translate(0,10)" + id="layer1"> + <path + d="M 13,0 C 5.8202983,0 0,5.8202983 0,13 0,20.179702 5.8202983,26 13,26 20.179702,26 26,20.179702 26,13 26,5.8202983 20.179702,0 13,0 z m 0,1 C 19.627417,1 25,6.372583 25,13 25,19.627417 19.627417,25 13,25 6.372583,25 1,19.627417 1,13 1,6.372583 6.372583,1 13,1 z" + transform="translate(0,-10)" + id="path3002" + style="color:#000000;fill:&stroke_color;;fill-opacity:1;stroke:none;stroke-width:0.82332432;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <path + d="m 11.276603,5.8201666 a 5.3516083,5.3516083 0 1 1 -10.70321633,0 5.3516083,5.3516083 0 1 1 10.70321633,0 z" + transform="matrix(2.2423166,0,0,2.2423166,-0.28571452,-10.050656)" + id="path4079" + style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:none;stroke-width:0.82325572;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + </g> +</svg> diff --git a/lib/sugar-web/graphics/icons/actions/radio.svg b/lib/sugar-web/graphics/icons/actions/radio.svg new file mode 100644 index 0000000..d250286 --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/radio.svg @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY fill_color "#FFFFFF"> + <!ENTITY stroke_color "#808080"> +]> +<svg + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="26" + height="26" + id="svg27352"> + <g + transform="translate(0,10)" + id="layer1"> + <path + d="M 13,0 C 5.8202983,0 0,5.8202983 0,13 0,20.179702 5.8202983,26 13,26 20.179702,26 26,20.179702 26,13 26,5.8202983 20.179702,0 13,0 z m 0,1 C 19.627417,1 25,6.372583 25,13 25,19.627417 19.627417,25 13,25 6.372583,25 1,19.627417 1,13 1,6.372583 6.372583,1 13,1 z" + transform="translate(0,-10)" + id="path3002" + style="color:#000000;fill:&stroke_color;;fill-opacity:1;stroke:none;stroke-width:0.82332432;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <path + d="m 11.276603,5.8201666 a 5.3516083,5.3516083 0 1 1 -10.70321633,0 5.3516083,5.3516083 0 1 1 10.70321633,0 z" + transform="matrix(2.2423166,0,0,2.2423166,-0.28571452,-10.050656)" + id="path4079" + style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:none;stroke-width:0.82325572;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + </g> +</svg> diff --git a/lib/sugar-web/graphics/icons/actions/zoom-groups.svg b/lib/sugar-web/graphics/icons/actions/zoom-groups.svg new file mode 100644 index 0000000..b88462f --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/zoom-groups.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#010101"> + <!ENTITY fill_color "#FFFFFF"> +]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="zoom-groups"> + <path d="M27.5,5C15.074,5,5,15.074,5,27.5S15.074,50,27.5,50 S50,39.926,50,27.5S39.926,5,27.5,5z M18.759,37.955c-2.618,0-4.745-2.127-4.745-4.746c0-2.621,2.125-4.745,4.745-4.745 s4.748,2.124,4.748,4.745C23.506,35.828,21.379,37.955,18.759,37.955z M27.5,22.625c-2.621,0-4.746-2.125-4.746-4.747 c0-2.618,2.125-4.742,4.746-4.742c2.623,0,4.748,2.123,4.748,4.742C32.246,20.501,30.121,22.625,27.5,22.625z M36.238,37.953 c-2.619,0-4.744-2.125-4.744-4.744c0-2.621,2.125-4.746,4.744-4.746c2.623,0,4.748,2.125,4.748,4.746 C40.986,35.828,38.861,37.953,36.238,37.953z" display="inline" fill="&fill_color;" id="zoom-groups_2_"/> +</g></svg>
\ No newline at end of file diff --git a/lib/sugar-web/graphics/icons/actions/zoom-home.svg b/lib/sugar-web/graphics/icons/actions/zoom-home.svg new file mode 100644 index 0000000..5578fec --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/zoom-home.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#010101"> + <!ENTITY fill_color "#FFFFFF"> +]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="zoom-home"> + <path d="M27.5,5C15.074,5,5,15.073,5,27.5C5,39.926,15.074,50,27.5,50 C39.928,50,50,39.926,50,27.5C50,15.073,39.926,5,27.5,5z M27.497,32.251c-2.621,0-4.749-2.131-4.749-4.75 c0-2.624,2.128-4.75,4.749-4.75c2.628,0,4.757,2.126,4.757,4.75C32.254,30.12,30.125,32.251,27.497,32.251z" display="inline" fill="&fill_color;" id="zoom-home_1_"/> +</g></svg>
\ No newline at end of file diff --git a/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg b/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg new file mode 100644 index 0000000..8d3f8d1 --- /dev/null +++ b/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#010101"> + <!ENTITY fill_color "#FFFFFF"> +]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="zoom-neighborhood"> + <path d="M27.5,5C15.074,5,5,15.074,5,27.5C5,39.927,15.074,50,27.5,50 C39.927,50,50,39.927,50,27.5C50,15.074,39.927,5,27.5,5z M37.604,13.904c2.096,0,3.795,1.699,3.795,3.794 c0,2.096-1.699,3.794-3.795,3.794s-3.794-1.699-3.794-3.794S35.51,13.904,37.604,13.904z M9.626,27.501 c0-2.099,1.7-3.799,3.793-3.799c2.097,0,3.796,1.7,3.796,3.799c0,2.095-1.7,3.793-3.796,3.793 C11.324,31.294,9.626,29.596,9.626,27.501z M17.391,41.096c-2.093,0-3.793-1.695-3.793-3.792c0-2.099,1.701-3.796,3.793-3.796 c2.099,0,3.799,1.696,3.799,3.796C21.189,39.399,19.489,41.096,17.391,41.096z M17.698,21.195c-2.094,0-3.795-1.7-3.795-3.799 c0-2.096,1.7-3.795,3.795-3.795c2.099,0,3.796,1.701,3.796,3.795C21.495,19.495,19.798,21.195,17.698,21.195z M27.499,45.374 c-2.1,0-3.797-1.7-3.797-3.794c0-2.098,1.696-3.797,3.797-3.797c2.095,0,3.794,1.699,3.794,3.797 C31.293,43.676,29.594,45.374,27.499,45.374z M27.501,17.217c-2.096,0-3.795-1.699-3.795-3.795s1.699-3.795,3.795-3.795 c2.097,0,3.795,1.699,3.795,3.795S29.598,17.217,27.501,17.217z M37.301,41.401c-2.096,0-3.795-1.699-3.795-3.795 s1.699-3.794,3.795-3.794s3.794,1.698,3.794,3.794S39.396,41.401,37.301,41.401z M41.581,31.298c-2.096,0-3.795-1.698-3.795-3.795 c0-2.096,1.699-3.793,3.799-3.793c2.094,0,3.791,1.697,3.791,3.793C45.376,29.6,43.679,31.298,41.581,31.298z" fill="&fill_color;" id="zoom-neighborhood_3_"/> +</g></svg>
\ No newline at end of file diff --git a/lib/sugar-web/graphics/icons/emblems/arrow-down.svg b/lib/sugar-web/graphics/icons/emblems/arrow-down.svg new file mode 100644 index 0000000..2de1a9e --- /dev/null +++ b/lib/sugar-web/graphics/icons/emblems/arrow-down.svg @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY fill_color "#FFFFFF"> + <!ENTITY stroke_color "#010101"> +]> +<svg + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="55" + height="11" + id="svg2"> + <g + transform="translate(0,-1041.3622)" + id="layer1"> + <path + d="m 29.756935,1046.8861 -2.256934,2.257 -2.256936,-2.257" + id="rect2998" + style="color:#000000;fill:none;stroke:&fill_color;;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + </g> +</svg> diff --git a/lib/sugar-web/graphics/icons/emblems/arrow-up.svg b/lib/sugar-web/graphics/icons/emblems/arrow-up.svg new file mode 100644 index 0000000..a977f4a --- /dev/null +++ b/lib/sugar-web/graphics/icons/emblems/arrow-up.svg @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY fill_color "#FFFFFF"> + <!ENTITY stroke_color "#010101"> +]> +<svg + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="55" + height="11" + id="svg2"> + <g + transform="translate(0,-1041.3622)" + id="layer1"> + <path + d="m 29.756935,1049.1431 -2.256934,-2.257 -2.256936,2.257" + id="rect2998" + style="color:#000000;fill:none;stroke:&fill_color;;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + </g> +</svg> diff --git a/lib/sugar-web/graphics/menupalette.js b/lib/sugar-web/graphics/menupalette.js new file mode 100644 index 0000000..df61cb2 --- /dev/null +++ b/lib/sugar-web/graphics/menupalette.js @@ -0,0 +1,64 @@ +define(["sugar-web/graphics/palette", "mustache"], function (palette, mustache) { + + var menupalette = {}; + + menupalette.MenuPalette = function (invoker, primaryText, menuData) { + palette.Palette.call(this, invoker, primaryText); + + this.selectItemEvent = new CustomEvent( + "selectItem", { + detail: { + item: undefined + }, + bubbles: true, + cancelable: true + }); + + this.template = + '{{#.}}' + + '<li><button' + + ' {{ #icon }}class="icon"{{ /icon }}' + + ' {{ #id }}id="{{ id }}"{{ /id }}' + + '>' + + '{{ #icon }}<span></span>{{ /icon }}' + + '{{ label }}</button></li>' + + '{{/.}}'; + + var menuElem = document.createElement('ul'); + menuElem.className = "menu"; + menuElem.innerHTML = mustache.render(this.template, menuData); + this.setContent([menuElem]); + + // Pop-down the palette when a item in the menu is clicked. + + this.buttons = menuElem.querySelectorAll('button'); + + var that = this; + + function popDownOnButtonClick(event) { + that.selectItemEvent.detail.target = event.target; + that.getPalette().dispatchEvent(that.selectItemEvent); + that.popDown(); + } + + for (var i = 0; i < this.buttons.length; i++) { + this.buttons[i].addEventListener('click', popDownOnButtonClick); + } + }; + + var addEventListener = function (type, listener, useCapture) { + return this.getPalette().addEventListener(type, listener, useCapture); + }; + + menupalette.MenuPalette.prototype = + Object.create(palette.Palette.prototype, { + addEventListener: { + value: addEventListener, + enumerable: true, + configurable: true, + writable: true + } + }); + + return menupalette; +}); diff --git a/lib/sugar-web/graphics/palette.js b/lib/sugar-web/graphics/palette.js new file mode 100644 index 0000000..9a194a1 --- /dev/null +++ b/lib/sugar-web/graphics/palette.js @@ -0,0 +1,179 @@ +define(function () { + var palettesGroup = []; + + function getOffset(elem) { + // Ugly hack to consider the palette margin. + var style = elem.currentStyle || window.getComputedStyle(elem, ''); + + // Remove 'px' from the strings. + var x = -2 * style.marginLeft.slice(0, -2); + var y = -1 * style.marginTop.slice(0, -2); + + var rect = elem.getBoundingClientRect(); + x += rect.left; + y += rect.top; + return { + top: y, + left: x, + width: rect.width, + height: rect.height + }; + } + + var palette = {}; + + palette.Palette = function (invoker, primaryText) { + this.invoker = invoker; + if (this.invoker.classList.contains("toolbutton")) { + this.invoker.classList.add("invoker"); + } + this.primaryText = primaryText; + var paletteElem; + var wrapperElem; + var headerElem; + var headerSeparatorElem; + var containerElem; + var that = this; + palettesGroup.push(this); + + invoker.addEventListener('click', function (event) { + if (!that.invoker.classList.contains("toolbutton")) { + updatePosition(event.x, event.y); + } + that.toggle(); + }); + + function updatePosition(clickX, clickY) { + var paletteX; + var paletteY; + + if (typeof (clickX) !== 'undefined' && + typeof (clickY) !== 'undefined') { + paletteX = clickX; + paletteY = clickY; + } else { + var invokerOffset = getOffset(that.invoker); + paletteX = invokerOffset.left; + paletteY = invokerOffset.top; + } + + paletteElem.style.left = paletteX + "px"; + paletteElem.style.top = paletteY + "px"; + } + + // A palette element can have a header, content, one or both. + + function createPaletteElement() { + if (paletteElem !== undefined) { + return; + } + paletteElem = document.createElement('div'); + paletteElem.className = "palette"; + paletteElem.style.visibility = "hidden"; + document.body.appendChild(paletteElem); + + if (that.invoker.classList.contains("toolbutton")) { + invokerElem = document.createElement('div'); + invokerElem.className = "palette-invoker"; + var style = that.invoker.currentStyle || + window.getComputedStyle(that.invoker, ''); + invokerElem.style.backgroundImage = style.backgroundImage; + + invokerElem.addEventListener('click', function (e) { + that.toggle(); + }); + + paletteElem.appendChild(invokerElem); + + } + + wrapperElem = document.createElement('div'); + wrapperElem.className = "wrapper"; + paletteElem.appendChild(wrapperElem); + + if (that.primaryText !== undefined) { + headerElem = document.createElement('div'); + headerElem.className = "header"; + headerElem.innerText = that.primaryText; + wrapperElem.appendChild(headerElem); + } + + headerSeparatorElem = document.createElement('hr'); + headerSeparatorElem.className = "header-separator"; + headerSeparatorElem.style.display = "none"; + wrapperElem.appendChild(headerSeparatorElem); + + containerElem = document.createElement('div'); + containerElem.className = "container"; + wrapperElem.appendChild(containerElem); + + updatePosition(); + } + + this.getPalette = function () { + if (paletteElem === undefined) { + createPaletteElement(); + } + return paletteElem; + }; + + this.setContent = function (elementsList) { + if (paletteElem === undefined) { + createPaletteElement(); + } + + (function removePreviousContent() { + for (var i = 0; i < containerElem.children.length; i++) { + var child = containerElem.children[i]; + containerElem.removeChild(child); + } + }()); + + (function addNewContent() { + for (var i = 0; i < elementsList.length; i++) { + var child = elementsList[i]; + containerElem.appendChild(child); + } + }()); + + // The header separator will be visible only if there are + // both, header and content. + if (elementsList.length > 0 && this.primaryText !== undefined) { + headerSeparatorElem.style.display = "block"; + } else { + headerSeparatorElem.style.display = "none"; + } + }; + + this.isDown = function () { + return paletteElem === undefined || + paletteElem.style.visibility == "hidden"; + }; + + }; + + palette.Palette.prototype.popUp = function () { + for (var i = 0; i < palettesGroup.length; i++) { + otherPalette = palettesGroup[i]; + if (otherPalette != this) { + otherPalette.popDown(); + } + } + this.getPalette().style.visibility = "visible"; + }; + + palette.Palette.prototype.popDown = function () { + this.getPalette().style.visibility = "hidden"; + }; + + palette.Palette.prototype.toggle = function () { + if (this.isDown()) { + this.popUp(); + } else { + this.popDown(); + } + }; + + return palette; + +}); diff --git a/lib/sugar-web/graphics/radiobuttonsgroup.js b/lib/sugar-web/graphics/radiobuttonsgroup.js new file mode 100644 index 0000000..712681b --- /dev/null +++ b/lib/sugar-web/graphics/radiobuttonsgroup.js @@ -0,0 +1,59 @@ +define(function () { + var radioButtonsGroup = {}; + + // ## RadioButtonsGroup + // + // A group of elements where only one can be active at the same + // time. + // + // When an element is clicked, it becomes the active one. The + // active element gains the 'active' CSS class. + // + // Parameters: + // + // * **elems** Array of elements of the group. + radioButtonsGroup.RadioButtonsGroup = function (elems) { + this.elems = elems; + var active; + + for (i = 0; i < elems.length; i++) { + var elem = elems[i]; + elem.addEventListener("click", clickHandler); + + // The first element that has 'active' CSS class is made + // the active of the group on startup. + if (active === undefined && elem.classList.contains('active')) { + active = elem; + } + } + + // If no element has 'active' CSS class, the first element of + // the array is made the active. + if (active === undefined) { + active = elems[0]; + updateClasses(); + } + + function clickHandler(evt) { + active = evt.target; + updateClasses(); + } + + function updateClasses() { + for (i = 0; i < elems.length; i++) { + var elem = elems[i]; + elem.classList.remove('active'); + } + active.classList.add('active'); + } + + // Get the active element. + this.getActive = function () { + return active; + }; + + }; + + return radioButtonsGroup; + +}); diff --git a/lib/sugar-web/graphics/xocolor.js b/lib/sugar-web/graphics/xocolor.js new file mode 100644 index 0000000..1184ffa --- /dev/null +++ b/lib/sugar-web/graphics/xocolor.js @@ -0,0 +1,729 @@ +define(function () { + + var xocolor = {}; + + xocolor.colors = [ + { + stroke: '#B20008', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#B20008' + }, + { + stroke: '#E6000A', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#E6000A' + }, + { + stroke: '#FFADCE', + fill: '#FF2B34' + }, + { + stroke: '#9A5200', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#FF2B34' + }, + { + stroke: '#807500', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#FF2B34' + }, + { + stroke: '#008009', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#FF2B34' + }, + { + stroke: '#00588C', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#FF2B34' + }, + { + stroke: '#5E008C', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#5E008C' + }, + { + stroke: '#7F00BF', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#7F00BF' + }, + { + stroke: '#D1A3FF', + fill: '#FF2B34' + }, + { + stroke: '#9A5200', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#9A5200' + }, + { + stroke: '#C97E00', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#C97E00' + }, + { + stroke: '#FFC169', + fill: '#FF8F00' + }, + { + stroke: '#807500', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#FF8F00' + }, + { + stroke: '#008009', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#FF8F00' + }, + { + stroke: '#00588C', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#FF8F00' + }, + { + stroke: '#5E008C', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#5E008C' + }, + { + stroke: '#A700FF', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#A700FF' + }, + { + stroke: '#D1A3FF', + fill: '#FF8F00' + }, + { + stroke: '#B20008', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#FF8F00' + }, + { + stroke: '#807500', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#BE9E00' + }, + { + stroke: '#FFFA00', + fill: '#EDDE00' + }, + { + stroke: '#008009', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#008009' + }, + { + stroke: '#00EA11', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#00EA11' + }, + { + stroke: '#8BFF7A', + fill: '#F8E800' + }, + { + stroke: '#00588C', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#00588C' + }, + { + stroke: '#00A0FF', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#00A0FF' + }, + { + stroke: '#BCCEFF', + fill: '#F8E800' + }, + { + stroke: '#5E008C', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#5E008C' + }, + { + stroke: '#AC32FF', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#AC32FF' + }, + { + stroke: '#D1A3FF', + fill: '#F8E800' + }, + { + stroke: '#B20008', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#F8E800' + }, + { + stroke: '#9A5200', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#F8E800' + }, + { + stroke: '#008009', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#00EA11' + }, + { + stroke: '#00588C', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#00EA11' + }, + { + stroke: '#5E008C', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#5E008C' + }, + { + stroke: '#7F00BF', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#7F00BF' + }, + { + stroke: '#D1A3FF', + fill: '#00EA11' + }, + { + stroke: '#B20008', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#00EA11' + }, + { + stroke: '#9A5200', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#00EA11' + }, + { + stroke: '#807500', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#00EA11' + }, + { + stroke: '#00588C', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#00A0FF' + }, + { + stroke: '#5E008C', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#5E008C' + }, + { + stroke: '#9900E6', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#9900E6' + }, + { + stroke: '#D1A3FF', + fill: '#00A0FF' + }, + { + stroke: '#B20008', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#00A0FF' + }, + { + stroke: '#9A5200', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#00A0FF' + }, + { + stroke: '#807500', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#00A0FF' + }, + { + stroke: '#008009', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#00A0FF' + }, + { + stroke: '#5E008C', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#5E008C' + }, + { + stroke: '#7F00BF', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#7F00BF' + }, + { + stroke: '#D1A3FF', + fill: '#AC32FF' + }, + { + stroke: '#B20008', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#AC32FF' + }, + { + stroke: '#9A5200', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#AC32FF' + }, + { + stroke: '#807500', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#AC32FF' + }, + { + stroke: '#008009', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#AC32FF' + }, + { + stroke: '#00588C', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#AC32FF' + } + ]; + + return xocolor; +}); diff --git a/lib/sugar-web/package.json b/lib/sugar-web/package.json new file mode 100644 index 0000000..000de4e --- /dev/null +++ b/lib/sugar-web/package.json @@ -0,0 +1,9 @@ +{ + "volo": { + "baseUrl": "lib", + "dependencies": { + "webL10n": "github:sugarlabs/webL10n", + "mustache": "github:janl/mustache.js/0.7.2" + } + } +}
\ No newline at end of file diff --git a/lib/webL10n.js b/lib/webL10n.js new file mode 100644 index 0000000..f76d66a --- /dev/null +++ b/lib/webL10n.js @@ -0,0 +1,1029 @@ +/** + * Copyright (c) 2011-2013 Fabien Cazenave, Mozilla. + * + * 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. + */ + +/*jshint browser: true, devel: true, es5: true, globalstrict: true */ +'use strict'; + +define(function (require) { + var gL10nData = {}; + var gTextData = ''; + var gTextProp = 'textContent'; + var gLanguage = ''; + var gMacros = {}; + var gReadyState = 'loading'; + + /** + * Synchronously loading l10n resources significantly minimizes flickering + * from displaying the app with non-localized strings and then updating the + * strings. Although this will block all script execution on this page, we + * expect that the l10n resources are available locally on flash-storage. + * + * As synchronous XHR is generally considered as a bad idea, we're still + * loading l10n resources asynchronously -- but we keep this in a setting, + * just in case... and applications using this library should hide their + * content until the `localized' event happens. + */ + + var gAsyncResourceLoading = true; // read-only + + + /** + * Debug helpers + * + * gDEBUG == 0: don't display any console message + * gDEBUG == 1: display only warnings, not logs + * gDEBUG == 2: display all console messages + */ + + var gDEBUG = 1; + + function consoleLog(message) { + if (gDEBUG >= 2) { + console.log('[l10n] ' + message); + } + }; + + function consoleWarn(message) { + if (gDEBUG) { + console.warn('[l10n] ' + message); + } + }; + + + /** + * DOM helpers for the so-called "HTML API". + * + * These functions are written for modern browsers. For old versions of IE, + * they're overridden in the 'startup' section at the end of this file. + */ + + function getL10nResourceLinks() { + return document.querySelectorAll('link[type="application/l10n"]'); + } + + function getL10nDictionary() { + var script = document.querySelector('script[type="application/l10n"]'); + // TODO: support multiple and external JSON dictionaries + return script ? JSON.parse(script.innerHTML) : null; + } + + function getTranslatableChildren(element) { + return element ? element.querySelectorAll('*[data-l10n-id]') : []; + } + + function getL10nAttributes(element) { + if (!element) + return {}; + + var l10nId = element.getAttribute('data-l10n-id'); + var l10nArgs = element.getAttribute('data-l10n-args'); + var args = {}; + if (l10nArgs) { + try { + args = JSON.parse(l10nArgs); + } catch (e) { + consoleWarn('could not parse arguments for #' + l10nId); + } + } + return { id: l10nId, args: args }; + } + + function fireL10nReadyEvent(lang) { + var evtObject = document.createEvent('Event'); + evtObject.initEvent('localized', true, false); + evtObject.language = lang; + document.dispatchEvent(evtObject); + } + + function xhrLoadText(url, onSuccess, onFailure, asynchronous) { + onSuccess = onSuccess || function _onSuccess(data) {}; + onFailure = onFailure || function _onFailure() { + consoleWarn(url + ' not found.'); + }; + + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, asynchronous); + if (xhr.overrideMimeType) { + xhr.overrideMimeType('text/plain; charset=utf-8'); + } + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + if (xhr.status == 200 || xhr.status === 0) { + onSuccess(xhr.responseText); + } else { + onFailure(); + } + } + }; + xhr.onerror = onFailure; + xhr.ontimeout = onFailure; + + // in Firefox OS with the app:// protocol, trying to XHR a non-existing + // URL will raise an exception here -- hence this ugly try...catch. + try { + xhr.send(null); + } catch (e) { + onFailure(); + } + } + + + /** + * l10n resource parser: + * - reads (async XHR) the l10n resource matching `lang'; + * - imports linked resources (synchronously) when specified; + * - parses the text data (fills `gL10nData' and `gTextData'); + * - triggers success/failure callbacks when done. + * + * @param {string} href + * URL of the l10n resource to parse. + * + * @param {string} lang + * locale (language) to parse. + * + * @param {Function} successCallback + * triggered when the l10n resource has been successully parsed. + * + * @param {Function} failureCallback + * triggered when the an error has occured. + * + * @return {void} + * uses the following global variables: gL10nData, gTextData, gTextProp. + */ + + function parseResource(href, lang, successCallback, failureCallback) { + var baseURL = href.replace(/[^\/]*$/, '') || './'; + + // handle escaped characters (backslashes) in a string + function evalString(text) { + if (text.lastIndexOf('\\') < 0) + return text; + return text.replace(/\\\\/g, '\\') + .replace(/\\n/g, '\n') + .replace(/\\r/g, '\r') + .replace(/\\t/g, '\t') + .replace(/\\b/g, '\b') + .replace(/\\f/g, '\f') + .replace(/\\{/g, '{') + .replace(/\\}/g, '}') + .replace(/\\"/g, '"') + .replace(/\\'/g, "'"); + } + + // parse *.properties text data into an l10n dictionary + function parseProperties(text) { + var dictionary = []; + + // token expressions + var reBlank = /^\s*|\s*$/; + var reComment = /^\s*#|^\s*$/; + var reSection = /^\s*\[(.*)\]\s*$/; + var reImport = /^\s*@import\s+url\((.*)\)\s*$/i; + var reSplit = /^([^=\s]*)\s*=\s*(.+)$/; // TODO: escape EOLs with '\' + + // parse the *.properties file into an associative array + function parseRawLines(rawText, extendedSyntax) { + var entries = rawText.replace(reBlank, '').split(/[\r\n]+/); + var currentLang = '*'; + var genericLang = lang.replace(/-[a-z]+$/i, ''); + var skipLang = false; + var match = ''; + + for (var i = 0; i < entries.length; i++) { + var line = entries[i]; + + // comment or blank line? + if (reComment.test(line)) + continue; + + // the extended syntax supports [lang] sections and @import rules + if (extendedSyntax) { + if (reSection.test(line)) { // section start? + match = reSection.exec(line); + currentLang = match[1]; + skipLang = (currentLang !== '*') && + (currentLang !== lang) && (currentLang !== genericLang); + continue; + } else if (skipLang) { + continue; + } + if (reImport.test(line)) { // @import rule? + match = reImport.exec(line); + loadImport(baseURL + match[1]); // load the resource synchronously + } + } + + // key-value pair + var tmp = line.match(reSplit); + if (tmp && tmp.length == 3) { + dictionary[tmp[1]] = evalString(tmp[2]); + } + } + } + + // import another *.properties file + function loadImport(url) { + xhrLoadText(url, function(content) { + parseRawLines(content, false); // don't allow recursive imports + }, null, false); // load synchronously + } + + // fill the dictionary + parseRawLines(text, true); + return dictionary; + } + + // load and parse l10n data (warning: global variables are used here) + xhrLoadText(href, function(response) { + gTextData += response; // mostly for debug + + // parse *.properties text data into an l10n dictionary + var data = parseProperties(response); + + // find attribute descriptions, if any + for (var key in data) { + var id, prop, index = key.lastIndexOf('.'); + if (index > 0) { // an attribute has been specified + id = key.substring(0, index); + prop = key.substr(index + 1); + } else { // no attribute: assuming text content by default + id = key; + prop = gTextProp; + } + if (!gL10nData[id]) { + gL10nData[id] = {}; + } + gL10nData[id][prop] = data[key]; + } + + // trigger callback + if (successCallback) { + successCallback(); + } + }, failureCallback, gAsyncResourceLoading); + }; + + // load and parse all resources for the specified locale + function loadLocale(lang, callback) { + callback = callback || function _callback() {}; + + clear(); + gLanguage = lang; + + // check all <link type="application/l10n" href="..." /> nodes + // and load the resource files + var langLinks = getL10nResourceLinks(); + var langCount = langLinks.length; + if (langCount == 0) { + // we might have a pre-compiled dictionary instead + var dict = getL10nDictionary(); + if (dict && dict.locales && dict.default_locale) { + consoleLog('using the embedded JSON directory, early way out'); + gL10nData = dict.locales[lang] || dict.locales[dict.default_locale]; + callback(); + } else { + consoleLog('no resource to load, early way out'); + } + // early way out + fireL10nReadyEvent(lang); + gReadyState = 'complete'; + return; + } + + // start the callback when all resources are loaded + var onResourceLoaded = null; + var gResourceCount = 0; + onResourceLoaded = function() { + gResourceCount++; + if (gResourceCount >= langCount) { + callback(); + fireL10nReadyEvent(lang); + gReadyState = 'complete'; + } + }; + + // load all resource files + function l10nResourceLink(link) { + var href = link.href; + var type = link.type; + this.load = function(lang, callback) { + var applied = lang; + parseResource(href, lang, callback, function() { + consoleWarn(href + ' not found.'); + applied = ''; + }); + return applied; // return lang if found, an empty string if not found + }; + } + + for (var i = 0; i < langCount; i++) { + var resource = new l10nResourceLink(langLinks[i]); + var rv = resource.load(lang, onResourceLoaded); + if (rv != lang) { // lang not found, used default resource instead + consoleWarn('"' + lang + '" resource not found'); + gLanguage = ''; + } + } + } + + // clear all l10n data + function clear() { + gL10nData = {}; + gTextData = ''; + gLanguage = ''; + // TODO: clear all non predefined macros. + // There's no such macro /yet/ but we're planning to have some... + } + + + /** + * Get rules for plural forms (shared with JetPack), see: + * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html + * https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p + * + * @param {string} lang + * locale (language) used. + * + * @return {Function} + * returns a function that gives the plural form name for a given integer: + * var fun = getPluralRules('en'); + * fun(1) -> 'one' + * fun(0) -> 'other' + * fun(1000) -> 'other'. + */ + + function getPluralRules(lang) { + var locales2rules = { + 'af': 3, + 'ak': 4, + 'am': 4, + 'ar': 1, + 'asa': 3, + 'az': 0, + 'be': 11, + 'bem': 3, + 'bez': 3, + 'bg': 3, + 'bh': 4, + 'bm': 0, + 'bn': 3, + 'bo': 0, + 'br': 20, + 'brx': 3, + 'bs': 11, + 'ca': 3, + 'cgg': 3, + 'chr': 3, + 'cs': 12, + 'cy': 17, + 'da': 3, + 'de': 3, + 'dv': 3, + 'dz': 0, + 'ee': 3, + 'el': 3, + 'en': 3, + 'eo': 3, + 'es': 3, + 'et': 3, + 'eu': 3, + 'fa': 0, + 'ff': 5, + 'fi': 3, + 'fil': 4, + 'fo': 3, + 'fr': 5, + 'fur': 3, + 'fy': 3, + 'ga': 8, + 'gd': 24, + 'gl': 3, + 'gsw': 3, + 'gu': 3, + 'guw': 4, + 'gv': 23, + 'ha': 3, + 'haw': 3, + 'he': 2, + 'hi': 4, + 'hr': 11, + 'hu': 0, + 'id': 0, + 'ig': 0, + 'ii': 0, + 'is': 3, + 'it': 3, + 'iu': 7, + 'ja': 0, + 'jmc': 3, + 'jv': 0, + 'ka': 0, + 'kab': 5, + 'kaj': 3, + 'kcg': 3, + 'kde': 0, + 'kea': 0, + 'kk': 3, + 'kl': 3, + 'km': 0, + 'kn': 0, + 'ko': 0, + 'ksb': 3, + 'ksh': 21, + 'ku': 3, + 'kw': 7, + 'lag': 18, + 'lb': 3, + 'lg': 3, + 'ln': 4, + 'lo': 0, + 'lt': 10, + 'lv': 6, + 'mas': 3, + 'mg': 4, + 'mk': 16, + 'ml': 3, + 'mn': 3, + 'mo': 9, + 'mr': 3, + 'ms': 0, + 'mt': 15, + 'my': 0, + 'nah': 3, + 'naq': 7, + 'nb': 3, + 'nd': 3, + 'ne': 3, + 'nl': 3, + 'nn': 3, + 'no': 3, + 'nr': 3, + 'nso': 4, + 'ny': 3, + 'nyn': 3, + 'om': 3, + 'or': 3, + 'pa': 3, + 'pap': 3, + 'pl': 13, + 'ps': 3, + 'pt': 3, + 'rm': 3, + 'ro': 9, + 'rof': 3, + 'ru': 11, + 'rwk': 3, + 'sah': 0, + 'saq': 3, + 'se': 7, + 'seh': 3, + 'ses': 0, + 'sg': 0, + 'sh': 11, + 'shi': 19, + 'sk': 12, + 'sl': 14, + 'sma': 7, + 'smi': 7, + 'smj': 7, + 'smn': 7, + 'sms': 7, + 'sn': 3, + 'so': 3, + 'sq': 3, + 'sr': 11, + 'ss': 3, + 'ssy': 3, + 'st': 3, + 'sv': 3, + 'sw': 3, + 'syr': 3, + 'ta': 3, + 'te': 3, + 'teo': 3, + 'th': 0, + 'ti': 4, + 'tig': 3, + 'tk': 3, + 'tl': 4, + 'tn': 3, + 'to': 0, + 'tr': 0, + 'ts': 3, + 'tzm': 22, + 'uk': 11, + 'ur': 3, + 've': 3, + 'vi': 0, + 'vun': 3, + 'wa': 4, + 'wae': 3, + 'wo': 0, + 'xh': 3, + 'xog': 3, + 'yo': 0, + 'zh': 0, + 'zu': 3 + }; + + // utility functions for plural rules methods + function isIn(n, list) { + return list.indexOf(n) !== -1; + } + function isBetween(n, start, end) { + return start <= n && n <= end; + } + + // list of all plural rules methods: + // map an integer to the plural form name to use + var pluralRules = { + '0': function(n) { + return 'other'; + }, + '1': function(n) { + if ((isBetween((n % 100), 3, 10))) + return 'few'; + if (n === 0) + return 'zero'; + if ((isBetween((n % 100), 11, 99))) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '2': function(n) { + if (n !== 0 && (n % 10) === 0) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '3': function(n) { + if (n == 1) + return 'one'; + return 'other'; + }, + '4': function(n) { + if ((isBetween(n, 0, 1))) + return 'one'; + return 'other'; + }, + '5': function(n) { + if ((isBetween(n, 0, 2)) && n != 2) + return 'one'; + return 'other'; + }, + '6': function(n) { + if (n === 0) + return 'zero'; + if ((n % 10) == 1 && (n % 100) != 11) + return 'one'; + return 'other'; + }, + '7': function(n) { + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '8': function(n) { + if ((isBetween(n, 3, 6))) + return 'few'; + if ((isBetween(n, 7, 10))) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '9': function(n) { + if (n === 0 || n != 1 && (isBetween((n % 100), 1, 19))) + return 'few'; + if (n == 1) + return 'one'; + return 'other'; + }, + '10': function(n) { + if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19))) + return 'few'; + if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19))) + return 'one'; + return 'other'; + }, + '11': function(n) { + if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) + return 'few'; + if ((n % 10) === 0 || + (isBetween((n % 10), 5, 9)) || + (isBetween((n % 100), 11, 14))) + return 'many'; + if ((n % 10) == 1 && (n % 100) != 11) + return 'one'; + return 'other'; + }, + '12': function(n) { + if ((isBetween(n, 2, 4))) + return 'few'; + if (n == 1) + return 'one'; + return 'other'; + }, + '13': function(n) { + if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) + return 'few'; + if (n != 1 && (isBetween((n % 10), 0, 1)) || + (isBetween((n % 10), 5, 9)) || + (isBetween((n % 100), 12, 14))) + return 'many'; + if (n == 1) + return 'one'; + return 'other'; + }, + '14': function(n) { + if ((isBetween((n % 100), 3, 4))) + return 'few'; + if ((n % 100) == 2) + return 'two'; + if ((n % 100) == 1) + return 'one'; + return 'other'; + }, + '15': function(n) { + if (n === 0 || (isBetween((n % 100), 2, 10))) + return 'few'; + if ((isBetween((n % 100), 11, 19))) + return 'many'; + if (n == 1) + return 'one'; + return 'other'; + }, + '16': function(n) { + if ((n % 10) == 1 && n != 11) + return 'one'; + return 'other'; + }, + '17': function(n) { + if (n == 3) + return 'few'; + if (n === 0) + return 'zero'; + if (n == 6) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '18': function(n) { + if (n === 0) + return 'zero'; + if ((isBetween(n, 0, 2)) && n !== 0 && n != 2) + return 'one'; + return 'other'; + }, + '19': function(n) { + if ((isBetween(n, 2, 10))) + return 'few'; + if ((isBetween(n, 0, 1))) + return 'one'; + return 'other'; + }, + '20': function(n) { + if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !( + isBetween((n % 100), 10, 19) || + isBetween((n % 100), 70, 79) || + isBetween((n % 100), 90, 99) + )) + return 'few'; + if ((n % 1000000) === 0 && n !== 0) + return 'many'; + if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92])) + return 'two'; + if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91])) + return 'one'; + return 'other'; + }, + '21': function(n) { + if (n === 0) + return 'zero'; + if (n == 1) + return 'one'; + return 'other'; + }, + '22': function(n) { + if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99))) + return 'one'; + return 'other'; + }, + '23': function(n) { + if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0) + return 'one'; + return 'other'; + }, + '24': function(n) { + if ((isBetween(n, 3, 10) || isBetween(n, 13, 19))) + return 'few'; + if (isIn(n, [2, 12])) + return 'two'; + if (isIn(n, [1, 11])) + return 'one'; + return 'other'; + } + }; + + // return a function that gives the plural form name for a given integer + var index = locales2rules[lang.replace(/-.*$/, '')]; + if (!(index in pluralRules)) { + consoleWarn('plural form unknown for [' + lang + ']'); + return function() { return 'other'; }; + } + return pluralRules[index]; + } + + // pre-defined 'plural' macro + gMacros.plural = function(str, param, key, prop) { + var n = parseFloat(param); + if (isNaN(n)) + return str; + + // TODO: support other properties (l20n still doesn't...) + if (prop != gTextProp) + return str; + + // initialize _pluralRules + if (!gMacros._pluralRules) { + gMacros._pluralRules = getPluralRules(gLanguage); + } + var index = '[' + gMacros._pluralRules(n) + ']'; + + // try to find a [zero|one|two] key if it's defined + if (n === 0 && (key + '[zero]') in gL10nData) { + str = gL10nData[key + '[zero]'][prop]; + } else if (n == 1 && (key + '[one]') in gL10nData) { + str = gL10nData[key + '[one]'][prop]; + } else if (n == 2 && (key + '[two]') in gL10nData) { + str = gL10nData[key + '[two]'][prop]; + } else if ((key + index) in gL10nData) { + str = gL10nData[key + index][prop]; + } else if ((key + '[other]') in gL10nData) { + str = gL10nData[key + '[other]'][prop]; + } + + return str; + }; + + + /** + * l10n dictionary functions + */ + + // fetch an l10n object, warn if not found, apply `args' if possible + function getL10nData(key, args) { + var data = gL10nData[key]; + if (!data) { + consoleWarn('#' + key + ' missing for [' + gLanguage + ']'); + } + + /** This is where l10n expressions should be processed. + * The plan is to support C-style expressions from the l20n project; + * until then, only two kinds of simple expressions are supported: + * {[ index ]} and {{ arguments }}. + */ + var rv = {}; + for (var prop in data) { + var str = data[prop]; + str = substIndexes(str, args, key, prop); + str = substArguments(str, args); + rv[prop] = str; + } + return rv; + } + + // replace {[macros]} with their values + function substIndexes(str, args, key, prop) { + var reIndex = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)\s*\]\}/; + var reMatch = reIndex.exec(str); + if (!reMatch || !reMatch.length) + return str; + + // an index/macro has been found + // Note: at the moment, only one parameter is supported + var macroName = reMatch[1]; + var paramName = reMatch[2]; + var param; + if (args && paramName in args) { + param = args[paramName]; + } else if (paramName in gL10nData) { + param = gL10nData[paramName]; + } + + // there's no macro parser yet: it has to be defined in gMacros + if (macroName in gMacros) { + var macro = gMacros[macroName]; + str = macro(str, param, key, prop); + } + return str; + } + + // replace {{arguments}} with their values + function substArguments(str, args) { + var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/; + var match = reArgs.exec(str); + while (match) { + if (!match || match.length < 2) + return str; // argument key not found + + var arg = match[1]; + var sub = ''; + if (arg in args) { + sub = args[arg]; + } else if (arg in gL10nData) { + sub = gL10nData[arg][gTextProp]; + } else { + consoleWarn('could not find argument {{' + arg + '}}'); + return str; + } + + str = str.substring(0, match.index) + sub + + str.substr(match.index + match[0].length); + match = reArgs.exec(str); + } + return str; + } + + // translate an HTML element + function translateElement(element) { + var l10n = getL10nAttributes(element); + if (!l10n.id) + return; + + // get the related l10n object + var data = getL10nData(l10n.id, l10n.args); + if (!data) { + consoleWarn('#' + l10n.id + ' missing for [' + gLanguage + ']'); + return; + } + + // translate element (TODO: security checks?) + // for the node content, replace the content of the first child textNode + // and clear other child textNodes + if (data[gTextProp]) { // XXX + if (getChildElementCount(element) === 0) { + element[gTextProp] = data[gTextProp]; + } else { + var children = element.childNodes, + found = false; + for (var i = 0, l = children.length; i < l; i++) { + if (children[i].nodeType === 3 && + /\S/.test(children[i].textContent)) { // XXX + // using nodeValue seems cross-browser + if (found) { + children[i].nodeValue = ''; + } else { + children[i].nodeValue = data[gTextProp]; + found = true; + } + } + } + if (!found) { + consoleWarn('unexpected error, could not translate element content'); + } + } + delete data[gTextProp]; + } + + for (var k in data) { + element[k] = data[k]; + } + } + + // webkit browsers don't currently support 'children' on SVG elements... + function getChildElementCount(element) { + if (element.children) { + return element.children.length; + } + if (typeof element.childElementCount !== 'undefined') { + return element.childElementCount; + } + var count = 0; + for (var i = 0; i < element.childNodes.length; i++) { + count += element.nodeType === 1 ? 1 : 0; + } + return count; + } + + // translate an HTML subtree + function translateFragment(element) { + element = element || document.documentElement; + + // check all translatable children (= w/ a `data-l10n-id' attribute) + var children = getTranslatableChildren(element); + var elementCount = children.length; + for (var i = 0; i < elementCount; i++) { + translateElement(children[i]); + } + + // translate element itself if necessary + translateElement(element); + } + + // Startup & Public API + + function l10nStartup() { + gReadyState = 'interactive'; + consoleLog('loading [' + navigator.language + '] resources, ' + + (gAsyncResourceLoading ? 'asynchronously.' : 'synchronously.')); + + // load the default locale and translate the document if required + if (document.documentElement.lang === navigator.language) { + loadLocale(navigator.language); + } else { + loadLocale(navigator.language, translateFragment); + } + } + + // public API + var l10n = { + start: function() { + if (document.readyState === 'complete' || + document.readyState === 'interactive') { + window.setTimeout(l10nStartup); + } else { + document.addEventListener('DOMContentLoaded', l10nStartup); + } + }, + + // get a localized string + get: function l10n_get(key, args, fallback) { + var data = getL10nData(key, args) || fallback; + if (data) { + return 'textContent' in data ? data.textContent : ''; + } + return '{{' + key + '}}'; + }, + + // get|set the document language and direction + get language() { + return { + // get|set the document language (ISO-639-1) + get code() { return gLanguage; }, + set code(lang) { loadLocale(lang, translateFragment); }, + + // get the direction (ltr|rtl) of the current language + get direction() { + // http://www.w3.org/International/questions/qa-scripts + // Arabic, Hebrew, Farsi, Pashto, Urdu + var rtlList = ['ar', 'he', 'fa', 'ps', 'ur']; + return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr'; + } + }; + }, + + // translate an element or document fragment + translate: translateFragment, + + // get (a clone of) the dictionary for the current locale + get dictionary() { return JSON.parse(JSON.stringify(gL10nData)); }, + + // this can be used to prevent race conditions + get readyState() { return gReadyState; }, + ready: function l10n_ready(callback) { + if (!callback) + return; + + if (gReadyState == 'complete') { + window.setTimeout(callback); + } else { + window.addEventListener('localized', callback); + } + } + }; + + return l10n; +}); |