This commit is contained in:
Michael Murtaugh
2019-05-27 16:05:50 +02:00
commit b027ca341a
29 changed files with 17864 additions and 0 deletions

221
src/forcenet.js Normal file
View File

@@ -0,0 +1,221 @@
import { event, select, selectAll} from 'd3-selection';
import { values, set, map } from 'd3-collection';
import { drag } from 'd3-drag';
import { zoom } from 'd3-zoom';
import { forceSimulation, forceLink, forceManyBody, forceCenter, forceX, forceY, forceRadial } from 'd3-force';
// import { Wiki, Page } from './wiki.js';
import EventEmitter from 'eventemitter3';
// import { json } from 'd3-fetch';
export class ForceNet {
constructor (symbols) {
var width = 600,
height = 600;
this.symbols = symbols || {};
this.events = new EventEmitter();
this.active_page = null;
// this.nodes = {};
this.simulation = forceSimulation()
.velocityDecay(0.1)
.force("link", forceLink().id(d => d.title))
.force("charge", forceManyBody())
.force("radial", forceRadial(180, width/2, height/2));
// .force("center", forceCenter(width / 2, height / 2));
this.svg = null;
// this.historylinks = {};
}
on (message, callback, context) {
this.events.on(message, callback, context);
}
init_svg (svg) {
this.svg = select(svg || "svg");
this.zoom = zoom()
.scaleExtent([1 / 16, 4])
.on("zoom", () => {
this.content.attr("transform", event.transform);
// console.log("transform", event.transform, this.content.attr("transform"));
});
this.rect = this.svg.append("rect")
.attr("width", 1000)
.attr("height", 1000)
.style("fill", "none")
.style("pointer-events", "all")
.call(this.zoom);
this.content = this.svg.append("g")
.attr("id", "content"),
this.linksg = this.content.append("g")
.attr("class", "links");
this.nodesg = this.content.append("g")
.attr("class", "nodes");
}
dragstarted (d) {
if (!event.active) this.simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
dragged (d) {
d.fx = event.x;
d.fy = event.y;
}
dragended(d) {
if (!event.active) this.simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
link_key (a, b) {
return (a < b) ? ("link_"+a+"_"+b) : ("link_"+b+"_"+a);
}
get_symbol (d, def) {
for (var i=0, l=d.cats.length; i<l; i++) {
if (this.symbols[d.cats[i]]) {
return this.symbols[d.cats[i]];
}
}
return this.symbols.default || def;
}
update_graph (graph) {
console.log("UPDATE GRAPH", graph.nodes.length, graph.links.length);
var link = this.linksg.selectAll("line")
.data(graph.links, d => { return this.link_key(d.source.title, d.target.title) });
var link_enter = link.enter()
.append("line");
link.exit().each(d => {
d.source.linked = false;
d.target.linked = false;
}).remove();
link_enter.merge(link).each(d => {
d.source.linked = true;
d.target.linked = true;
});
var node = this.nodesg
.selectAll("g.page")
.data(graph.nodes, function (d) { return d.title });
var that = this;
var node_enter = node.enter().append("g")
.attr("class", "page")
// .attr("class", d=>"page "+this.wiki.get_ns_classname(d.ns))
.on("click", function(d) {
that.events.emit("nodeclick", d, this);
// this.set_active_node(d.title);
})
.on("mouseover", function (d) {
// console.log("mouseover", this);
select(this).classed("mouse", true);
})
.on("mouseout", function (d) {
// console.log("mouseout", this);
select(this).classed("mouse", false);
})
.call(drag()
.on("start", this.dragstarted.bind(this))
.on("drag", this.dragged.bind(this))
.on("end", this.dragended.bind(this)));
node_enter.append("use")
.attr("xlink:href", d => this.get_symbol(d, "default"))
.attr("class", "testcolor");
// {
// for (var i=0, l=d.cats.length; i<l; i++) {
// if (this.symbols[d.cats[i]]) {
// return this.symbols[d.cats[i]];
// }
// }
// return this.symbols.default || "default";
// });
// node_enter.append("circle")
// .attr("r", 6);
node_enter.append("text")
.text(d => d.title)
.attr("x", 10);
//node_enter.append("title")
// .text(function(d) { return d.title; });
node = node_enter.merge(node);
link = link_enter.merge(link);
node.classed("active", d=>d.active);
this.simulation
.nodes(graph.nodes)
.on("tick", ticked);
this.simulation.force("link")
.links(graph.links);
this.simulation.force("radial").radius(d => d.linked ? null : 200);
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
// node
// .attr("cx", function(d) { return d.x; })
// .attr("cy", function(d) { return d.y; });
node
.attr("transform", d => `translate(${d.x},${d.y})`);
}
this.update_nodes();
this.update_forces();
// this.simulation.alphaTarget(0.3).restart();
}
update_nodes () {
var nodes = this.nodesg.selectAll("g.page");
console.log("update_nodes", nodes.size());
nodes.classed("active", d=>d.active);
nodes.classed("active2", d=>d.active2);
nodes.classed("highlight", d=>d.highlight);
nodes.sort((a, b) => {
// console.log("sort", a, b);
var x = a.active ? 10 : (a.active2 ? 5 : 0),
y = b.active ? 10 : (b.active2 ? 5 : 0);
return x - y;
});
var links = this.linksg.selectAll("line");
links.classed("active2", d=>d.active2);
links.classed("history", d=>d.type == "history");
links.sort((a, b) => {
// console.log("sort", a, b);
var x = a.active2 ? 10 : (a.history ? 5 : 0),
y = b.active2 ? 10 : (b.history ? 5 : 0);
return x - y;
});
}
update_forces () {
var force = this.simulation.force("link");
console.log("update_forces:force", force);
this.simulation.force("link").strength(d => {
if (d.source.active || d.target.active) {
return 1;
} else {
// same as d3.force's defaultStrength
return 0.5 * (1 / Math.min(d.source.count, d.target.count));
}
});
this.simulation.alphaTarget(0.3).restart();
}
}

3
src/test.js Normal file
View File

@@ -0,0 +1,3 @@
export function test () {
alert("testing import");
}

154
src/wiki.js Normal file
View File

@@ -0,0 +1,154 @@
import fetchJsonp from 'fetch-jsonp';
import { map } from 'd3-collection';
var NS = {
main: 0,
discussion: 1,
template: 10,
category: 14,
news: 3106,
web: 3116
}
export class Wiki {
constructor (apiurl) {
this.apiurl = apiurl;
this.pages_by_title = {};
this.ns_names = {};
for (var key in NS) {
var nsid = NS[key];
this.ns_names[nsid] = key;
}
}
get_page_by_title (title) {
var p = this.pages_by_title[title];
if (p) {
return p;
} else {
p = new Page(this, {title: title, ns: 0});
this.pages_by_title[title] = p;
return p;
}
}
page_for_object (n, merge_data) {
if (merge_data === undefined) { merge_data = true; }
var title = n.title,
p = this.pages_by_title[title];
if (p) {
if (merge_data) { p.merge_data(n); }
return p;
} else {
p = new Page(this, n);
this.pages_by_title[title] = p;
return p;
}
}
get_ns_classname (nsid) {
// console.log("get_ns_classname", nsid, this.ns_names[nsid]);
var ret = this.ns_names[nsid];
console.log("classname", ret);
if (ret === undefined) { console.log("warning classname undefined for ns", nsid); }
return ret;
}
union (p1, p2) {
var union = map(p1, d=>d.title);
for (var i=0, l=p2.length; i<l; i++) {
var x = p2[i];
union.set(x.title, x);
}
return union.values();
}
}
export class Page {
constructor (wiki, node) {
this.wiki = wiki;
this.merge_data(node);
}
url () {
// return this.wiki.apiurl.replace("api.php", "index.php")+"/"+encodeURIComponent(this.title);
return this.wiki.apiurl.replace("api.php", "index.php")+"/"+encodeURIComponent(this.title);
}
merge_data (node) {
for (var key in node) {
if (node.hasOwnProperty(key)) {
this[key] = node[key];
}
}
}
// api.php?action=query&prop=categories&titles=Albert%20Einstein
async get_prop (pname, prefix) {
var ret = [];
var url = this.wiki.apiurl+"?action=query&format=json&formatversion=2&prop="+pname+"&titles="+encodeURIComponent(this.title);
while (true) {
var data = await fetchJsonp(url);
var json = await data.json();
// console.log("BACKLINKS.RAW", json);
// filter REDIRECTS + Discussion pages (ns==1)
if (json.query.pages[0]) {
var p = json.query.pages[0];
// extract any missing page info
if (p.ns && !this.ns) { this.ns = p.ns; }
if (p.pageid && !this.pageid) { this.pageid = p.pageid; }
}
if (json.query.pages[0][pname]) {
ret.push.apply(ret, json.query.pages[0][pname]);
}
if (!json.continue) { break; }
url = this.wiki.apiurl+"?action=query&format=json&formatversion=2&prop="+pname+"&"+prefix+"continue="+json.continue[prefix+"continue"]+"&titles="+encodeURIComponent(this.title);
}
ret = ret.map(x => this.wiki.page_for_object(x));
// console.log("get_backlinks", ret);
console.log("get_prop", pname, prefix, ret);
return ret;
}
async get_links () {
return await this.get_prop("links", "pl");
// // http://localhost/mw/api.php?action=query&prop=links&titles=Bienvenue_%C3%A0_l%E2%80%99erg
// var ret = [];
// var url = this.wiki.apiurl+"?action=query&format=json&formatversion=2&prop=links&titles="+encodeURIComponent(this.title);
// while (true) {
// var data = await fetchJsonp(url);
// var json = await data.json();
// // console.log("JSON", json);
// if (json.query.pages[0]) {
// var p = json.query.pages[0];
// // extract any missing page info
// if (p.ns && !this.ns) { this.ns = p.ns; }
// if (p.pageid && !this.pageid) { this.pageid = p.pageid; }
// }
// if (json.query.pages[0].links) {
// ret.push.apply(ret, json.query.pages[0].links);
// }
// if (!json.continue) { break; }
// url = this.wiki.apiurl+"?action=query&format=json&formatversion=2&prop=links&plcontinue="+json.continue.plcontinue+"&titles="+encodeURIComponent(this.title);
// }
// ret = ret.map(x => this.wiki.page_for_object(x));
// // map these to page objects
// return ret;
}
async get_linkshere () {
return await this.get_prop("linkshere", "lh");
}
async get_list () {
// http://localhost/mw/api.php?action=query&prop=links&titles=Bienvenue_%C3%A0_l%E2%80%99erg
var ret = [];
var url = this.wiki.apiurl+"?action=query&format=json&formatversion=2&list=backlinks&bltitle="+encodeURIComponent(this.title);
while (true) {
var data = await fetchJsonp(url);
var json = await data.json();
// console.log("BACKLINKS.RAW", json);
// filter REDIRECTS + Discussion pages (ns==1)
var backlinks = json.query.backlinks;
// backlink = backlinks.filter(x => (!x.redirect && x.ns != 1);
ret.push.apply(ret, backlinks);
if (!json.continue) { break; }
url = this.wiki.apiurl+"?action=query&format=json&formatversion=2&list=backlinks&blcontinue="+json.continue.blcontinue+"&bltitle="+encodeURIComponent(this.title);
}
ret = ret.map(x => this.wiki.page_for_object(x));
// console.log("get_backlinks", ret);
return ret;
}
}

210
src/wikimap.js Normal file
View File

@@ -0,0 +1,210 @@
import { event, select, selectAll} from 'd3-selection';
import { values, set, map } from 'd3-collection';
import { drag } from 'd3-drag';
import { forceSimulation, forceLink, forceManyBody, forceCenter, forceX, forceY, forceRadial } from 'd3-force';
import { Wiki, Page } from './wiki.js';
import EventEmitter from 'eventemitter3';
export class Map {
constructor (apiurl) {
var width = 600,
height = 600;
this.wiki = new Wiki(apiurl);
this.events = new EventEmitter();
this.active_page = null;
// this.nodes = {};
this.simulation = forceSimulation()
.velocityDecay(0.1)
.force("link", forceLink().id(d => d.title))
.force("charge", forceManyBody())
.force("radial", forceRadial(180, width/2, height/2));
// .force("center", forceCenter(width / 2, height / 2));
this.svg = null;
this.historylinks = {};
}
on (message, callback, context) {
this.events.on(message, callback, context);
}
init_svg (svg) {
this.svg = select(svg || "svg");
this.linksg = this.svg.append("g")
.attr("class", "links");
this.nodesg = this.svg.append("g")
.attr("class", "nodes");
}
dragstarted (d) {
if (!event.active) this.simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
dragged (d) {
d.fx = event.x;
d.fy = event.y;
}
dragended(d) {
if (!event.active) this.simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
update_graph (graph) {
// console.log("UPDATE GRAPH", graph.nodes.length, graph.links.length);
var that = this,
link = this.linksg.selectAll("line")
.data(graph.links, function (d) { return that.link_key(d.source.title, d.target.title) });
var link_enter = link.enter()
.append("line");
link.exit().each(d => {
d.source.linked = false;
d.target.linked = false;
}).remove();
link_enter.merge(link).each(d => {
d.source.linked = true;
d.target.linked = true;
});
var node = this.nodesg
.selectAll("g.page")
.data(graph.nodes, function (d) { return d.title });
var node_enter = node.enter().append("g")
.attr("class", d=>"page "+this.wiki.get_ns_classname(d.ns))
.on("click", d => {
this.set_active_node(d.title);
})
.on("mouseover", function (d) {
// console.log("mouseover", this);
select(this).classed("mouse", true);
})
.on("mouseout", function (d) {
// console.log("mouseout", this);
select(this).classed("mouse", false);
})
.call(drag()
.on("start", this.dragstarted.bind(this))
.on("drag", this.dragged.bind(this))
.on("end", this.dragended.bind(this)));
node_enter.append("circle")
.attr("r", 6);
node_enter.append("text")
.text(d => d.title)
.attr("x", 10);
//node_enter.append("title")
// .text(function(d) { return d.title; });
node = node_enter.merge(node);
link = link_enter.merge(link);
node.classed("active", d=>d.active);
this.simulation
.nodes(graph.nodes)
.on("tick", ticked);
this.simulation.force("link")
.links(graph.links);
this.simulation.force("radial").radius(d => d.linked ? null : 200);
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
// node
// .attr("cx", function(d) { return d.x; })
// .attr("cy", function(d) { return d.y; });
node
.attr("transform", d => `translate(${d.x},${d.y})`);
}
this.simulation.alphaTarget(0.3).restart();
}
link_key (a, b) {
return (a < b) ? ("link_"+a+"_"+b) : ("link_"+b+"_"+a);
}
walk (node, links) {
var links_seen = {};
// var node = this.ensure_node(nodename);
node.all_links.forEach(x => {
var link_key = this.link_key(node.title, x.title);
if (!links_seen[link_key]) {
links.push({source: node, target: x});
links_seen[link_key] = true;
}
})
return;
}
set_active_title (title) {
this.set_active_node(title);
}
async set_active_node (page) {
if (typeof(page) === "string") {
page = this.wiki.get_page_by_title(page)
}
if (page === this.active_page) {
// console.log("page is already the active page", page, this.active_page);
return;
}
if (this.active_page) {
this.active_page.active = false;
var lkey = this.link_key(this.active_page.title, page.title),
source = (this.active_page.title < page.title) ? this.active_page : page,
target = (this.active_page.title < page.title) ? page : this.active_page;
this.historylinks[lkey] = {source: source, target: target};
}
// n = this.ensure_node(pagetitle);
this.active_page = page;
this.active_page.active = true;
this.load(this.active_page);
this.events.emit("page", this.active_page.title);
}
async load (page) {
console.log("loading", page.title);
var links = await page.get_links(),
backlinks = await page.get_linkshere(),
alllinks = this.wiki.union(links, backlinks);
page.all_links = alllinks;
// console.log("alllinks", alllinks);
var graph = {};
graph.nodes = values(this.wiki.pages_by_title);
graph.links = [];
this.walk(page, graph.links);
// activate historylinks
values(this.historylinks).forEach(x => {
graph.links.push(x);
})
// graph.links = titles.map(t => ({source: pagetitle, target: t}));
this.update_graph(graph);
// console.log("GOT DATA", titles);
// return titles;
}
}
// http://erg.activearchives.org/mw/api.php?action=query&prop=links&titles=Bienvenue_%C3%A0_l%E2%80%99erg
// http://erg.activearchives.org/w/api.php?action=query&prop=info&titles=Main%20Page
// Bienvenue_à_lerg
// http://erg.activearchives.org/mw/index.php/Bienvenue_%C3%A0_l%E2%80%99erg

285
src/wikimapsimple.js Normal file
View File

@@ -0,0 +1,285 @@
import { event, select, selectAll} from 'd3-selection';
import { values, set, map } from 'd3-collection';
import { drag } from 'd3-drag';
import { zoom } from 'd3-zoom';
import { forceSimulation, forceLink, forceManyBody, forceCenter, forceX, forceY, forceRadial } from 'd3-force';
import { Wiki, Page } from './wiki.js';
import EventEmitter from 'eventemitter3';
import { json } from 'd3-fetch';
import { ForceNet } from './forcenet.js';
export class SimpleMap {
constructor (symbols) {
var width = 600,
height = 600;
this.events = new EventEmitter();
this.active_page = null;
// this.nodes = {};
this.symbols = symbols;
this.net = new ForceNet(symbols);
this.net.on("nodeclick", this.nodeclick.bind(this));
// this.simulation = forceSimulation()
// .velocityDecay(0.1)
// .force("link", forceLink().id(d => d.title))
// .force("charge", forceManyBody())
// .force("radial", forceRadial(180, width/2, height/2));
// // .force("center", forceCenter(width / 2, height / 2));
this.svg = null;
this.historylinks = {};
this.links = null;
this.highlight_category = null;
this.show_history = false;
}
nodeclick (d, elt) {
console.log("nodeclick", d, elt, this);
this.set_active_node(d, elt);
}
init_svg (svg) {
this.net.init_svg(svg);
}
async load_json (source) {
var data = await json(source);
// index the nodes by title, init link-arity count
var index = {};
this.nodes_by_title = index;
for (let i=0, l=data.nodes.length; i<l; i++) {
let node = data.nodes[i];
node.count = 0;
index[node.title] = node;
}
this.nodes = data.nodes;
var use_links = [];
data.links = data.links.forEach(x => {
var source = index[x.source],
target = index[x.target];
if (source === undefined) {
console.log("bad source", x.source);
return;
}
if (target === undefined) {
console.log("bad target", x.target);
return;
}
source.count += 1;
target.count += 1;
use_links.push({ source: source, target: target });
});
data.links = use_links;
this.links = data.links;
// console.log("data", data);
// calculate the node sizes (link arity)
this.net.update_graph(data);
}
get_symbol_image_path (cname) {
var symbol = this.symbols[cname];
if (symbol) {
let hpos = symbol.indexOf("#"),
rest = symbol.substr(hpos+1);
rest = rest.replace(/'/g, '');
return "img/"+rest+".png";
}
}
async load_cats (src, elt) {
var data = await json(src);
console.log("indexing categories by title");
var cats_by_title = {};
for (let i=0, l=data.length; i<l; i++) {
let cat = data[i];
cats_by_title[cat.title] = cat;
cat.pages = [];
cat.tcount = 0;
}
// index categories
console.log("indexing categories");
for (let key in this.nodes_by_title) {
let node = this.nodes_by_title[key];
// console.log("key", key, node.cats);
for (let j=0, jl=node.cats.length; j<jl; j++) {
let cname = node.cats[j],
cat = cats_by_title[cname];
if (cat) {
// increment the category + parents
cat.pages.push(node);
cat.tcount += 1
while (cat.parent) {
cat = cats_by_title[cat.parent];
cat.tcount += 1;
}
} else {
console.log("Warning, unknown category", cname);
}
}
}
console.log("pre filter", data.length);
data = data.filter(d => d.tcount > 0)
console.log("post filter", data.length);
console.log("load_cats.data", data, elt);
var cat = select(elt)
.selectAll("div.cat")
.data(data)
.enter()
.append("div")
.attr("class", "cat");
cat.classed("icon", d => this.get_symbol_image_path(d.title));
cat.append("span").attr("class", "icon").filter(d=> this.get_symbol_image_path(d.title)).style("background-image", d => "url("+this.get_symbol_image_path(d.title)+")");
cat.append("span").attr("class", "spacing").html(d => {
var d = d.depth,
ret = "";
while(d) {
ret += "&nbsp;&nbsp;&nbsp;&nbsp;";
d-=1;
}
return ret;
});
cat.append("a").attr("class", "label").html(d => d.title).attr("href", "#").on("click", d => {
event.preventDefault();
this.category_click(d);
})
cat.append("span").attr("class", "count").html(d => d.tcount)
}
category_click (d) {
console.log("category click", d);
if (this.highlight_category) {
// cleanup old pages
this.highlight_category.pages.forEach(d => d.highlight = false);
}
this.highlight_category = d;
this.highlight_category.pages.forEach(d => d.highlight = true);
this.net.update_nodes();
// set highlight category...
// all nodes with this category get .highlight = true
// make a category index ?!
}
on (message, callback, context) {
this.events.on(message, callback, context);
}
/* OLD STYLE with node.all_links
walk (node, links) {
var links_seen = {};
// var node = this.ensure_node(nodename);
node.all_links.forEach(x => {
var link_key = this.link_key(node.title, x.title);
if (!links_seen[link_key]) {
links.push({source: node, target: x});
links_seen[link_key] = true;
}
})
return;
}
*/
set_active_title (title) {
this.set_active_node(title);
}
activate_linked_nodes (page, active) {
// deactivate linked links/nodes
for (let i=0, l=this.links.length; i<l; i++) {
let link = this.links[i];
if (link.source == page || link.target == page) {
link.active2 = active;
link.source.active2 = active;
link.target.active2 = active;
}
}
}
async set_active_node (page) {
if (typeof(page) === "string") {
let pagename = page;
page = this.nodes_by_title[page];
if (!page) {
console.log("wikimap.set_active_node: page not found", pagename);
}
}
if (page === this.active_page) {
// console.log("page is already the active page", page, this.active_page);
return;
}
if (this.active_page) {
this.active_page.active = false;
// deactivate linked links/nodes
this.activate_linked_nodes(this.active_page, false);
// ENSURE HISTORY LINK TO PREVIOUS NODE AND CURRENT
var lkey = this.net.link_key(this.active_page.title, page.title),
source = (this.active_page.title < page.title) ? this.active_page : page,
target = (this.active_page.title < page.title) ? page : this.active_page;
this.historylinks[lkey] = {source: source, target: target, type:"history"};
}
this.active_page = page;
this.active_page.active = true;
this.activate_linked_nodes(this.active_page, true);
this.events.emit("page", this.active_page.title);
this.net.update_nodes();
this.net.update_forces();
}
set_show_history (value) {
console.log("wikimapsimple.show_history", value);
if (this.show_history !== value) {
this.show_history = value;
if (this.show_history) {
let graph = {};
graph.nodes = this.nodes;
graph.links = this.links.slice();
for (var key in this.historylinks) {
graph.links.push(this.historylinks[key])
}
this.net.update_graph(graph);
} else {
let graph = {};
graph.nodes = this.nodes;
graph.links = this.links;
this.net.update_graph(graph);
}
}
}
/*
async load (page) {
console.log("loading", page.title);
var links = await page.get_links(),
backlinks = await page.get_linkshere(),
alllinks = this.wiki.union(links, backlinks);
page.all_links = alllinks;
// console.log("alllinks", alllinks);
var graph = {};
graph.nodes = values(this.wiki.pages_by_title);
graph.links = [];
this.walk(page, graph.links);
// activate historylinks
values(this.historylinks).forEach(x => {
graph.links.push(x);
})
// graph.links = titles.map(t => ({source: pagetitle, target: t}));
this.update_graph(graph);
// console.log("GOT DATA", titles);
// return titles;
}
*/
}
// http://erg.activearchives.org/mw/api.php?action=query&prop=links&titles=Bienvenue_%C3%A0_l%E2%80%99erg
// http://erg.activearchives.org/w/api.php?action=query&prop=info&titles=Main%20Page
// Bienvenue_à_lerg
// http://erg.activearchives.org/mw/index.php/Bienvenue_%C3%A0_l%E2%80%99erg

6
src/wikimaptotal.js Normal file
View File

@@ -0,0 +1,6 @@
import { SimpleMap } from './wikimapsimple.js';
// gather ALL nodes + links from the wiki via the API
export { SimpleMap };