- commit
- 96d8af4e0a1b7b1aca33f9da8ec3e5d0da82a5b1
- parent
- c5cc3d69c33624ef0bafb7c632227aadaa532e7d
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2023-07-01 19:19
refactor js
Diffstat
| M | plutopluto/index.html | 31 | ++++++++++++++----------------- |
| D | plutopluto/static/jqlite.js | 63 | ------------------------------------------------------------ |
| D | plutopluto/static/mustache.js | 578 | ------------------------------------------------------------ |
| M | plutopluto/static/plutopluto.js | 219 | +++++++++++++++++++++++++++++++------------------------------ |
4 files changed, 126 insertions, 765 deletions
diff --git a/plutopluto/index.html b/plutopluto/index.html
@@ -6,27 +6,24 @@ 6 6 <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src *"> 7 7 <title>plutopluto</title> 8 8 <link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico"/>9 -1 <script src="/static/jqlite.js" type="text/javascript" ></script>10 -1 <script src="/static/mustache.js" type="text/javascript" ></script>11 -1 <script src="/static/plutopluto.js" type="text/javascript" ></script>12 9 <link rel="stylesheet" href="/static/style.css" type="text/css" /> 13 10 </head> 14 11 15 12 <body>16 -1 <ul id="stream" class="posts">17 -1 <li class="post">18 -1 <article>19 -1 <header>20 -1 <a class="source u-bold" href="{{link}}" title="{{title}}">{{source}}</a>21 -1 <span class="meta">22 -1 · Posted on <a href="{{link}}">{{#formatDate}}{{dt}}{{/formatDate}}</a>23 -1 </span>24 -1 </header>25 -1 {{{content}}}26 -1 </article>27 -1 </li>28 -1 </ul>29 -1 <span class="loading" title="click here if nothing happens">loading...</span>-1 13 <template> -1 14 <article> -1 15 <header> -1 16 <a class="source u-bold" href="{{link}}" title="{{title}}">{{source}}</a> -1 17 <span class="meta"> -1 18 · Posted on <a href="{{link}}">{{dt}}</a> -1 19 </span> -1 20 </header> -1 21 {{{content}}} -1 22 </article> -1 23 </template> -1 24 <ul id="stream" class="posts"></ul> -1 25 <button type="button" class="loading" title="click here if nothing happens">loading…</button> -1 26 <script src="/static/plutopluto.js" type="module"></script> 30 27 </body> 31 28 32 29 </html>
diff --git a/plutopluto/static/jqlite.js b/plutopluto/static/jqlite.js
@@ -1,63 +0,0 @@1 -1 "use strict";2 -13 -1 var $ = function(query) {4 -1 var ob = {};5 -16 -1 if (query == document || query.ELEMENT_NODE) {7 -1 ob.target = query;8 -1 } else if (query.trim()[0] === '<') {9 -1 var div = document.createElement('div');10 -1 div.innerHTML = query;11 -1 ob.target = div.children[0];12 -1 } else {13 -1 ob.target = document.querySelector(query);14 -1 }15 -116 -1 ob.on = function(ev, handler) {17 -1 ob.target.addEventListener(ev, handler);18 -1 };19 -1 ob.ready = function(handler) {20 -1 ob.target.addEventListener('DOMContentLoaded', handler);21 -1 };22 -1 ob.click = function(handler) {23 -1 ob.target.addEventListener('click', handler);24 -1 };25 -1 ob.html = function(value) {26 -1 if (typeof value !== 'undefined') {27 -1 ob.target.innerHTML = value;28 -1 } else {29 -1 return ob.target.innerHTML;30 -1 }31 -1 };32 -1 ob.children = function() {33 -1 return ob.target.children;34 -1 };35 -1 ob.append = function(element) {36 -1 ob.target.appendChild(element.target);37 -1 };38 -1 ob.replace = function(element) {39 -1 ob.target.parentNode.replaceChild(element.target, ob.target);40 -1 }41 -142 -1 return ob;43 -1 };44 -145 -1 $.ajax = function(url, settings) {46 -1 if (settings.hasOwnProperty('data')) {47 -1 var pairs = [];48 -1 for (var key in settings.data) {49 -1 var value = settings.data[key];50 -1 pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));51 -1 }52 -1 url += '?' + pairs.join('&');53 -1 }54 -155 -1 var request = new XMLHttpRequest();56 -1 request.open('GET', url, true);57 -1 request.onload = function() {58 -1 if (this.status >= 199 && this.status < 400){59 -1 settings.success(JSON.parse(this.response));60 -1 }61 -1 };62 -1 request.send();63 -1 };
diff --git a/plutopluto/static/mustache.js b/plutopluto/static/mustache.js
@@ -1,578 +0,0 @@1 -1 /*!2 -1 * mustache.js - Logic-less {{mustache}} templates with JavaScript3 -1 * http://github.com/janl/mustache.js4 -1 */5 -16 -1 /*global define: false*/7 -18 -1 (function (global, factory) {9 -1 if (typeof exports === "object" && exports) {10 -1 factory(exports); // CommonJS11 -1 } else if (typeof define === "function" && define.amd) {12 -1 define(['exports'], factory); // AMD13 -1 } else {14 -1 factory(global.Mustache = {}); // <script>15 -1 }16 -1 }(this, function (mustache) {17 -118 -1 var Object_toString = Object.prototype.toString;19 -1 var isArray = Array.isArray || function (object) {20 -1 return Object_toString.call(object) === '[object Array]';21 -1 };22 -123 -1 function isFunction(object) {24 -1 return typeof object === 'function';25 -1 }26 -127 -1 function escapeRegExp(string) {28 -1 return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");29 -1 }30 -131 -1 // Workaround for https://issues.apache.org/jira/browse/COUCHDB-57732 -1 // See https://github.com/janl/mustache.js/issues/18933 -1 var RegExp_test = RegExp.prototype.test;34 -1 function testRegExp(re, string) {35 -1 return RegExp_test.call(re, string);36 -1 }37 -138 -1 var nonSpaceRe = /\S/;39 -1 function isWhitespace(string) {40 -1 return !testRegExp(nonSpaceRe, string);41 -1 }42 -143 -1 var entityMap = {44 -1 "&": "&",45 -1 "<": "<",46 -1 ">": ">",47 -1 '"': '"',48 -1 "'": ''',49 -1 "/": '/'50 -1 };51 -152 -1 function escapeHtml(string) {53 -1 return String(string).replace(/[&<>"'\/]/g, function (s) {54 -1 return entityMap[s];55 -1 });56 -1 }57 -158 -1 var whiteRe = /\s*/;59 -1 var spaceRe = /\s+/;60 -1 var equalsRe = /\s*=/;61 -1 var curlyRe = /\s*\}/;62 -1 var tagRe = /#|\^|\/|>|\{|&|=|!/;63 -164 -1 /**65 -1 * Breaks up the given `template` string into a tree of tokens. If the `tags`66 -1 * argument is given here it must be an array with two string values: the67 -1 * opening and closing tags used in the template (e.g. [ "<%", "%>" ]). Of68 -1 * course, the default is to use mustaches (i.e. mustache.tags).69 -1 *70 -1 * A token is an array with at least 4 elements. The first element is the71 -1 * mustache symbol that was used inside the tag, e.g. "#" or "&". If the tag72 -1 * did not contain a symbol (i.e. {{myValue}}) this element is "name". For73 -1 * all text that appears outside a symbol this element is "text".74 -1 *75 -1 * The second element of a token is its "value". For mustache tags this is76 -1 * whatever else was inside the tag besides the opening symbol. For text tokens77 -1 * this is the text itself.78 -1 *79 -1 * The third and fourth elements of the token are the start and end indices,80 -1 * respectively, of the token in the original template.81 -1 *82 -1 * Tokens that are the root node of a subtree contain two more elements: 1) an83 -1 * array of tokens in the subtree and 2) the index in the original template at84 -1 * which the closing tag for that section begins.85 -1 */86 -1 function parseTemplate(template, tags) {87 -1 if (!template)88 -1 return [];89 -190 -1 var sections = []; // Stack to hold section tokens91 -1 var tokens = []; // Buffer to hold the tokens92 -1 var spaces = []; // Indices of whitespace tokens on the current line93 -1 var hasTag = false; // Is there a {{tag}} on the current line?94 -1 var nonSpace = false; // Is there a non-space char on the current line?95 -196 -1 // Strips all whitespace tokens array for the current line97 -1 // if there was a {{#tag}} on it and otherwise only space.98 -1 function stripSpace() {99 -1 if (hasTag && !nonSpace) {100 -1 while (spaces.length)101 -1 delete tokens[spaces.pop()];102 -1 } else {103 -1 spaces = [];104 -1 }105 -1106 -1 hasTag = false;107 -1 nonSpace = false;108 -1 }109 -1110 -1 var openingTagRe, closingTagRe, closingCurlyRe;111 -1 function compileTags(tags) {112 -1 if (typeof tags === 'string')113 -1 tags = tags.split(spaceRe, 2);114 -1115 -1 if (!isArray(tags) || tags.length !== 2)116 -1 throw new Error('Invalid tags: ' + tags);117 -1118 -1 openingTagRe = new RegExp(escapeRegExp(tags[0]) + '\\s*');119 -1 closingTagRe = new RegExp('\\s*' + escapeRegExp(tags[1]));120 -1 closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tags[1]));121 -1 }122 -1123 -1 compileTags(tags || mustache.tags);124 -1125 -1 var scanner = new Scanner(template);126 -1127 -1 var start, type, value, chr, token, openSection;128 -1 while (!scanner.eos()) {129 -1 start = scanner.pos;130 -1131 -1 // Match any text between tags.132 -1 value = scanner.scanUntil(openingTagRe);133 -1134 -1 if (value) {135 -1 for (var i = 0, valueLength = value.length; i < valueLength; ++i) {136 -1 chr = value.charAt(i);137 -1138 -1 if (isWhitespace(chr)) {139 -1 spaces.push(tokens.length);140 -1 } else {141 -1 nonSpace = true;142 -1 }143 -1144 -1 tokens.push([ 'text', chr, start, start + 1 ]);145 -1 start += 1;146 -1147 -1 // Check for whitespace on the current line.148 -1 if (chr === '\n')149 -1 stripSpace();150 -1 }151 -1 }152 -1153 -1 // Match the opening tag.154 -1 if (!scanner.scan(openingTagRe))155 -1 break;156 -1157 -1 hasTag = true;158 -1159 -1 // Get the tag type.160 -1 type = scanner.scan(tagRe) || 'name';161 -1 scanner.scan(whiteRe);162 -1163 -1 // Get the tag value.164 -1 if (type === '=') {165 -1 value = scanner.scanUntil(equalsRe);166 -1 scanner.scan(equalsRe);167 -1 scanner.scanUntil(closingTagRe);168 -1 } else if (type === '{') {169 -1 value = scanner.scanUntil(closingCurlyRe);170 -1 scanner.scan(curlyRe);171 -1 scanner.scanUntil(closingTagRe);172 -1 type = '&';173 -1 } else {174 -1 value = scanner.scanUntil(closingTagRe);175 -1 }176 -1177 -1 // Match the closing tag.178 -1 if (!scanner.scan(closingTagRe))179 -1 throw new Error('Unclosed tag at ' + scanner.pos);180 -1181 -1 token = [ type, value, start, scanner.pos ];182 -1 tokens.push(token);183 -1184 -1 if (type === '#' || type === '^') {185 -1 sections.push(token);186 -1 } else if (type === '/') {187 -1 // Check section nesting.188 -1 openSection = sections.pop();189 -1190 -1 if (!openSection)191 -1 throw new Error('Unopened section "' + value + '" at ' + start);192 -1193 -1 if (openSection[1] !== value)194 -1 throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);195 -1 } else if (type === 'name' || type === '{' || type === '&') {196 -1 nonSpace = true;197 -1 } else if (type === '=') {198 -1 // Set the tags for the next time around.199 -1 compileTags(value);200 -1 }201 -1 }202 -1203 -1 // Make sure there are no open sections when we're done.204 -1 openSection = sections.pop();205 -1206 -1 if (openSection)207 -1 throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);208 -1209 -1 return nestTokens(squashTokens(tokens));210 -1 }211 -1212 -1 /**213 -1 * Combines the values of consecutive text tokens in the given `tokens` array214 -1 * to a single token.215 -1 */216 -1 function squashTokens(tokens) {217 -1 var squashedTokens = [];218 -1219 -1 var token, lastToken;220 -1 for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {221 -1 token = tokens[i];222 -1223 -1 if (token) {224 -1 if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {225 -1 lastToken[1] += token[1];226 -1 lastToken[3] = token[3];227 -1 } else {228 -1 squashedTokens.push(token);229 -1 lastToken = token;230 -1 }231 -1 }232 -1 }233 -1234 -1 return squashedTokens;235 -1 }236 -1237 -1 /**238 -1 * Forms the given array of `tokens` into a nested tree structure where239 -1 * tokens that represent a section have two additional items: 1) an array of240 -1 * all tokens that appear in that section and 2) the index in the original241 -1 * template that represents the end of that section.242 -1 */243 -1 function nestTokens(tokens) {244 -1 var nestedTokens = [];245 -1 var collector = nestedTokens;246 -1 var sections = [];247 -1248 -1 var token, section;249 -1 for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {250 -1 token = tokens[i];251 -1252 -1 switch (token[0]) {253 -1 case '#':254 -1 case '^':255 -1 collector.push(token);256 -1 sections.push(token);257 -1 collector = token[4] = [];258 -1 break;259 -1 case '/':260 -1 section = sections.pop();261 -1 section[5] = token[2];262 -1 collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;263 -1 break;264 -1 default:265 -1 collector.push(token);266 -1 }267 -1 }268 -1269 -1 return nestedTokens;270 -1 }271 -1272 -1 /**273 -1 * A simple string scanner that is used by the template parser to find274 -1 * tokens in template strings.275 -1 */276 -1 function Scanner(string) {277 -1 this.string = string;278 -1 this.tail = string;279 -1 this.pos = 0;280 -1 }281 -1282 -1 /**283 -1 * Returns `true` if the tail is empty (end of string).284 -1 */285 -1 Scanner.prototype.eos = function () {286 -1 return this.tail === "";287 -1 };288 -1289 -1 /**290 -1 * Tries to match the given regular expression at the current position.291 -1 * Returns the matched text if it can match, the empty string otherwise.292 -1 */293 -1 Scanner.prototype.scan = function (re) {294 -1 var match = this.tail.match(re);295 -1296 -1 if (!match || match.index !== 0)297 -1 return '';298 -1299 -1 var string = match[0];300 -1301 -1 this.tail = this.tail.substring(string.length);302 -1 this.pos += string.length;303 -1304 -1 return string;305 -1 };306 -1307 -1 /**308 -1 * Skips all text until the given regular expression can be matched. Returns309 -1 * the skipped string, which is the entire tail if no match can be made.310 -1 */311 -1 Scanner.prototype.scanUntil = function (re) {312 -1 var index = this.tail.search(re), match;313 -1314 -1 switch (index) {315 -1 case -1:316 -1 match = this.tail;317 -1 this.tail = "";318 -1 break;319 -1 case 0:320 -1 match = "";321 -1 break;322 -1 default:323 -1 match = this.tail.substring(0, index);324 -1 this.tail = this.tail.substring(index);325 -1 }326 -1327 -1 this.pos += match.length;328 -1329 -1 return match;330 -1 };331 -1332 -1 /**333 -1 * Represents a rendering context by wrapping a view object and334 -1 * maintaining a reference to the parent context.335 -1 */336 -1 function Context(view, parentContext) {337 -1 this.view = view == null ? {} : view;338 -1 this.cache = { '.': this.view };339 -1 this.parent = parentContext;340 -1 }341 -1342 -1 /**343 -1 * Creates a new context using the given view with this context344 -1 * as the parent.345 -1 */346 -1 Context.prototype.push = function (view) {347 -1 return new Context(view, this);348 -1 };349 -1350 -1 /**351 -1 * Returns the value of the given name in this context, traversing352 -1 * up the context hierarchy if the value is absent in this context's view.353 -1 */354 -1 Context.prototype.lookup = function (name) {355 -1 var cache = this.cache;356 -1357 -1 var value;358 -1 if (name in cache) {359 -1 value = cache[name];360 -1 } else {361 -1 var context = this, names, index;362 -1363 -1 while (context) {364 -1 if (name.indexOf('.') > 0) {365 -1 value = context.view;366 -1 names = name.split('.');367 -1 index = 0;368 -1369 -1 while (value != null && index < names.length)370 -1 value = value[names[index++]];371 -1 } else {372 -1 value = context.view[name];373 -1 }374 -1375 -1 if (value != null)376 -1 break;377 -1378 -1 context = context.parent;379 -1 }380 -1381 -1 cache[name] = value;382 -1 }383 -1384 -1 if (isFunction(value))385 -1 value = value.call(this.view);386 -1387 -1 return value;388 -1 };389 -1390 -1 /**391 -1 * A Writer knows how to take a stream of tokens and render them to a392 -1 * string, given a context. It also maintains a cache of templates to393 -1 * avoid the need to parse the same template twice.394 -1 */395 -1 function Writer() {396 -1 this.cache = {};397 -1 }398 -1399 -1 /**400 -1 * Clears all cached templates in this writer.401 -1 */402 -1 Writer.prototype.clearCache = function () {403 -1 this.cache = {};404 -1 };405 -1406 -1 /**407 -1 * Parses and caches the given `template` and returns the array of tokens408 -1 * that is generated from the parse.409 -1 */410 -1 Writer.prototype.parse = function (template, tags) {411 -1 var cache = this.cache;412 -1 var tokens = cache[template];413 -1414 -1 if (tokens == null)415 -1 tokens = cache[template] = parseTemplate(template, tags);416 -1417 -1 return tokens;418 -1 };419 -1420 -1 /**421 -1 * High-level method that is used to render the given `template` with422 -1 * the given `view`.423 -1 *424 -1 * The optional `partials` argument may be an object that contains the425 -1 * names and templates of partials that are used in the template. It may426 -1 * also be a function that is used to load partial templates on the fly427 -1 * that takes a single argument: the name of the partial.428 -1 */429 -1 Writer.prototype.render = function (template, view, partials) {430 -1 var tokens = this.parse(template);431 -1 var context = (view instanceof Context) ? view : new Context(view);432 -1 return this.renderTokens(tokens, context, partials, template);433 -1 };434 -1435 -1 /**436 -1 * Low-level method that renders the given array of `tokens` using437 -1 * the given `context` and `partials`.438 -1 *439 -1 * Note: The `originalTemplate` is only ever used to extract the portion440 -1 * of the original template that was contained in a higher-order section.441 -1 * If the template doesn't use higher-order sections, this argument may442 -1 * be omitted.443 -1 */444 -1 Writer.prototype.renderTokens = function (tokens, context, partials, originalTemplate) {445 -1 var buffer = '';446 -1447 -1 // This function is used to render an arbitrary template448 -1 // in the current context by higher-order sections.449 -1 var self = this;450 -1 function subRender(template) {451 -1 return self.render(template, context, partials);452 -1 }453 -1454 -1 var token, value;455 -1 for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {456 -1 token = tokens[i];457 -1458 -1 switch (token[0]) {459 -1 case '#':460 -1 value = context.lookup(token[1]);461 -1462 -1 if (!value)463 -1 continue;464 -1465 -1 if (isArray(value)) {466 -1 for (var j = 0, valueLength = value.length; j < valueLength; ++j) {467 -1 buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate);468 -1 }469 -1 } else if (typeof value === 'object' || typeof value === 'string') {470 -1 buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate);471 -1 } else if (isFunction(value)) {472 -1 if (typeof originalTemplate !== 'string')473 -1 throw new Error('Cannot use higher-order sections without the original template');474 -1475 -1 // Extract the portion of the original template that the section contains.476 -1 value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);477 -1478 -1 if (value != null)479 -1 buffer += value;480 -1 } else {481 -1 buffer += this.renderTokens(token[4], context, partials, originalTemplate);482 -1 }483 -1484 -1 break;485 -1 case '^':486 -1 value = context.lookup(token[1]);487 -1488 -1 // Use JavaScript's definition of falsy. Include empty arrays.489 -1 // See https://github.com/janl/mustache.js/issues/186490 -1 if (!value || (isArray(value) && value.length === 0))491 -1 buffer += this.renderTokens(token[4], context, partials, originalTemplate);492 -1493 -1 break;494 -1 case '>':495 -1 if (!partials)496 -1 continue;497 -1498 -1 value = isFunction(partials) ? partials(token[1]) : partials[token[1]];499 -1500 -1 if (value != null)501 -1 buffer += this.renderTokens(this.parse(value), context, partials, value);502 -1503 -1 break;504 -1 case '&':505 -1 value = context.lookup(token[1]);506 -1507 -1 if (value != null)508 -1 buffer += value;509 -1510 -1 break;511 -1 case 'name':512 -1 value = context.lookup(token[1]);513 -1514 -1 if (value != null)515 -1 buffer += mustache.escape(value);516 -1517 -1 break;518 -1 case 'text':519 -1 buffer += token[1];520 -1 break;521 -1 }522 -1 }523 -1524 -1 return buffer;525 -1 };526 -1527 -1 mustache.name = "mustache.js";528 -1 mustache.version = "0.8.1";529 -1 mustache.tags = [ "{{", "}}" ];530 -1531 -1 // All high-level mustache.* functions use this writer.532 -1 var defaultWriter = new Writer();533 -1534 -1 /**535 -1 * Clears all cached templates in the default writer.536 -1 */537 -1 mustache.clearCache = function () {538 -1 return defaultWriter.clearCache();539 -1 };540 -1541 -1 /**542 -1 * Parses and caches the given template in the default writer and returns the543 -1 * array of tokens it contains. Doing this ahead of time avoids the need to544 -1 * parse templates on the fly as they are rendered.545 -1 */546 -1 mustache.parse = function (template, tags) {547 -1 return defaultWriter.parse(template, tags);548 -1 };549 -1550 -1 /**551 -1 * Renders the `template` with the given `view` and `partials` using the552 -1 * default writer.553 -1 */554 -1 mustache.render = function (template, view, partials) {555 -1 return defaultWriter.render(template, view, partials);556 -1 };557 -1558 -1 // This is here for backwards compatibility with 0.4.x.559 -1 mustache.to_html = function (template, view, partials, send) {560 -1 var result = mustache.render(template, view, partials);561 -1562 -1 if (isFunction(send)) {563 -1 send(result);564 -1 } else {565 -1 return result;566 -1 }567 -1 };568 -1569 -1 // Export the escaping function so that the user may override it.570 -1 // See https://github.com/janl/mustache.js/issues/244571 -1 mustache.escape = escapeHtml;572 -1573 -1 // Export these mainly for testing, but also for advanced usage.574 -1 mustache.Scanner = Scanner;575 -1 mustache.Context = Context;576 -1 mustache.Writer = Writer;577 -1578 -1 }));
diff --git a/plutopluto/static/plutopluto.js b/plutopluto/static/plutopluto.js
@@ -1,119 +1,124 @@1 -1 "use strict";2 -13 -1 $(document).ready(function() {4 -1 $.ajax('/config', {success: function(config) {5 -1 var entries = [];6 -1 var page = 0;7 -18 -1 // load templates9 -1 var template = $('#stream').html();10 -1 Mustache.parse(template);11 -1 $('#stream').html('');12 -113 -1 var formatDate = function(date, format) {14 -1 var o = {15 -1 'Y': date.getFullYear(),16 -1 'M': date.getMonth() + 1,17 -1 'd': date.getDate(),18 -1 'h': date.getHours(),19 -1 'm': date.getMinutes(),20 -1 's': date.getSeconds-1 1 var template = document.querySelector('template'); -1 2 var stream = document.querySelector('#stream'); -1 3 var loading = document.querySelector('.loading'); -1 4 -1 5 var formatDate = function(text) { -1 6 var date = new Date(parseInt(text, 10) * 1000); -1 7 var format = 'YYYY/MM/dd hh:mm'; -1 8 -1 9 var o = { -1 10 'Y': date.getFullYear(), -1 11 'M': date.getMonth() + 1, -1 12 'd': date.getDate(), -1 13 'h': date.getHours(), -1 14 'm': date.getMinutes(), -1 15 's': date.getSeconds, -1 16 }; -1 17 -1 18 for (var key in o) { -1 19 if (new RegExp('(' + key + '+)').test(format)) { -1 20 var value = o[key].toString(); -1 21 if (RegExp.$1.length > 1) { -1 22 value = ('0000' + value).substr(-RegExp.$1.length); 21 23 } -1 24 format = format.replace(RegExp.$1, value); -1 25 } -1 26 } -1 27 -1 28 return format; -1 29 }; -1 30 -1 31 var bottomDistance = function() { -1 32 var doc = document.body.scrollHeight; -1 33 var screen = window.innerHeight; -1 34 var position = document.body.scrollTop || window.scrollY; -1 35 return doc - position - screen; -1 36 }; -1 37 -1 38 var escapeHTML = function(text) { -1 39 var p = document.createElement('p'); -1 40 p.textContent = text; -1 41 return p.innerHTML; -1 42 }; -1 43 -1 44 var appendEntries = function(entries) { -1 45 entries.forEach(entry => { -1 46 var li = document.createElement('li'); -1 47 li.className = 'post'; -1 48 li.innerHTML = template.innerHTML -1 49 .replace('{{source}}', escapeHTML(entry.source)) -1 50 .replace('{{link}}', escapeHTML(entry.link)) -1 51 .replace('{{title}}', escapeHTML(entry.title)) -1 52 .replace('{{dt}}', escapeHTML(formatDate(entry.dt))) -1 53 .replace('{{{content}}}', entry.content); -1 54 stream.append(li); -1 55 }); -1 56 }; -1 57 -1 58 var fetchJSON = function(url) { -1 59 return fetch(url).then(r => { -1 60 if (r.ok) { -1 61 return r.json(); -1 62 } else { -1 63 throw r; -1 64 } -1 65 }); -1 66 }; 22 6723 -1 for (var key in o) {24 -1 if (new RegExp('(' + key + '+)').test(format)) {25 -1 var value = o[key].toString();26 -1 if (RegExp.$1.length > 1) {27 -1 value = ("0000" + value).substr(-RegExp.$1.length);28 -1 }29 -1 format = format.replace(RegExp.$1, value)30 -1 }31 -1 }-1 68 fetchJSON('/config').then(config => { -1 69 var entries = []; -1 70 var page = 0; 32 7133 -1 return format;34 -1 };-1 72 var loadNextPageLock = false; -1 73 var loadNextPage = function() { -1 74 if (loadNextPageLock) { -1 75 return; -1 76 } 35 7736 -1 var formatDateFilter = function() {37 -1 return function(text, render) {38 -1 var dt = new Date(parseInt(render(text), 10) * 1000);39 -1 return formatDate(dt, 'YYYY/MM/dd hh:mm');40 -1 }41 -1 };42 -143 -1 var bottomDistance = function() {44 -1 var doc = document.body.scrollHeight;45 -1 var screen = window.innerHeight;46 -1 var position = document.body.scrollTop || window.scrollY;47 -1 return doc - position - screen;48 -1 };49 -150 -1 var appendEntries = function(entries) {51 -1 entries.forEach(function(entry) {52 -1 entry.formatDate = formatDateFilter;53 -1 var rendered = Mustache.render(template, entry);54 -1 $('#stream').append($(rendered));55 -1 });56 -1 };57 -158 -1 var loadNextPageLock = false;59 -1 var loadNextPage = function() {60 -1 if (!loadNextPageLock) {61 -1 loadNextPageLock = true;62 -163 -1 config.urls.forEach(function(url) {64 -1 if (url.indexOf('{page}') >= 0) {65 -1 url = url.replace('{page}', page);66 -1 } else if (page !== 0) {67 -1 return;68 -1 }69 -170 -1 $.ajax('/parse', {71 -1 data: {url: url},72 -1 success: function(data) {73 -1 entries = entries.concat(data.entries);74 -175 -1 // now that we have entries, we can show some76 -1 if ($('#stream').children().length === 0) {77 -1 loadMore();78 -1 }79 -180 -1 // ideally we would wait until all requests have finished81 -1 // but this is only a simple optimisation anyway82 -1 loadNextPageLock = false;83 -1 }84 -1 });85 -1 });86 -1 page++;-1 78 loadNextPageLock = true; -1 79 -1 80 var promises = config.urls.map(url => { -1 81 if (url.includes('{page}')) { -1 82 url = url.replace('{page}', page); -1 83 } else if (page !== 0) { -1 84 return; 87 85 }88 -1 };89 8690 -1 var loadMore = function() {91 -1 entries.sort(function(a, b) {92 -1 return b.dt - a.dt;-1 87 return fetchJSON('/parse?' + new URLSearchParams({url: url})).then(data => { -1 88 entries = entries.concat(data.entries); 93 89 });94 -1 appendEntries(entries.splice(0, 10));95 -1 if (entries.length < 30) {96 -1 loadNextPage();97 -1 }98 -1 };-1 90 }); 99 91100 -1 $(document).on('scroll', function() {101 -1 if (bottomDistance() < 4000) {-1 92 Promise.all(promises).finally(() => { -1 93 // now that we have entries, we can show some -1 94 if (stream.children.length === 0) { 102 95 loadMore(); 103 96 }104 -1 }, {passive: true});105 -1 $('.loading').click(loadMore);106 -1107 -1 // load initial content108 -1 loadNextPage();109 -1 }});110 -1111 -1 // youtube video embedding112 -1 $(document).on('click', function(event) {113 -1 if (event.target.alt.match(/^https:\/\/www\.youtube/)) {114 -1 var iframe = $('<iframe class="youtube">');115 -1 iframe.target.src = event.target.alt;116 -1 $(event.target).replace(iframe);-1 97 -1 98 loadNextPageLock = false; -1 99 }); -1 100 -1 101 page++; -1 102 }; -1 103 -1 104 var loadMore = function() { -1 105 entries.sort((a, b) => { -1 106 return b.dt - a.dt; -1 107 }); -1 108 appendEntries(entries.splice(0, 10)); -1 109 if (entries.length < 30) { -1 110 loadNextPage(); 117 111 }118 -1 });-1 112 }; -1 113 -1 114 document.addEventListener('scroll', () => { -1 115 if (bottomDistance() < 4000) { -1 116 loadMore(); -1 117 } -1 118 }, {passive: true}); -1 119 -1 120 loading.addEventListener('click', loadMore); -1 121 -1 122 // load initial content -1 123 loadNextPage(); 119 124 });