diff --git a/scripts/entry.js b/scripts/entry.js index f04f5ed..b664d83 100644 --- a/scripts/entry.js +++ b/scripts/entry.js @@ -27,11 +27,16 @@ function Entry(data,host) this.quote = data.quote; if(data.quote && this.target && this.target[0]){ - var dummy_portal = {"url":this.target[0],"json":{"name":r.escape_html(portal_from_hash(this.target[0].toString())).substring(1)}}; + var icon = this.target[0].replace(/\/$/, "") + "/media/content/icon.svg" + // set the source's icon for quotes of remotes + if (host && host.json && host.json.sameAs && has_hash(host.json.sameAs, this.target[0])) { + icon = host.icon + } + var dummy_portal = {"url":this.target[0], "icon": icon, "json":{"name":r.escape_html(portal_from_hash(this.target[0].toString())).substring(1)}}; this.quote = new Entry(data.quote, dummy_portal); } - this.is_seed = this.host ? r.home.portal.json.port.indexOf(this.host.url) > -1 : false; + this.is_seed = this.host && has_hash(r.home.portal.json.port, this.host.url); } this.update(data, host); @@ -102,13 +107,7 @@ function Entry(data,host) if (desc){ title += "\n" + desc; } - var url; - if (this.host.url === r.client_url || this.host.url === "$rotonde") { - url = r.client_url + "/media/logo.svg"; - } else { - url = this.host.url + "/media/content/icon.svg"; - } - return ""; + return ""; } this.header = function() @@ -371,8 +370,8 @@ function Entry(data,host) space = m.length; var word = m.substring(c, space); - if (word.length > 1 && word[0] == "@") { - var name_match = r.operator.name_pattern.exec(word); + var name_match; + if (word.length > 1 && word[0] == "@" && (name_match = r.operator.name_pattern.exec(word))) { var remnants = word.substr(name_match[0].length); if (name_match[1] == r.home.portal.json.name) { n += ""+name_match[0]+""+remnants; @@ -491,10 +490,14 @@ function Entry(data,host) return true; } - if(this.target && this.target.length > 0){ - return has_hash(r.home.portal, this.target); + // check for mentions of our portal or of one of our remotes in sameAs + if (this.target && this.target.length > 0) { + var has_mention = has_hash(r.home.portal, this.target); + if (r.home.portal.json && r.home.portal.json.sameAs) { + has_mention = has_mention || has_hash(r.home.portal.json.sameAs, this.target); + } + return has_mention; } - return false; } diff --git a/scripts/feed.js b/scripts/feed.js index c495599..696a35f 100644 --- a/scripts/feed.js +++ b/scripts/feed.js @@ -139,7 +139,11 @@ function Feed(feed_urls) return; } - var url = r.home.feed.queue[0]; + var entry = r.home.feed.queue[0]; + var url = entry; + if (entry.url) { + url = entry.url; + } r.home.feed.queue = r.home.feed.queue.slice(1); @@ -151,7 +155,12 @@ function Feed(feed_urls) r.home.feed.next(); return; } - portal.connect() + + if (entry.oncreate) + portal.fire(entry.oncreate); + if (entry.onparse) + portal.onparse.push(entry.onparse); + portal.connect(); r.home.feed.update_log(); } @@ -177,6 +186,10 @@ function Feed(feed_urls) this.__get_portal_cache__[hashes[id]] = portal; } + if (!portal.is_remote) { + portal.load_remotes(); + } + // Invalidate the collected network cache and recollect. r.home.collect_network(true); @@ -262,6 +275,8 @@ function Feed(feed_urls)         var portal = r.home.feed.portals[id];         await portal.refresh();       } + if (r.home.feed.portal_rotonde) + await r.home.feed.portal_rotonde.connect_service();       r.home.feed.refresh('delayed: ' + why);     }, 750);      return; @@ -283,7 +298,9 @@ function Feed(feed_urls) var portal = r.home.feed.portals[id]; entries.push.apply(entries, portal.entries()); } - + if (r.home.feed.portal_rotonde) + entries.push.apply(entries, r.home.feed.portal_rotonde.entries()); + this.mentions = 0; this.whispers = 0; @@ -319,7 +336,7 @@ function Feed(feed_urls) } var now = new Date(); - var entries_now = new Set(); + var entries_now = []; for (id in sorted_entries){ var entry = sorted_entries[id]; @@ -338,7 +355,7 @@ function Feed(feed_urls) c = -2; var elem = !entry ? null : entry.to_element(timeline, c, cmin, cmax, coffset); if (elem != null) { - entries_now.add(entry); + entries_now.push(entry); } if (c >= 0) ca++; @@ -347,7 +364,7 @@ function Feed(feed_urls) // Remove any "zombie" entries - removed entries not belonging to any portal. for (id in this.entries_prev) { var entry = this.entries_prev[id]; - if (entries_now.has(entry)) + if (entries_now.indexOf(entry) > -1) continue; entry.remove_element(); } diff --git a/scripts/operator.js b/scripts/operator.js index 5be9b76..a321d94 100644 --- a/scripts/operator.js +++ b/scripts/operator.js @@ -169,9 +169,6 @@ function Operator(el) if(r.home.portal.json.port.indexOf(path) > -1){ r.home.portal.json.port.splice(r.home.portal.json.port.indexOf(path), 1); } - else if(r.home.portal.json.port.indexOf(path+"/") > -1){ - r.home.portal.json.port.splice(r.home.portal.json.port.indexOf(path+"/"), 1); - } else{ console.log("could not find",path) } @@ -183,13 +180,63 @@ function Operator(el) r.home.feed.portals[id].id = id; } portal.badge_remove(); - portal.entries_remove(); } r.home.save(); r.home.feed.refresh("unfollowing: "+option); } + this.commands.mirror = function(p,option) + { + var remote = p; + if(remote.slice(-1) !== "/") { remote += "/" } + + if (!r.home.portal.json.sameAs) + r.home.portal.json.sameAs = []; + + if (has_hash(r.home.portal.json.sameAs, remote)) + return; + + // create the array if it doesn't exist + if (!r.home.portal.json.sameAs) { r.home.portal.json.sameAs = [] } + r.home.portal.json.sameAs.push(remote); + try { + var remote_portal = new Portal(remote) + remote_portal.start().then(r.home.portal.load_remotes) + } catch (err) { + console.error("Error when connecting to remote", err) + } + r.home.save(); + } + + this.commands.unmirror = function(p,option) + { + var remote = p; + if(remote.slice(-1) !== "/") { remote += "/" } + + if (!r.home.portal.json.sameAs) + r.home.portal.json.sameAs = []; + + // Remove + if (r.home.portal.json.sameAs.indexOf(remote) > -1) { + r.home.portal.json.sameAs.splice(r.home.portal.json.sameAs.indexOf(remote), 1); + } else { + console.log("could not find",remote); + return; + } + + var portal = r.home.feed.get_portal(remote); + if (portal && portal.is_remote) { + r.home.feed.portals.splice(portal.id, 1)[0]; + for (var id in r.home.feed.portals) { + r.home.feed.portals[id].id = id; + } + } + + r.home.save(); + r.home.feed.refresh("mirroring: "+option); + } + this.commands.dat = function(p,option) { option = to_hash(option); @@ -614,6 +661,8 @@ function Operator(el) this.lookup_name = function(name) { + if (r.home.feed.portal_rotonde && name === r.home.feed.portal_rotonde.json.name) + return [r.home.feed.portal_rotonde]; // We return an array since multiple people might be using the same name. var results = []; for(var url in r.home.feed.portals){ diff --git a/scripts/portal.js b/scripts/portal.js index 8a4ad16..f278227 100644 --- a/scripts/portal.js +++ b/scripts/portal.js @@ -3,8 +3,13 @@ function Portal(url) var p = this; this.url = url; + this.icon = url.replace(/\/$/, "") + "/media/content/icon.svg"; + if (this.url === r.client_url || this.url === "$rotonde") { + this.icon = r.client_url.replace(/\/$/, "") + "/media/logo.svg"; + } this.file = null; this.json = null; + this.is_remove = false; this.archive = new DatArchive(this.url); // Resolve "masked" (f.e. hashbase) dat URLs to "hashed" (dat://0123456789abcdef/) one. DatArchive.resolveName(this.url).then(hash => { @@ -12,16 +17,43 @@ function Portal(url) this.dat = "dat://"+hash+"/"; }); + this.is_remote = false; + this.remote_parent = null; + this.last_entry = null; this.badge_element = null; this.badge_element_html = null; + this.onparse = []; // Contains functions of format json => {...} + this.fire = function(event) { + var handlers; + if (typeof(event) === "function") + handlers = [event]; + else if (event.length && typeof(event[0]) === "function") + handlers = event; + else + handlers = this["on"+event]; + if (!handlers || handlers.length === 0) return true; // Return true by default. + var args = Array.prototype.splice.call(arguments, 1); + for (var id in handlers) { + var result = handlers[id].apply(this, args); + if (result === true) // We only want true, not truly values. + continue; // If the handler returned true, continue to the next handler. + else if (result === false) // We only want false, not falsy values. + return false; // Exit early. + else if (result !== undefined) + return result; // If the handler returned something, return it early. + } + return true; + } + this.start = async function() { var file = await this.archive.readFile('/portal.json',{timeout: 2000}).then(console.log("done!")); this.json = JSON.parse(file); + if (!this.fire("parse", this.json)) throw new Error("onparse returned false!"); this.maintenance(); } @@ -53,6 +85,7 @@ function Portal(url) try { p.json = JSON.parse(p.file); + if (!p.fire("parse", p.json)) throw new Error("onparse returned false!"); p.file = null; r.home.feed.register(p); } catch (err) { @@ -62,6 +95,40 @@ function Portal(url) setTimeout(r.home.feed.next, r.home.feed.connection_delay); } + this.load_remotes = async function() { + if (!p.json || !p.json.sameAs || p.json.sameAs.length === 0) { + return; + } + + r.home.feed.queue.push.apply(r.home.feed.queue, p.json.sameAs.map((remote_url) => { + return { + url: remote_url, + oncreate: function() { + this.is_remote = true; + this.remote_parent = p; + var hash = p.hashes()[0] + this.icon = "dat://" + hash + "/media/content/icon.svg"; + }, + onparse: function(json) { + this.json.name = `${p.json.name}=${json.name}` + if (has_hash(r.home.portal, this.remote_parent)) { + Array.prototype.push.apply(r.home.feed.queue, this.json.port.map((port) => { + return { + url: port, + onparse: function() { return true} + } + })) + } + if (this.json && this.json.sameAs) { + return has_hash(this.json.sameAs, p.hashes()); + } + return false + } + } + })); + r.home.feed.connect(); + } + this.connect_service = async function() { console.log('connecting to rotonde client service messages: ', p.url); @@ -76,8 +143,9 @@ function Portal(url) try { p.json = JSON.parse(p.file); + if (!p.fire("parse", p.json)) throw new Error("onparse returned false!"); p.file = null; - r.home.feed.portals.push(r.home.feed.portal_rotonde = this); + r.home.feed.portal_rotonde = this; } catch (err) { console.log('parsing failed: ', p.url); } @@ -97,6 +165,7 @@ function Portal(url) try { p.json = JSON.parse(p.file); + if (!p.fire("parse", p.json)) throw new Error("onparse returned false!"); p.file = null; } catch (err) { console.log('parsing failed: ', p.url); @@ -124,7 +193,12 @@ function Portal(url) } try { + var oldName = p.json.name; p.json = JSON.parse(p.file); + // don't replace name for remotes + if (p.is_remote) { + p.json.name = oldName; + } p.file = null; } catch (err) { console.log('parsing failed: ', p.url); @@ -168,7 +242,7 @@ function Portal(url) this.relationship = function(target = r.home.portal.hashes_set()) { - if (this === r.home.feed.portal_rotonde) return create_rune("portal", "rotonde"); + if (this.url === r.client_url) return create_rune("portal", "rotonde"); if (has_hash(this, target)) return create_rune("portal", "self"); if (has_hash(this.json.port, target)) return create_rune("portal", "both");