mirror of
https://github.com/flarum/core.git
synced 2025-08-03 15:07:53 +02:00
Massive JavaScript cleanup
- Use JSX for templates - Docblock/comment everything - Mostly passes ESLint (still some work to do) - Lots of renaming, refactoring, etc. CSS hasn't been updated yet.
This commit is contained in:
61
js/lib/utils/ItemList.js
Normal file
61
js/lib/utils/ItemList.js
Normal file
@@ -0,0 +1,61 @@
|
||||
class Item {
|
||||
constructor(content, priority) {
|
||||
this.content = content;
|
||||
this.priority = priority;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `ItemList` class collects items and then arranges them into an array
|
||||
* by priority.
|
||||
*/
|
||||
export default class ItemList {
|
||||
/**
|
||||
* Add an item to the list.
|
||||
*
|
||||
* @param {String} key A unique key for the item.
|
||||
* @param {*} content The item's content.
|
||||
* @param {Integer} [priority] The priority of the item. Items with a higher
|
||||
* priority will be positioned before items with a lower priority.
|
||||
* @public
|
||||
*/
|
||||
add(key, content, priority) {
|
||||
this[key] = new Item(content, priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge another list's items into this one.
|
||||
*
|
||||
* @param {ItemList} items
|
||||
* @public
|
||||
*/
|
||||
merge(items) {
|
||||
for (const i in items) {
|
||||
if (items.hasOwnProperty(i) && items[i] instanceof Item) {
|
||||
this[i] = items[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the list into an array of item content arranged by priority. Each
|
||||
* item's content will be assigned an `itemName` property equal to the item's
|
||||
* unique key.
|
||||
*
|
||||
* @return {Array}
|
||||
* @public
|
||||
*/
|
||||
toArray() {
|
||||
const items = [];
|
||||
|
||||
for (const i in this) {
|
||||
if (this.hasOwnProperty(i) && this[i] instanceof Item) {
|
||||
this[i].content.itemName = i;
|
||||
items.push(this[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return items.sort((a, b) => b.priority - a.priority).map(item => item.content);
|
||||
}
|
||||
}
|
||||
|
73
js/lib/utils/ScrollListener.js
Normal file
73
js/lib/utils/ScrollListener.js
Normal file
@@ -0,0 +1,73 @@
|
||||
const scroll = window.requestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
(callback => window.setTimeout(callback, 1000 / 60));
|
||||
|
||||
/**
|
||||
* The `ScrollListener` class sets up a listener that handles window scroll
|
||||
* events.
|
||||
*/
|
||||
export default class ScrollListener {
|
||||
/**
|
||||
* @param {Function} callback The callback to run when the scroll position
|
||||
* changes.
|
||||
* @public
|
||||
*/
|
||||
constructor(callback) {
|
||||
this.callback = callback;
|
||||
this.lastTop = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* On each animation frame, as long as the listener is active, run the
|
||||
* `update` method.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
loop() {
|
||||
if (!this.active) return;
|
||||
|
||||
this.update();
|
||||
|
||||
scroll(this.loop.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the scroll position has changed; if it has, run the handler.
|
||||
*
|
||||
* @param {Boolean} [force=false] Whether or not to force the handler to be
|
||||
* run, even if the scroll position hasn't changed.
|
||||
* @public
|
||||
*/
|
||||
update(force) {
|
||||
const top = window.pageYOffset;
|
||||
|
||||
if (this.lastTop !== top || force) {
|
||||
this.callback(top);
|
||||
this.lastTop = top;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to and handling the window's scroll position.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
start() {
|
||||
if (!this.active) {
|
||||
this.active = true;
|
||||
this.loop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop listening to and handling the window's scroll position.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
stop() {
|
||||
this.active = false;
|
||||
}
|
||||
}
|
69
js/lib/utils/SubtreeRetainer.js
Normal file
69
js/lib/utils/SubtreeRetainer.js
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* The `SubtreeRetainer` class represents a Mithril virtual DOM subtree. It
|
||||
* keeps track of a number of pieces of data, allowing the subtree to be
|
||||
* retained if none of them have changed.
|
||||
*
|
||||
* @example
|
||||
* // constructor
|
||||
* this.subtree = new SubtreeRetainer(
|
||||
* () => this.props.post.freshness,
|
||||
* () => this.showing
|
||||
* );
|
||||
* this.subtree.check(() => this.props.user.freshness);
|
||||
*
|
||||
* // view
|
||||
* this.subtree.retain() || 'expensive expression'
|
||||
*
|
||||
* @see https://lhorie.github.io/mithril/mithril.html#persisting-dom-elements-across-route-changes
|
||||
*/
|
||||
export default class SubtreeRetainer {
|
||||
/**
|
||||
* @param {...callbacks} callbacks Functions returning data to keep track of.
|
||||
*/
|
||||
constructor(...callbacks) {
|
||||
this.invalidate();
|
||||
this.callbacks = callbacks;
|
||||
this.data = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a virtual DOM directive that will retain a subtree if no data has
|
||||
* changed since the last check.
|
||||
*
|
||||
* @return {Object|false}
|
||||
* @public
|
||||
*/
|
||||
retain() {
|
||||
let needsRebuild = false;
|
||||
|
||||
this.callbacks.forEach((callback, i) => {
|
||||
const result = callback();
|
||||
|
||||
if (result !== this.data[i]) {
|
||||
this.data[i] = result;
|
||||
needsRebuild = true;
|
||||
}
|
||||
});
|
||||
|
||||
return needsRebuild ? false : {subtree: 'retain'};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add another callback to be checked.
|
||||
*
|
||||
* @param {...Function} callbacks
|
||||
* @public
|
||||
*/
|
||||
check(...callbacks) {
|
||||
this.callbacks = this.callbacks.concat(callbacks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the subtree, forcing it to be rerendered.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
invalidate() {
|
||||
this.data = {};
|
||||
}
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
export default function(number) {
|
||||
if (number >= 1000000) {
|
||||
return Math.floor(number / 1000000)+'M';
|
||||
} else if (number >= 1000) {
|
||||
return Math.floor(number / 1000)+'K';
|
||||
} else {
|
||||
return number.toString();
|
||||
}
|
||||
}
|
20
js/lib/utils/abbreviateNumber.js
Normal file
20
js/lib/utils/abbreviateNumber.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* The `abbreviateNumber` utility converts a number to a shorter localized form.
|
||||
*
|
||||
* @example
|
||||
* abbreviateNumber(1234);
|
||||
* // "1.2K"
|
||||
*
|
||||
* @param {Integer} number
|
||||
* @return {String}
|
||||
*/
|
||||
export default function abbreviateNumber(number) {
|
||||
// TODO: translation
|
||||
if (number >= 1000000) {
|
||||
return Math.floor(number / 1000000) + 'M';
|
||||
} else if (number >= 1000) {
|
||||
return Math.floor(number / 1000) + 'K';
|
||||
} else {
|
||||
return number.toString();
|
||||
}
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
export default function anchorScroll(element, callback) {
|
||||
var scrollAnchor = $(element).offset().top - $(window).scrollTop();
|
||||
|
||||
callback();
|
||||
|
||||
$(window).scrollTop($(element).offset().top - scrollAnchor);
|
||||
}
|
22
js/lib/utils/anchorScroll.js
Normal file
22
js/lib/utils/anchorScroll.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* The `anchorScroll` utility saves the scroll position relative to an element,
|
||||
* and then restores it after a callback has been run.
|
||||
*
|
||||
* This is useful if a redraw will change the page's content above the viewport.
|
||||
* Normally doing this will result in the content in the viewport being pushed
|
||||
* down or pulled up. By wrapping the redraw with this utility, the scroll
|
||||
* position can be anchor to an element that is in or below the viewport, so
|
||||
* the content in the viewport will stay the same.
|
||||
*
|
||||
* @param {DOMElement} element The element to anchor the scroll position to.
|
||||
* @param {Function} callback The callback to run that will change page content.
|
||||
*/
|
||||
export default function anchorScroll(element, callback) {
|
||||
const $element = $(element);
|
||||
const $window = $(window);
|
||||
const relativeScroll = $element.offset().top - $window.scrollTop();
|
||||
|
||||
callback();
|
||||
|
||||
$window.scrollTop($element.offset().top - relativeScroll);
|
||||
}
|
@@ -1,75 +0,0 @@
|
||||
import ItemList from 'flarum/utils/item-list';
|
||||
import Alert from 'flarum/components/alert';
|
||||
import ServerError from 'flarum/utils/server-error';
|
||||
import Translator from 'flarum/utils/translator';
|
||||
|
||||
class App {
|
||||
constructor() {
|
||||
this.initializers = new ItemList();
|
||||
this.translator = new Translator();
|
||||
this.cache = {};
|
||||
this.serverError = null;
|
||||
}
|
||||
|
||||
boot() {
|
||||
this.initializers.toArray().forEach((initializer) => initializer(this));
|
||||
}
|
||||
|
||||
preloadedDocument() {
|
||||
if (app.preload.document) {
|
||||
const results = app.store.pushPayload(app.preload.document);
|
||||
app.preload.document = null;
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
setTitle(title) {
|
||||
document.title = (title ? title+' - ' : '')+this.forum.attribute('title');
|
||||
}
|
||||
|
||||
request(options) {
|
||||
var extract = options.extract;
|
||||
options.extract = function(xhr, xhrOptions) {
|
||||
if (xhr.status === 500) {
|
||||
throw new ServerError;
|
||||
}
|
||||
return extract ? extract(xhr.responseText) : (xhr.responseText.length === 0 ? null : xhr.responseText);
|
||||
};
|
||||
|
||||
return m.request(options).then(response => {
|
||||
this.alerts.dismiss(this.serverError);
|
||||
return response;
|
||||
}, response => {
|
||||
this.alerts.dismiss(this.serverError);
|
||||
if (response instanceof ServerError) {
|
||||
this.alerts.show(this.serverError = new Alert({ type: 'warning', message: 'Oops! Something went wrong on the server. Please try again.' }))
|
||||
}
|
||||
throw response;
|
||||
});
|
||||
}
|
||||
|
||||
handleApiErrors(response) {
|
||||
this.alerts.clear();
|
||||
|
||||
response.errors.forEach(error =>
|
||||
this.alerts.show(new Alert({ type: 'warning', message: error.detail }))
|
||||
);
|
||||
}
|
||||
|
||||
route(name, params) {
|
||||
var url = this.routes[name][0].replace(/:([^\/]+)/g, function(m, t) {
|
||||
var value = params[t];
|
||||
delete params[t];
|
||||
return value;
|
||||
});
|
||||
var queryString = m.route.buildQueryString(params);
|
||||
return url+(queryString ? '?'+queryString : '');
|
||||
}
|
||||
|
||||
translate(key, input) {
|
||||
return this.translator.translate(key, input);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
@@ -1,12 +0,0 @@
|
||||
export default function classList(classes) {
|
||||
var classNames = [];
|
||||
for (var i in classes) {
|
||||
var value = classes[i];
|
||||
if (value === true) {
|
||||
classNames.push(i);
|
||||
} else if (value) {
|
||||
classNames.push(value);
|
||||
}
|
||||
}
|
||||
return classNames.join(' ');
|
||||
}
|
20
js/lib/utils/classList.js
Normal file
20
js/lib/utils/classList.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* The `classList` utility creates a list of class names by joining an object's
|
||||
* keys, but only for values which are truthy.
|
||||
*
|
||||
* @example
|
||||
* classList({ foo: true, bar: false, qux: 'qaz' });
|
||||
* // "foo qux"
|
||||
*
|
||||
* @param {Object} classes
|
||||
* @return {String}
|
||||
*/
|
||||
export default function classList(classes) {
|
||||
const classNames = [];
|
||||
|
||||
for (const i in classes) {
|
||||
if (classes[i]) classNames.push(i);
|
||||
}
|
||||
|
||||
return classNames.join(' ');
|
||||
}
|
@@ -1,22 +1,37 @@
|
||||
export default function computed() {
|
||||
var args = [].slice.apply(arguments);
|
||||
var keys = args.slice(0, -1);
|
||||
var compute = args.slice(-1)[0];
|
||||
/**
|
||||
* The `computed` utility creates a function that will cache its output until
|
||||
* any of the dependent values are dirty.
|
||||
*
|
||||
* @param {...String} dependentKeys The keys of the dependent values.
|
||||
* @param {function} compute The function which computes the value using the
|
||||
* dependent values.
|
||||
* @return {}
|
||||
*/
|
||||
export default function computed(...dependentKeys) {
|
||||
const keys = dependentKeys.slice(0, -1);
|
||||
const compute = dependentKeys.slice(-1)[0];
|
||||
|
||||
const dependentValues = {};
|
||||
let computedValue;
|
||||
|
||||
var values = {};
|
||||
var computed;
|
||||
return function() {
|
||||
var recompute = false;
|
||||
keys.forEach(function(key) {
|
||||
var value = typeof this[key] === 'function' ? this[key]() : this[key];
|
||||
if (values[key] !== value) {
|
||||
let recompute = false;
|
||||
|
||||
// Read all of the dependent values. If any of them have changed since last
|
||||
// time, then we'll want to recompute our output.
|
||||
keys.forEach(key => {
|
||||
const value = typeof this[key] === 'function' ? this[key]() : this[key];
|
||||
|
||||
if (dependentValues[key] !== value) {
|
||||
recompute = true;
|
||||
values[key] = value;
|
||||
dependentValues[key] = value;
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
if (recompute) {
|
||||
computed = compute.apply(this, keys.map((key) => values[key]));
|
||||
computedValue = compute.apply(this, keys.map(key => dependentValues[key]));
|
||||
}
|
||||
return computed;
|
||||
}
|
||||
};
|
||||
|
||||
return computedValue;
|
||||
};
|
||||
}
|
||||
|
@@ -1,45 +1,79 @@
|
||||
/**
|
||||
* The `evented` mixin provides methods allowing an object to trigger events,
|
||||
* running externally registered event handlers.
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Arrays of registered event handlers, grouped by the event name.
|
||||
*
|
||||
* @type {Object}
|
||||
* @protected
|
||||
*/
|
||||
handlers: null,
|
||||
|
||||
/**
|
||||
|
||||
* Get all of the registered handlers for an event.
|
||||
*
|
||||
* @param {String} event The name of the event.
|
||||
* @return {Array}
|
||||
* @protected
|
||||
*/
|
||||
getHandlers(event) {
|
||||
this.handlers = this.handlers || {};
|
||||
return this.handlers[event] = this.handlers[event] || [];
|
||||
|
||||
this.handlers[event] = this.handlers[event] || [];
|
||||
|
||||
return this.handlers[event];
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
* Trigger an event.
|
||||
*
|
||||
* @param {String} event The name of the event.
|
||||
* @param {...*} args Arguments to pass to event handlers.
|
||||
* @public
|
||||
*/
|
||||
trigger(event, ...args) {
|
||||
this.getHandlers(event).forEach((handler) => handler.apply(this, args));
|
||||
this.getHandlers(event).forEach(handler => handler.apply(this, args));
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
* Register an event handler.
|
||||
*
|
||||
* @param {String} event The name of the event.
|
||||
* @param {function} handler The function to handle the event.
|
||||
*/
|
||||
on(event, handler) {
|
||||
this.getHandlers(event).push(handler);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
* Register an event handler so that it will run only once, and then
|
||||
* unregister itself.
|
||||
*
|
||||
* @param {String} event The name of the event.
|
||||
* @param {function} handler The function to handle the event.
|
||||
*/
|
||||
one(event, handler) {
|
||||
var wrapper = function() {
|
||||
const wrapper = function() {
|
||||
handler.apply(this, arguments);
|
||||
|
||||
this.off(event, wrapper);
|
||||
};
|
||||
|
||||
this.getHandlers(event).push(wrapper);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
* Unregister an event handler.
|
||||
*
|
||||
* @param {String} event The name of the event.
|
||||
* @param {function} handler The function that handles the event.
|
||||
*/
|
||||
off(event, handler) {
|
||||
var handlers = this.getHandlers(event);
|
||||
var index = handlers.indexOf(handler);
|
||||
const handlers = this.getHandlers(event);
|
||||
const index = handlers.indexOf(handler);
|
||||
|
||||
if (index !== -1) {
|
||||
handlers.splice(index, 1);
|
||||
}
|
||||
|
15
js/lib/utils/extract.js
Normal file
15
js/lib/utils/extract.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* The `extract` utility deletes a property from an object and returns its
|
||||
* value.
|
||||
*
|
||||
* @param {Object} object The object that owns the property
|
||||
* @param {String} property The name of the property to extract
|
||||
* @return {*} The value of the property
|
||||
*/
|
||||
export default function extract(object, property) {
|
||||
const value = object[property];
|
||||
|
||||
delete object[property];
|
||||
|
||||
return value;
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
export default function(number) {
|
||||
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
}
|
14
js/lib/utils/formatNumber.js
Normal file
14
js/lib/utils/formatNumber.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* The `formatNumber` utility localizes a number into a string with the
|
||||
* appropriate punctuation.
|
||||
*
|
||||
* @example
|
||||
* formatNumber(1234);
|
||||
* // 1,234
|
||||
*
|
||||
* @param {Number} number
|
||||
* @return {String}
|
||||
*/
|
||||
export default function formatNumber(number) {
|
||||
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
export default function humanTime(time) {
|
||||
var m = moment(time);
|
||||
|
||||
var minute = 6e4;
|
||||
var hour = 36e5;
|
||||
var day = 864e5;
|
||||
var ago = null;
|
||||
|
||||
var diff = m.diff(moment());
|
||||
|
||||
if (diff < -30 * day) {
|
||||
if (m.year() === moment().year()) {
|
||||
ago = m.format('D MMM');
|
||||
} else {
|
||||
ago = m.format('MMM \'YY');
|
||||
}
|
||||
} else {
|
||||
ago = m.fromNow();
|
||||
}
|
||||
|
||||
return ago;
|
||||
};
|
28
js/lib/utils/humanTime.js
Normal file
28
js/lib/utils/humanTime.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* The `humanTime` utility converts a date to a localized, human-readable time-
|
||||
* ago string.
|
||||
*
|
||||
* @param {Date} time
|
||||
* @return {String}
|
||||
*/
|
||||
export default function humanTime(time) {
|
||||
const m = moment(time);
|
||||
|
||||
const day = 864e5;
|
||||
const diff = m.diff(moment());
|
||||
let ago = null;
|
||||
|
||||
// If this date was more than a month ago, we'll show the name of the month
|
||||
// in the string. If it wasn't this year, we'll show the year as well.
|
||||
if (diff < -30 * day) {
|
||||
if (m.year() === moment().year()) {
|
||||
ago = m.format('D MMM');
|
||||
} else {
|
||||
ago = m.format('MMM \'YY');
|
||||
}
|
||||
} else {
|
||||
ago = m.fromNow();
|
||||
}
|
||||
|
||||
return ago;
|
||||
};
|
@@ -1,70 +0,0 @@
|
||||
export class Item {
|
||||
constructor(content, position) {
|
||||
this.content = content;
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
export default class ItemList {
|
||||
add(key, content, position) {
|
||||
this[key] = new Item(content, position);
|
||||
}
|
||||
|
||||
merge(items) {
|
||||
for (var i in items) {
|
||||
if (items.hasOwnProperty(i) && items[i] instanceof Item) {
|
||||
this[i] = items[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toArray() {
|
||||
var items = [];
|
||||
for (var i in this) {
|
||||
if (this.hasOwnProperty(i) && this[i] instanceof Item) {
|
||||
this[i].content.itemName = i;
|
||||
items.push(this[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var array = [];
|
||||
|
||||
var addItems = function(method, position) {
|
||||
items = items.filter(function(item) {
|
||||
if ((position && item.position && item.position[position]) || (!position && !item.position)) {
|
||||
array[method](item);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
};
|
||||
addItems('unshift', 'first');
|
||||
addItems('push', false);
|
||||
addItems('push', 'last');
|
||||
|
||||
items.forEach(item => {
|
||||
var key = item.position.before || item.position.after;
|
||||
var type = item.position.before ? 'before' : 'after';
|
||||
// TODO: Allow both before and after to be specified, and multiple keys to
|
||||
// be specified for each.
|
||||
// e.g. {before: ['foo', 'bar'], after: ['qux', 'qaz']}
|
||||
// This way extensions can make sure they are positioned where
|
||||
// they want to be relative to other extensions.
|
||||
// Alternatively, it might be better to just have a numbered priority
|
||||
// system, so extensions don't have to make awkward references to each other.
|
||||
if (key) {
|
||||
var index = array.indexOf(this[key]);
|
||||
if (index === -1) {
|
||||
array.push(item);
|
||||
} else {
|
||||
array.splice(index + (type === 'after' ? 1 : 0), 0, item);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
array = array.map(item => item.content);
|
||||
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +0,0 @@
|
||||
export default function mapRoutes(routes) {
|
||||
var map = {};
|
||||
for (var r in routes) {
|
||||
routes[r][1].props.routeName = r;
|
||||
map[routes[r][0]] = routes[r][1];
|
||||
}
|
||||
return map;
|
||||
}
|
21
js/lib/utils/mapRoutes.js
Normal file
21
js/lib/utils/mapRoutes.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* The `mapRoutes` utility converts a map of named application routes into a
|
||||
* format that can be understood by Mithril.
|
||||
*
|
||||
* @see https://lhorie.github.io/mithril/mithril.route.html#defining-routes
|
||||
* @param {Object} routes
|
||||
* @return {Object}
|
||||
*/
|
||||
export default function mapRoutes(routes) {
|
||||
const map = {};
|
||||
|
||||
for (const key in routes) {
|
||||
const route = routes[key];
|
||||
|
||||
if (route.component) route.component.props.routeName = key;
|
||||
|
||||
map[route.path] = route.component;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
@@ -1,11 +1,20 @@
|
||||
/**
|
||||
* The `mixin` utility assigns the properties of a set of 'mixin' objects to
|
||||
* the prototype of a parent object.
|
||||
*
|
||||
* @example
|
||||
* class MyClass extends mixin(ExtistingClass, evented, etc) {}
|
||||
*
|
||||
* @param {Class} Parent The class to extend the new class from.
|
||||
* @param {...Object} mixins The objects to mix in.
|
||||
* @return {Class} A new class that extends Parent and contains the mixins.
|
||||
*/
|
||||
export default function mixin(Parent, ...mixins) {
|
||||
class Mixed extends Parent {}
|
||||
for (var i in mixins) {
|
||||
var keys = Object.keys(mixins[i]);
|
||||
for (var j in keys) {
|
||||
var prop = keys[j];
|
||||
Mixed.prototype[prop] = mixins[i][prop];
|
||||
}
|
||||
}
|
||||
|
||||
mixins.forEach(object => {
|
||||
Object.assign(Mixed.prototype, object);
|
||||
});
|
||||
|
||||
return Mixed;
|
||||
}
|
||||
|
@@ -1,43 +0,0 @@
|
||||
var scroll = window.requestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
function(callback) { window.setTimeout(callback, 1000/60) };
|
||||
|
||||
export default class ScrollListener {
|
||||
constructor(callback) {
|
||||
this.callback = callback;
|
||||
this.lastTop = -1;
|
||||
}
|
||||
|
||||
loop() {
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.update();
|
||||
|
||||
scroll(this.loop.bind(this));
|
||||
}
|
||||
|
||||
update(force) {
|
||||
var top = window.pageYOffset;
|
||||
|
||||
if (this.lastTop !== top || force) {
|
||||
this.callback(top);
|
||||
this.lastTop = top;
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
start() {
|
||||
if (!this.active) {
|
||||
this.active = true;
|
||||
this.loop();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,2 +0,0 @@
|
||||
export default class ServerError {
|
||||
}
|
@@ -1,34 +0,0 @@
|
||||
function hsvToRgb(h, s, v) {
|
||||
var r, g, b, i, f, p, q, t;
|
||||
if (h && s === undefined && v === undefined) {
|
||||
s = h.s; v = h.v; h = h.h;
|
||||
}
|
||||
i = Math.floor(h * 6);
|
||||
f = h * 6 - i;
|
||||
p = v * (1 - s);
|
||||
q = v * (1 - f * s);
|
||||
t = v * (1 - (1 - f) * s);
|
||||
switch (i % 6) {
|
||||
case 0: r = v; g = t; b = p; break;
|
||||
case 1: r = q; g = v; b = p; break;
|
||||
case 2: r = p; g = v; b = t; break;
|
||||
case 3: r = p; g = q; b = v; break;
|
||||
case 4: r = t; g = p; b = v; break;
|
||||
case 5: r = v; g = p; b = q; break;
|
||||
}
|
||||
return {
|
||||
r: Math.floor(r * 255),
|
||||
g: Math.floor(g * 255),
|
||||
b: Math.floor(b * 255)
|
||||
};
|
||||
}
|
||||
|
||||
export default function stringToColor(string) {
|
||||
var num = 0;
|
||||
for (var i = 0; i < string.length; i++) {
|
||||
num += string.charCodeAt(i);
|
||||
}
|
||||
var hue = num % 360;
|
||||
var rgb = hsvToRgb(hue / 360, 0.3, 0.9);
|
||||
return ''+rgb.r.toString(16)+rgb.g.toString(16)+rgb.b.toString(16);
|
||||
};
|
@@ -1,5 +1,38 @@
|
||||
export function dasherize(string) {
|
||||
return string.replace(/([A-Z])/g, function ($1) {
|
||||
return '-' + $1.toLowerCase();
|
||||
});
|
||||
/**
|
||||
* Truncate a string to the given length, appending ellipses if necessary.
|
||||
*
|
||||
* @param {String} string
|
||||
* @param {Number} length
|
||||
* @param {Number} [start=0]
|
||||
* @return {String}
|
||||
*/
|
||||
export function truncate(string, length, start = 0) {
|
||||
return (start > 0 ? '...' : '') +
|
||||
string.substring(start, start + length) +
|
||||
(string.length > start + length ? '...' : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a slug out of the given string. Non-alphanumeric characters are
|
||||
* converted to hyphens.
|
||||
*
|
||||
* @param {String} string
|
||||
* @return {String}
|
||||
*/
|
||||
export function slug(string) {
|
||||
return string.toLowerCase()
|
||||
.replace(/[^a-z0-9]/gi, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/-$|^-/g, '') || '-';
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip HTML tags and quotes out of the given string, replacing them with
|
||||
* meaningful punctuation.
|
||||
*
|
||||
* @param {String} string
|
||||
* @return {String}
|
||||
*/
|
||||
export function getPlainContent(string) {
|
||||
return $('<div/>').html(string.replace(/(<\/p>|<br>)/g, '$1 ')).text();
|
||||
}
|
||||
|
45
js/lib/utils/stringToColor.js
Normal file
45
js/lib/utils/stringToColor.js
Normal file
@@ -0,0 +1,45 @@
|
||||
function hsvToRgb(h, s, v) {
|
||||
let r;
|
||||
let g;
|
||||
let b;
|
||||
|
||||
const i = Math.floor(h * 6);
|
||||
const f = h * 6 - i;
|
||||
const p = v * (1 - s);
|
||||
const q = v * (1 - f * s);
|
||||
const t = v * (1 - (1 - f) * s);
|
||||
|
||||
switch (i % 6) {
|
||||
case 0: r = v; g = t; b = p; break;
|
||||
case 1: r = q; g = v; b = p; break;
|
||||
case 2: r = p; g = v; b = t; break;
|
||||
case 3: r = p; g = q; b = v; break;
|
||||
case 4: r = t; g = p; b = v; break;
|
||||
case 5: r = v; g = p; b = q; break;
|
||||
}
|
||||
|
||||
return {
|
||||
r: Math.floor(r * 255),
|
||||
g: Math.floor(g * 255),
|
||||
b: Math.floor(b * 255)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given string to a unique color.
|
||||
*
|
||||
* @param {String} string
|
||||
* @return {String}
|
||||
*/
|
||||
export default function stringToColor(string) {
|
||||
let num = 0;
|
||||
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
num += string.charCodeAt(i);
|
||||
}
|
||||
|
||||
const hue = num % 360;
|
||||
const rgb = hsvToRgb(hue / 360, 0.3, 0.9);
|
||||
|
||||
return '' + rgb.r.toString(16) + rgb.g.toString(16) + rgb.b.toString(16);
|
||||
};
|
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
// constructor
|
||||
this.subtree = new SubtreeRetainer(
|
||||
() => this.props.post.freshness,
|
||||
() => this.showing
|
||||
);
|
||||
this.subtree.check(() => this.props.user.freshness);
|
||||
|
||||
// view
|
||||
this.subtree.retain() || 'expensive expression'
|
||||
*/
|
||||
export default class SubtreeRetainer {
|
||||
constructor() {
|
||||
this.invalidate();
|
||||
this.callbacks = [].slice.call(arguments);
|
||||
this.old = {};
|
||||
}
|
||||
|
||||
retain() {
|
||||
var needsRebuild = false;
|
||||
this.callbacks.forEach((callback, i) => {
|
||||
var result = callback();
|
||||
if (result !== this.old[i]) {
|
||||
this.old[i] = result;
|
||||
needsRebuild = true;
|
||||
}
|
||||
});
|
||||
return needsRebuild ? false : {subtree: 'retain'};
|
||||
}
|
||||
|
||||
check() {
|
||||
this.callbacks = this.callbacks.concat([].slice.call(arguments));
|
||||
}
|
||||
|
||||
invalidate() {
|
||||
this.old = {};
|
||||
}
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
export default class Translator {
|
||||
constructor() {
|
||||
this.translations = {};
|
||||
}
|
||||
|
||||
plural(count) {
|
||||
return count == 1 ? 'one' : 'other';
|
||||
}
|
||||
|
||||
translate(key, input) {
|
||||
var parts = key.split('.');
|
||||
var translation = this.translations;
|
||||
|
||||
parts.forEach(function(part) {
|
||||
translation = translation && translation[part];
|
||||
});
|
||||
|
||||
if (typeof translation === 'object' && typeof input.count !== 'undefined') {
|
||||
translation = translation[this.plural(input.count)];
|
||||
}
|
||||
|
||||
if (typeof translation === 'string') {
|
||||
for (var i in input) {
|
||||
translation = translation.replace(new RegExp('{'+i+'}', 'gi'), input[i]);
|
||||
}
|
||||
|
||||
return translation;
|
||||
} else {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
export default function truncate(string = '', length, start = 0) {
|
||||
return (start > 0 ? '...' : '') +
|
||||
string.substring(start, start + length) +
|
||||
(string.length > start + length ? '...' : '');
|
||||
}
|
Reference in New Issue
Block a user