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");