'use strict'; const marked = require('marked'); function decamelize(str, sep) { sep = sep === undefined ? '-' : sep; return str .replace(/([a-z\d])([A-Z])/g, '$1' + sep + '$2') .replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1' + sep + '$2') .toLowerCase(); } function* range(start=0, end=null, step=1) { if (end == null) { end = start; start = 0; } for (let i = start; i < end; i += step) { yield i; } } function _formatUnits(number, base, suffixes, callback) { if (!number && number !== 0) { return NaN; } number *= 1.0; let suffix = suffixes.shift(); while (number >= base && suffixes.length > 0) { suffix = suffixes.shift(); number /= base; } if (callback === undefined) { callback = (number, suffix) => { return suffix ? number.toFixed(1) + suffix : number; }; } return callback(number, suffix); } function formatFileSize(fileSize) { return _formatUnits( fileSize, 1024, ['B', 'K', 'M', 'G'], (number, suffix) => { const decimalPlaces = number < 20 && suffix !== 'B' ? 1 : 0; return number.toFixed(decimalPlaces) + suffix; }); } function formatRelativeTime(timeString) { if (!timeString) { return 'never'; } const then = Date.parse(timeString); const now = Date.now(); const difference = Math.abs(now - then) / 1000.0; const future = now < then; const descriptions = [ [60, 'a few seconds', null], [60 * 2, 'a minute', null], [60 * 60, '% minutes', 60], [60 * 60 * 2, 'an hour', null], [60 * 60 * 24, '% hours', 60 * 60], [60 * 60 * 24 * 2, 'a day', null], [60 * 60 * 24 * 30.42, '% days', 60 * 60 * 24], [60 * 60 * 24 * 30.42 * 2, 'a month', null], [60 * 60 * 24 * 30.42 * 12, '% months', 60 * 60 * 24 * 30.42], [60 * 60 * 24 * 30.42 * 12 * 2, 'a year', null], [8640000000000000 /*max*/, '% years', 60 * 60 * 24 * 30.42 * 12], ]; let text = null; for (let kv of descriptions) { const multiplier = kv[0]; const template = kv[1]; const divider = kv[2]; if (difference < multiplier) { text = template.replace(/%/, Math.round(difference / divider)); break; } } if (text === 'a day') { return future ? 'tomorrow' : 'yesterday'; } return future ? 'in ' + text : text + ' ago'; } function formatMarkdown(text) { const renderer = new marked.Renderer(); const options = { renderer: renderer, breaks: true, sanitize: true, smartypants: true, }; const sjis = []; const preDecorator = text => { text = text.replace( /\[sjis\]((?:[^\[]|\[(?!\/?sjis\]))+)\[\/sjis\]/ig, (match, capture) => { var ret = '%%%SJIS' + sjis.length; sjis.push(capture); return ret; }); //prevent ^#... from being treated as headers, due to tag permalinks text = text.replace(/^#/g, '%%%#'); //fix \ before ~ being stripped away text = text.replace(/\\~/g, '%%%T'); //post, user and tags premalinks text = text.replace( /(^|^\(|(?:[^\]])\(|[\s<>\[\]\)])([+#@][a-zA-Z0-9_-]+)/g, '$1[$2]($2)'); text = text.replace(/\]\(@(\d+)\)/g, '](/post/$1)'); text = text.replace(/\]\(\+([a-zA-Z0-9_-]+)\)/g, '](/user/$1)'); text = text.replace(/\]\(#([a-zA-Z0-9_-]+)\)/g, '](/posts/query=$1)'); return text; }; const postDecorator = text => { //restore fixes text = text.replace(/%%%T/g, '\\~'); text = text.replace(/%%%#/g, '#'); text = text.replace( /(?:

)?%%%SJIS(\d+)(?:<\/p>)?/, (match, capture) => { return '

' + sjis[capture] + '
'; }); //search permalinks text = text.replace( /\[search\]((?:[^\[]|\[(?!\/?search\]))+)\[\/search\]/ig, '$1'); //spoilers text = text.replace( /\[spoiler\]((?:[^\[]|\[(?!\/?spoiler\]))+)\[\/spoiler\]/ig, '$1'); //[small] text = text.replace( /\[small\]((?:[^\[]|\[(?!\/?small\]))+)\[\/small\]/ig, '$1'); //strike-through text = text.replace(/(^|[^\\])(~~|~)([^~]+)\2/g, '$1$3'); text = text.replace(/\\~/g, '~'); return text; }; return postDecorator(marked(preDecorator(text), options)); } function formatUrlParameters(dict) { let result = []; for (let key of Object.keys(dict)) { const value = dict[key]; if (key === 'parameters') { continue; } if (value) { result.push(`${key}=${value}`); } } return result.join(';'); } function splitByWhitespace(str) { return str.split(/\s+/).filter(s => s); } function parseUrlParameters(query) { let result = {}; for (let word of (query || '').split(/;/)) { const [key, value] = word.split(/=/, 2); result[key] = value; } result.query = result.query || ''; result.page = parseInt(result.page || '1'); return result; } function parseUrlParametersRoute(ctx, next) { // ctx.parameters = {"user":...,"action":...} from /users/:user/:action // ctx.parameters.parameters = value of :parameters as per /url/:parameters Object.assign( ctx.parameters, parseUrlParameters(ctx.parameters.parameters)); next(); } function unindent(callSite, ...args) { function format(str) { let size = -1; return str.replace(/\n(\s+)/g, (m, m1) => { if (size < 0) { size = m1.replace(/\t/g, ' ').length; } return '\n' + m1.slice(Math.min(m1.length, size)); }); } if (typeof callSite === 'string') { return format(callSite); } if (typeof callSite === 'function') { return (...args) => format(callSite(...args)); } let output = callSite .slice(0, args.length + 1) .map((text, i) => (i === 0 ? '' : args[i - 1]) + text) .join(''); return format(output); } function enableExitConfirmation() { window.onbeforeunload = e => { return 'Are you sure you want to leave? ' + 'Data you have entered may not be saved.'; }; } function disableExitConfirmation() { window.onbeforeunload = null; } function confirmPageExit() { if (!window.onbeforeunload) { return true; } if (window.confirm(window.onbeforeunload())) { disableExitConfirmation(); return true; } } function makeCssName(text, suffix) { return suffix + '-' + text.replace(/[^a-z0-9]/g, '_'); } function escapeHtml(unsafe) { return unsafe.toString() .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function arraysDiffer(source1, source2) { source1 = [...source1]; source2 = [...source2]; return ( source1.filter(value => !source2.includes(value)).length > 0 || source2.filter(value => !source1.includes(value)).length > 0); } module.exports = { range: range, formatUrlParameters: formatUrlParameters, parseUrlParameters: parseUrlParameters, parseUrlParametersRoute: parseUrlParametersRoute, formatRelativeTime: formatRelativeTime, formatFileSize: formatFileSize, formatMarkdown: formatMarkdown, unindent: unindent, enableExitConfirmation: enableExitConfirmation, disableExitConfirmation: disableExitConfirmation, confirmPageExit: confirmPageExit, escapeHtml: escapeHtml, makeCssName: makeCssName, splitByWhitespace: splitByWhitespace, arraysDiffer: arraysDiffer, decamelize: decamelize, };