User:Whiteknight/bookpage.js

From Wikibooks, open books for an open world
Jump to navigation Jump to search
Note: After saving, changes may not occur immediately. Click here to learn how to bypass your browser's cache.
  • Mozilla / Firefox / Safari: hold down Shift while clicking Reload, or press Ctrl-Shift-R (Cmd-Shift-R on Apple Mac);
  • Internet Explorer: hold Ctrl while clicking Refresh, or press Ctrl-F5;
  • Konqueror: simply click the Reload button, or press F5;
  • Opera users may need to completely clear their cache in Tools→Preferences.
// <source lang="javascript">
//class constructor for BookPage
function BookPage(name) {
  if(name.match(/\s$/)) name = name.substring(0, name.length - 1);
  this.pagename = name;
  this.headings = new Array();
  this.subpages = new Array();
  this.formspan = wk.makeElement('div');
  this.parent = null;                     //subpage parent, null if root
  this.parent2 = null;                    //heading parent, null if not in a heading.
  this.pagetext = "";                     //initial text of a page
  this.comments = "";                     //comment text which is in the outline only
  this.collapse = vbd.defaultcollapse;    //flag to collapse part of the tree
  this.inherittext = vbd.defaultinherit;  //flag to inherit text from parent
  this.box = null;
}

//add a new heading to the page
BookPage.prototype.addHeading = function(heading) {
  heading.parent = this;
  this.headings.push(heading);
}

//add a new subpage to the page
BookPage.prototype.addSubpage = function(subpage) {
  subpage.parent = this;
  this.subpages.push(subpage);
}

//create a special node for a page that is to be created but does not appear in the outline
BookPage.prototype.makeSpecialPageNode = function(name) {
  // Create a node that thinks it's a subpage, but isn't included in the list of subpages of the parent.
  var node = new BookPage(name);
  node.parent = this;
  node.formspan = null;
  return node;
}

//Show a little animation when things are happening behind the scenes
BookPage.prototype.makeLoadingNotice = function(text) {
  this.formspan.innerHTML = "<img src=\"http://upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif\"> <big>" + text + "...</big>"
}

//make an editor, according to the users preferences
BookPage.prototype.makeEditInterface = function(page, deftext, defsummary) {
  if(vbd.useexternaledit) wk.showEditWindowExternal(page, deftext, defsummary);
  else {
    var self = this;
    this.makeLoadingNotice("Loading");
    wk.loadEditPage(page, function (xml) {
      wk.showEditWindowSmall(xml, self.formspan, deftext, defsummary);
      self.closeButton();
    });
  }
}

//create the part of the outline for the page text and comments
BookPage.prototype.getTextNode = function () {
  var self = this;
  var div = wk.makeElement('small', null, 
    ((this.pagetext.length == 0)?('[click here to edit page text]'):(this.pagetext)));
  div.innerHTML = div.innerHTML + ((this.inherittext && !this.isRoot())?(' (inherited)'):(""))
  var cmts = wk.makeElement('small', {style:'color: #666666'}, 
    ((this.comments.length == 0)?('[click here to edit comments]'):(this.comments)));
  div.onclick = function() {
    var edit = wk.makeElement('textarea', {rows:10});
    edit.value = self.pagetext;
    self.formspan.innerHTML = 'Enter some text for the page:';
    self.formspan.appendChild(edit);
    if(!self.isRoot()) {
      var inherit = wk.makeElement('input', {type:"checkbox"});
      inherit.checked = self.inherittext;
      wk.appendChildren(self.formspan, [inherit, 'inherit text from parent?']);
    }
    self.formspan.appendChild(wk.makeButton('', 'Save', function() {
      if(!self.isRoot()) self.inherittext = inherit.checked;
      self.pagetext = edit.value;
      if(self.pagetext.match(/^\s*$/)) self.pagetext = "";
      self.formspan.innerHTML = "";
      vbd.visual();
    }));
    self.closeButton();
    edit.focus(); 
  }
  div.style.cursor = "pointer";
  cmts.onclick = function() {
    var edit = wk.makeElement('textarea', {rows:10});
    edit.value = self.comments;
    self.formspan.innerHTML = 'Enter some comments:';
    self.formspan.appendChild(edit);
    self.formspan.appendChild(wk.makeButton('', 'Save', function() {
      self.comments = edit.value;
      if(self.comments.match(/^\s*$/)) self.comments = "";
      self.formspan.innerHTML = "";
      vbd.visual();
    }));
    self.closeButton();
    edit.focus(); 
  }
  cmts.style.cursor = "pointer";
  return wk.makeElement('table', {width:"100%", style:"background-color: transparent"}, [
    wk.makeElement('td', {width:"50%", style:"padding-left: 2em;"}, div),
    wk.makeElement('td', {style:"padding-left: 2em;"}, cmts)
  ]);
}

//remove a subpage
BookPage.prototype.removeSubpage = function(subpage) {
  for(var i = 0; i < this.subpages.length; i++) {
    if(this.subpages[i] != subpage) continue;
    this.subpages.splice(i, 1);
    return;
  }
}

//remove a heading
BookPage.prototype.removeHeading = function(heading) {
  for(var i = 0; i < this.headings.length; i++) {
    if(this.headings[i] != heading) continue;
    this.headings.splice(i, 1);
    return;
  }
}

//create a link to modify the list of headings.
BookPage.prototype.makeHeadingsLink = function () {
  var link = wk.makeElement('a', null, ['Headings']);
  var self = this;
  link.onclick = function() {
    var text = "";
    for(var i = 0; i < self.headings.length; i++) {
      text = text + self.headings[i].label + "\n";
    }
    var old = vbd.CopyArray(self.headings);
    var edit = wk.makeElement('textarea', {rows:10, cols:50});
    edit.value = text;
    self.formspan.innerHTML = "";
    self.formspan.appendChild(document.createTextNode(
      'Enter the names of all headings, one per line'));
    self.formspan.appendChild(edit);
    self.formspan.appendChild(wk.makeButton('', 'Save', function() {
      self.headings.length = 0;
      var pages = edit.value.split("\n");
      for(var i = 0; i < pages.length; i++) {
        if(pages[i].match(/^\s*$/)) continue;
        var stat = vbd.FindHeadNameInArray(old, pages[i]);
        if(stat != -1) {
          self.addHeading(old[stat]);
        } else {
          self.addHeading(new PageHeading(vbd.forceFirstCaps(pages[i])));
        }
      }
      self.formspan.innerHTML = "";
      vbd.visual();
    }));
    self.closeButton();
    edit.focus();
  }
  return(link);
}

//create a link to add a new subpage
BookPage.prototype.makeSubpagesAddLink = function () {
  var link = wk.makeElement('a', null, [" [ + ]"]);
  var self = this;
  link.onclick = function () {
    self.addSubpage(new BookPage(vbd.defpagename + " " + vbd.newpagecnt));
    vbd.newpagecnt++;
    vbd.visual();
  }
  return link;
}

//create a link to add a new heading
BookPage.prototype.makeHeadingsAddLink = function () {
  var link = wk.makeElement('a', null, [" [ + ]"]);
  var self = this;
  link.onclick = function () {
    self.addHeading(new PageHeading(vbd.defheadname + " " + vbd.newheadcnt));
    vbd.newheadcnt++;
    vbd.visual();
  }
  return link;
}

//make a list to modify the list of subpages
BookPage.prototype.makeSubpagesLink = function () {
  var link = wk.makeElement('a', null, ['Subpages']);
  var self = this;
  link.onclick = function() {
    var text = "";
    for(var i = 0; i < self.subpages.length; i++) {
      text = text + self.subpages[i].pagename + "\n";
    }
    var old = vbd.CopyArray(self.subpages);
    var edit = wk.makeElement('textarea', {rows:10, cols:50});
    edit.value = text;
    self.formspan.innerHTML = "";
    self.formspan.appendChild(document.createTextNode('Enter subpages, one per line'));
    self.formspan.appendChild(edit);
    self.formspan.appendChild(wk.makeButton('', 'Save', function() {
      self.subpages.length = 0;
      var pages = edit.value.split("\n");
      for(var i = 0; i < pages.length; i++) {
        if(pages[i].match(/^\s*$/)) continue;
        var stat = vbd.FindPageNameInArray(old, pages[i]);
        if(stat != -1) self.addSubpage(old[stat]);
        else self.addSubpage(new BookPage(vbd.forceFirstCaps(pages[i])));
      }
      self.formspan.innerHTML = "";
      vbd.visual();
    }));
    self.closeButton();
    edit.focus();
  }
  return(link);
}

//display the title of the page
BookPage.prototype.getTitleNode = function () {
  var self = this;
  var container = wk.makeElement('span');
  var collapse = wk.makeElement('small', null, 
    ((this.collapse == 0)?('[-] '):('[+] ')));
  collapse.style.cursor = "pointer";
  collapse.onclick = function () {
    self.collapse = (self.collapse == 1)?(0):(1);
    vbd.visual();
  }
  var span = wk.makeElement('big', {style:"font-weight: bold;"}, this.pagename);
  span.style.cursor = "pointer";
  span.onclick = function() {
    var edit = wk.makeElement('input', {type:'text', value:(self.pagename), size:50});
    self.formspan.innerHTML = "";
    wk.appendChildren(self.formspan, ['Enter the new name: ', edit, wk.makeButton('', 'Rename', function() {
      self.formspan.innerHTML = "Renaming...";
      var pagename = vbd.forceCaps(edit.value, self.isRoot());
      if(self.isRoot()) wk.loadWikiText(pagename, function (text) { 
        if(text.length != 0) self.formspan.innerHTML = "<b>Warning:</b> " + pagename + " already exists. Check it before saving over it.";
        else self.formspan.innerHTML = "";
      });
      else self.formspan.innerHTML = "";
      self.pagename = pagename.replace("\n", "");
      vbd.visual();
    })]);
    if(!self.isRoot()) self.formspan.appendChild(wk.makeButton('', 'Delete', function () {
      if(confirm("delete page '" + self.pagename + "'?")) {
        if(self.parent2 == null) self.parent.removeSubpage(self); 
        else self.parent2.removeSubpage(self);
        vbd.visual();
      }
    }));
    self.closeButton();
  }
  if(this.subpages.length != 0 || this.headings.length != 0) container.appendChild(collapse);
  container.appendChild(span);
  return(container);
}

//functions for dealing with recursion
BookPage.prototype.isRoot = function () { 
  return (this.parent == null)?(1):(0);
}
BookPage.prototype.getFullName = function() {
  if(this.isRoot()) return this.pagename;
  return this.parent.getFullName() + "/" + this.pagename;
} 
BookPage.prototype.getDepth = function() {
  if(this.isRoot()) return 0;
  return this.parent.getDepth() + 1;
}
BookPage.prototype.getBookName = function () {
  if(this.isRoot()) return this.pagename;
  return this.parent.getBookName();
}

//make wikitext of a page, using nested asterisks, as required
BookPage.prototype.makeWikitextLinkStars = function (parent) {
  var depth = this.getDepth() - ((parent != null)?(parent.getDepth()):(0));
  var stars = "";
  for(var i = 0; i < depth; i++) {
    stars = stars + "*";
  }
  var text = stars + "[[" + this.getFullName() + "|" + this.pagename + "]]\n";
  text = text.replace(/\n/g, "") + "\n";
  for(var i = 0; i < this.subpages.length; i++) {
    text += this.subpages[i].makeWikitextLinkStars(parent);
  }
  for(var i = 0; i < this.headings.length; i++) {
    text += this.headings[i].makeWikitextLinkStars(parent);
  }
  return text;
}

//make links for a subpage, as formattined for a template
BookPage.prototype.makeTemplateLinks = function () {
  var text = "[[" + this.getFullName() + "|" + this.pagename + "]] - ";
  for(var i = 0; i < this.subpages.length; i++) {
     text += this.subpages[i].makeTemplateLinks() + " - ";
  }
  for(var i = 0; i < this.headings.length; i++) {
     text += this.headings[i].makeTemplateLinks();
  }
  return text;
}    

//make a link for a page without nested asterisks
BookPage.prototype.makeWikitextLink = function () {
  return "*[[" + this.getFullName() + "|" + this.pagename + "]]";
}

BookPage.prototype.getPageText = function() {
  if(!this.inherittext || this.isRoot()) return this.pagetext;
  return this.parent.getPageText() + "\n" + this.pagetext;
}

//make the wikitext of an entire page
BookPage.prototype.makeWikitextPage = function () {
  var text = "{{" + this.getBookName() + "/Page}}\n\n";
  for(var i = 0; i < this.subpages.length; i++) {
    text = text + this.subpages[i].makeWikitextLinkStars(this);
  }
  text += this.getPageText() + "\n";
  for(var i = 0; i < this.headings.length; i++) {
    text = text + this.headings[i].makeWikitext(this) + "\n";
  }
  text = text + "[[Category:" + this.getBookName() + "]]\n";
  return text;
}

//make the wikitext for the special root page
BookPage.prototype.makeWikitextRoot = function () {
  var text = "{{New book}}\n{{" + this.pagename + "/Page}}\n" +
    "{{Reading level|" + vbd.readingLevels[vbd.readingLevel] + "}}\n\n";
  text += "== Preface ==\n" + this.pagetext + "\n";
  text += "== Table of Contents ==\n\n";
  if(vbd.useintroduction) text += "*[[" + this.pagename + "/Introduction|Introduction]]\n";
  for(var i = 0; i < this.subpages.length; i++) {
    text += this.subpages[i].makeWikitextLinkStars();
  }
  text += "\n";
  for(var i = 0; i < this.headings.length; i++) {
    text += this.headings[i].makeWikitext() + "\n";
  }
  text += "\n== Resources and Licensing ==\n\n";
  if(vbd.useresources) text += "*[[" + this.pagename + "/Resources|Resources]]\n";
  if(vbd.uselicensing) text += "*[[" + this.pagename + "/Licensing|Licensing]]\n";
  text += "\n[[Category:" + this.getBookName() + "]]\n";
  text += "{{Subject";
  for(var i = 0; i < vbd.subjects.length; i++) {
    text += "|" + vbd.subjects[i];
  }
  text += "}}\n{{Alphabetical|" + this.pagename.substr(0, 1) + "}}\n";
  return text;
}

//make the print version text of the root page
BookPage.prototype.makePrintVersionRoot = function() {
  var text = "{{Print version notice}}\n" +
    "__NOTOC__ __NOEDITSECTION__\n" +
    "<br style=\"page-break-after: always\">\n" +
    "{{:" + this.getBookName() + "/Cover}}\n" +
    "<br style=\"page-break-after: always\">\n\n";
  if(vbd.useintroduction) text += "{{Print chapter heading|Introduction}}\n";
  for(var x = 0; x < this.subpages.length; x++) {
    text += this.subpages[x].makePrintVersionText();
  }
  for(var x = 0; x < this.headings.length; x++) {
    text += this.headings[x].makePrintVersionText();
  }
  text = text + "{{Print unit page|Resources and Licensing}}\n\n";
  if(vbd.useresources) text += "{{Print chapter heading|Resources}}\n"
  if(vbd.uselicensing) text += "{{Print chapter heading|Licensing}}\n";
  text += "\n== License: GFDL ==\n\n{{:GFDL}}\n\n";
  return text;
}

//make the print version text of a subpage
BookPage.prototype.makePrintVersionText = function () {
  return "{{Print chapter heading|" + this.pagename + "}}\n" + 
    "{{:" + this.getFullName() + "}}\n\n";
}

//make the wikitext for any page
BookPage.prototype.makeWikitextAll = function()  {
  if(this.isRoot()) return this.makeWikitextRoot();
  return this.makeWikitextPage();
}

//make a link to view the wikitext of the current page
BookPage.prototype.makeDisplayLink = function () {
  var link = wk.makeElement('a', null, ['Wikitext']);
  var self = this;
  link.onclick = function() {
    self.formspan.innerHTML = "<pre>" + self.makeWikitextAll() + "</pre>";
    self.closeButton();
  }
  return(link);
}

//make the collection text of a page
BookPage.prototype.makeCollectionText = function (subt) {
  var text = "";
  if(this.isRoot()) {
    if(subt == null || subt == "") subt = "A Book from English Wikibooks";
    text = "== " + this.pagename + " ==\n=== " + subt + " ===\n";
    if(vbd.usecollectionpreface) text +=  ":[[Wikibooks:Collections Preface]]\n";
    if(vbd.useintroduction) text += ":[[" + this.pagename + "/Introduction|Introduction]]\n";
  }
  else text = ":[[" + this.getFullName() + "|" + this.pagename + "]]\n";
  for(var i = 0; i < this.subpages.length; i++) {
    text = text + this.subpages[i].makeCollectionText();
  }
  for(var i = 0; i < this.headings.length; i++) {
    text = text + this.headings[i].makeCollectionText();
  }
  if(this.isRoot()) {
    text += "\n;Resources and Licensing\n";
    if(vbd.useresources) text += ":[[" + this.pagename + "/Resources|Resources]]\n";
    if(vbd.uselicensing) text += ":[[" + this.pagename + "/Licensing|Licensing]]\n";
    text += "\n[[Category:Collections]]\n";
  }
  return text;
}

//make a button to close an open formspan
BookPage.prototype.closeButton = function () {
  var self = this;
  this.formspan.appendChild(wk.makeButton('', 'Close', function() {
    self.formspan.innerHTML = "";
  }));  
}

//add all subpages of the current page to list
BookPage.prototype.listAllSubpages = function(list) {
  if(this.subpages.length) {
    for(var i = this.subpages.length - 1; i >= 0; i--) {
      list = this.subpages[i].listAllSubpages(list);
      list.push(this.subpages[i]);
    }
  }
  if(this.headings.length) {
    for(var i = this.headings.length - 1; i >= 0; i--) {
      list = this.headings[i].listAllSubpages(list);
    }
  }
  return list;
}

//a link to create the book using AJAXy magic
BookPage.prototype.makeAutomateLink = function () {
  var automate = wk.makeElement('a', null, ['Create']);
  var self = this;
  automate.onclick = function() {
    if(!wk.testUserPermissions("administrator")) {
      self.formspan.innerHTML = "<b>ERROR:</b> You must be an administrator to use this feature.";
      return;
    }
    wk.loadWikiText(self.pagename, function (text) { 
      if(text.length != 0) self.formspan.innerHTML = "<b>ERROR:</b> The page " + self.pagename + " already exists. "
        + "This function cannot be used to overwrite existing pages.";
      else {
        var list = self.listAllSubpages(new Array());
        if(vbd.useintroduction) list.push(self.makeSpecialPageNode("Introduction"));
        if(vbd.useresources) list.push(self.makeSpecialPageNode("Resources"));
        if(vbd.uselicensing) list.push(self.makeSpecialPageNode("Licensing"));
        if(confirm("WARNING: This operation will automatically create " + (list.length + 1) + " pages. " +
          "You are responsible for any edits made by this tool, and any pages that it creates. " +
          "Do you want to continue?")) self.automaticCreation(list); 
      }
    });
  }
  return automate;
}

//create a page using AJAXy magic
BookPage.prototype.automaticCreation = function(list) {
  if(this.formspan) this.makeLoadingNotice("Creating");
  else wk.appendChildren(this.parent.formspan, [wk.makeElement('br'), "Creating " + this.pagename + "..."]);
  var text = this.makeWikitextAll();
  var self = this;
  wk.postEdit(this.getFullName(), text, "Automatic page creation", {callback: function() {
    /* XXX: because this is a recursive callback, it could create quite a large mess on the VMs stack for
            very large books. It should be possible with some kind of count parameter to loop and unwind the
            stack partially after every N edits. Either implement some kind of a sentinel like this, or
            find a different method for automating the process.
    */
    if(self.formspan) {
      self.formspan.innerHTML = "<big>Done.</big> &mdash; ";
      self.formspan.appendChild(wk.wikiLink(self.getFullName(), "View"));
    } else {
      wk.appendChildren(vbd.pageTree.formspan, ["Done. - ", wk.wikiLink(self.getFullName(), "View")]);
    } 
    if(list.length) {
       var next = list.pop();
       next.automaticCreation(list);
    }
  }});
}

//make a link to edit the text of the page
BookPage.prototype.makeEditLink = function () {
  var link = wk.makeElement('a', null, ['Edit']);
  var self = this;
  link.onclick = function() {
    self.makeEditInterface(self.getFullName(), self.makeWikitextAll(), self.pagename + ": Created by Whiteknight's Visual Book Designer");
  }
  return(link);
}

//prepare the text to save a subpage from the outline.
// XXX: Eventually, should probably save the structures directly in JSON for easy loading.
BookPage.prototype.makeSaveText = function () {
  var depth = this.getDepth();
  var stars = "";
  for(var i = 0; i < depth; i++) {
    stars = stars + "*";
  }
  var text = stars + this.pagename + "\n";
  if(this.pagetext.length != 0) {
    var pagetext = this.pagetext;
    pagetext.replace(/\n/g, "\n&");
    text += "&" + this.pagetext + "\n";
  }
  if(this.comments.length != 0) {
    var comments = this.comments;
    comments.replace(/\n/g, "\n%");
    text += "%" + this.comments + "\n";
  }
  for(var i = 0; i < this.subpages.length; i++)
  {
    text += this.subpages[i].makeSaveText();
  }
  for(var i = 0; i < this.headings.length; i++) {
    text += this.headings[i].makeSaveText();
  }
  return text; 
}

//make a link to save the outline
BookPage.prototype.makeSaveLink = function () {
  var link = wk.makeElement('a', null, ['Save']);
  var self = this;
  link.onclick = function() {
    var page = "User:" + wgUserName + "/" + self.pagename;
    var edit = wk.makeElement('input', {type:'text', value:page, size:50});
    self.formspan.innerHTML = 'Enter the page name to save at: ';
    self.formspan.appendChild(edit);
    self.formspan.appendChild(wk.makeButton('', 'Save', function() {
      self.makeEditInterface(edit.value, function(t) {
        return vbd.killOldSaves(t) + "{{User:Whiteknight/SavedOutlineStart|" + wk.getDate() + "}}\n\n" +
          self.makeSaveText() + "\n\n{{User:Whiteknight/SavedOutlineEnd}}\n";
      }, self.pagename + ": Saving Visual Book Designer Outline");
    }));
    self.closeButton();
    edit.focus();
  }
  return(link);
}

//make a link to load a saved outline
BookPage.prototype.makeLoadLink = function () {
  var link = wk.makeElement('a', null, ['Load']);
  var self = this;
  link.onclick = function() {
    var page = "User:" + wgUserName + "/" + self.pagename;
    var edit = wk.makeElement('input', {type:'text', value:page, size:50});
    wk.appendChildren(self.formspan, ['Enter the page name to load from: ', edit, wk.makeButton('', 'Load', function() {
      page = edit.value;
      self.makeLoadingNotice("Loading");
      wk.loadWikiText(page, function (text) { 
        var start = text.indexOf('{{User:Whiteknight/SavedOutlineStart}}');
        var end = text.indexOf('{{User:Whiteknight/SavedOutlineEnd}}');
        if(start == -1 || end == -1) {
          self.formspan.innerHTML = "";
          self.formspan.appendChild(wk.makeElement('font', {color:'#FF0000'}, [
            'Cannot load outline from ' + page 
          ]));
          self.closeButton();
        } else vbd.loadNodeTree(text.substring(start + 38, end));
      });
    })]);
    self.closeButton();
    edit.focus();
  }
  return(link);
}

//make a link to load the TOC from an existing book
BookPage.prototype.makeLoadTOCLink = function () {
  var link = wk.makeElement('a', null, ['Load TOC']);
  var self = this;
  link.onclick = function() {
    var page = self.pagename;
    var edit = wk.makeElement('input', {type:'text', value:page, size:50});
    self.formspan.innerHTML = 'Enter the name of the book to load: ';
    self.formspan.appendChild(edit);
    self.formspan.appendChild(wk.makeButton('', 'Load', function() {
      page = edit.value;
      wk.loadWikiText(page, function (text) { 
        vbd.loadNodeTreeTOC(text, page);
      });
    }));
    self.closeButton();
    edit.focus();
  }
  return(link);
}

BookPage.prototype.makeSaveCollectionLink = function (key) {
  var link = wk.makeElement('a', null, ['Save']);
  var self = this;
  link.onclick = function() {
    var page = "";
    if(key == "personal") page = "User:" + wgUserName + "/Collections/" + self.pagename;
    else page = "Wikibooks:Collections/" + self.pagename;
    var subt = wk.makeElement('input', {type:'text', size:50});
    var edit = wk.makeElement('input', {type:'text', value:page, size:50});
    self.formspan.innerHTML = "";
    wk.appendChildren(self.formspan, ["Enter a subtitle for the book: ", subt,
      wk.makeElement('br'), 
      'Enter the ' + key + ' collection name to save at: ', edit]);
    self.formspan.appendChild(wk.makeButton('', 'Save', function() {
      self.makeEditInterface(edit.value, self.makeCollectionText(subt.value), ": Saving " + key + " collection");
    }));
    self.closeButton();
    edit.focus();
  }
  return(link);
}

BookPage.prototype.makeLoadCollectionLink = function (type) {
  var link = wk.makeElement('a', null, ['Load']);
  var self = this;
  link.onclick = function() {
    var page = "";
    if(type == 'personal') page = "User:" + wgUserName + "/Collections/";
    else page = "Wikibooks:Collections/";
    var edit = wk.makeElement('input', {type:'text', size:50});
    self.formspan.innerHTML = "";
    wk.appendChildren(self.formspan, ["Enter the name of the " + type + " collection to load: ", edit,
      wk.makeButton('', 'Load', function () {
        wk.loadWikiText(page + edit.value, function (text) { 
          vbd.loadNodeTreeCollection(text, page);
        });
      })
    ]);
  }
  return link;
}

//make the text for a heading template
BookPage.prototype.makeTemplate = function () {
  var self = this;
  self.makeEditInterface("Template:" + this.pagename + "/Page", vbd.makeTemplateText(), self.pagename + ": Creating page head template");
}

BookPage.prototype.makeFileLink = function() {
  var self = this;
  var disp = wk.makeElement('a', null, ['View']);
  disp.onclick = function () {
    self.formspan.innerHTML = "<b>Warning:</b> Print versions are deprecated. Please use <b>Collections</b> instead.<br>" +
      "<pre>" + self.makePrintVersionRoot().replace(/</g, "&lt;") + "</pre>";
    self.closeButton();
  }
  var saveprint = wk.makeElement('a', null, ['Save']);
  saveprint.onclick = function() {
    self.makeEditInterface(self.getFullName() + "/Print Version", self.makePrintVersionRoot(),
      self.pagename + ": Print version created by Whiteknight's Visual Book Designer");
  }
  var link = wk.makeElement('a', null, ['File']);
  var self = this;
  link.onclick = function () {
    self.formspan.innerHTML = "";
    //vbd.createDropMenu(self.formspan, [
    wk.appendChildren(self.formspan, [
      "Select whether you want to load or save this outline:", wk.makeElement('br'),
      'Saved Outlines: ', self.makeSaveLink(), ' - ', self.makeLoadLink(), wk.makeElement('br'),
      'Personal Collection: ', self.makeSaveCollectionLink('personal'), ' - ', self.makeLoadCollectionLink('personal'), wk.makeElement('br'),
      'Public Collection: ', self.makeSaveCollectionLink('community'), ' - ', self.makeLoadCollectionLink('community'), wk.makeElement('br'),
      'Existing Books: ', self.makeLoadTOCLink(), ' - ', self.makeAutomateLink(), wk.makeElement('br'), 
      'Print versions: ', disp, ' - ', saveprint, wk.makeElement('br'), 
    ]);
    self.closeButton();
  }
  return link;
}

//make additional links for the main page
BookPage.prototype.makeRootMenu = function() {
  if(!this.isRoot()) { return document.createTextNode(' - '); }
  var box = wk.makeElement('span', null, [" - "]);
  var nav = wk.makeElement('a', null, ['Templates']);
  var self = this;
  nav.onclick = function () {
    self.formspan.innerHTML = '';
    var temps = wk.makeElement('select', {size:"1"});
    for(var i = 0; i < vbd.templates.length; i++) {
      temps.appendChild(wk.makeElement("option", {value:i}, vbd.templates[i][3]));
    }
    temps.onchange = function () { vbd.template = temps.selectedIndex; }
    temps.selectedIndex = vbd.template;
    var select = wk.makeElement('select', {size:"1"});
    for(var i = 0; i < vbd.readingLevels.length; i++) {
      select.appendChild(wk.makeElement("option", {value:i}, vbd.readingLevels[i]));
    }
    select.selectedIndex = vbd.readingLevel;
    select.onchange = function () { vbd.readingLevel = select.selectedIndex; }
    wk.appendChildren(self.formspan, [wk.makeElement('br'),
      "Select a page navigation template (",
      wk.wikiLink("User:Whiteknight/Book_Foundry#Header_Templates", "Help"), "): ",
      temps, wk.makeButton('WKVBDMakeTemplate', 'Make Template', function () { self.makeTemplate() }), wk.makeElement('br'),
      "Select the book ", wk.wikiLink("Wikibooks:Reading Levels", "reading level"), ": ", select]);
    if(typeof cg == 'object') {
      var topics = cg.makeSubjectSelect(10, true);
      cg.setSelected(vbd.subjects);
      topics.onchange = function () {
        vbd.subjects.length = 0;
        vbd.subjects = cg.returnAllSelected();
      }
      wk.appendChildren(self.formspan, [wk.makeElement('br'), 'Select major subjects for your book. ',
        'Hold ctrl to select multiple entries.', wk.makeElement('br'), topics, wk.makeElement('br')]);
    }
    self.closeButton();
  }
  var options = wk.makeElement('a', null, ['Options']);
  options.onclick = function () {
     self.formspan.innerHTML = "";
     wk.appendChildren(self.formspan, [
       vbd.makeOptionsCheckbox("defaultinherit"), "inherit page text by default", wk.makeElement('br'),
       vbd.makeOptionsCheckbox("defaultcollapse"), "collapse nodes by default", wk.makeElement('br'),
       vbd.makeOptionsCheckbox("useresources"), "add a resources page by default", wk.makeElement('br'),
       vbd.makeOptionsCheckbox("uselicensing"), "add a licensing page by default", wk.makeElement('br'),
       vbd.makeOptionsCheckbox("useintroduction"), "add an introduction page by default", wk.makeElement('br'),
       vbd.makeOptionsCheckbox("usecollectionpreface"), "add [[Wikibooks:Collections Preface]] to Collections by default", wk.makeElement('br'),
       vbd.makeOptionsCheckbox("commentsaspages"), "use page comments to create talk pages", wk.makeElement('br'),
       vbd.makeOptionsCheckbox("useexternaledit"), "make edits in an external tab or window", wk.makeElement('br')
     ]);
     self.closeButton();
  }
  var clear = wk.makeElement('a', null, ['Clear']);
  clear.onclick = function () {
    if(!confirm('Clear this outline? You will lose any unsaved work.')) return;
    vbd.clear();
    self.formspan.innerHTML = "outline cleared";
    vbd.visual();
  }
  wk.appendChildren(box, [
    nav, ' - ',
    this.makeFileLink(), ' - ',
    options, ' - ',
    clear, ' - ',
    wk.makeElement('small', null, [" (Version " + vbd.version + ")"]),
    wk.makeElement('br')
  ]);
  return box;
}

//make a subpage node in the outline
BookPage.prototype.makeNode = function() {
  this.box = wk.makeElement('div', null, [
    this.getTitleNode(),
    this.makeRootMenu(),
    wk.makeElement('small', null, [
      this.makeHeadingsLink(), this.makeHeadingsAddLink(), " - ",
      this.makeSubpagesLink(), this.makeSubpagesAddLink(), " - ",
      this.makeDisplayLink(), " - ",
      this.makeEditLink()
    ])
  ]);
  this.box.style.position = "relative";
  this.box.appendChild(wk.makeElement('br'));
  this.box.appendChild(this.getTextNode());
  this.box.appendChild(this.formspan);
  this.box.style.padding = "5px";
  this.box.style.marginBottom = "1em";
  var container = wk.makeElement('div');
  container.style.marginLeft = "2em";
  if(this.collapse == 0) {
    for(var i = 0; i < this.subpages.length; i++) {
      if(this.subpages[i] == null) continue;
      var node = this.subpages[i].makeNode();
      container.appendChild(node);
    }
    for(var i = 0; i < this.headings.length; i++) {
      container.appendChild(this.headings[i].makeNode());
      container.appendChild(wk.makeElement('br'));
    }
  }
  container.style.borderLeft = "1px dashed #000000";
  container.style.borderBottom = "1px dashed #000000";
  this.box.appendChild(container);
  var self = this;
  //<noinclude>
  //this.box.addEventListener("mouseup", function(event) {
  //  if(vbd.lastClicked == self)  {
  //    self.box.style.border = "none";
  //    vbd.lastClicked = null;
  //    vbd.visual();
  //    return;
  //  }
  //  if(vbd.lastClicked == null || event.target != self.box) return;
  //  document.removeEventListener("mousemove", vbd.moveMouse, false);
  //  var dragged = vbd.lastClicked;
  //  vbd.lastClicked.box.style.border = "none";
  //  vbd.lastClicked = null;
  //  if(dragged instanceof BookPage) {  //it's a bookpage
  //    if(dragged.parent2 != null) {
  //      dragged.parent2.removeSubpage(dragged);
  //      self.addSubpage(dragged);
  //    } else {
  //      dragged.parent.removeSubpage(dragged);
  //      self.addSubpage(dragged);
  //    }
  //  } else {  //it's a heading
  //    dragged.parent.removeHeading(dragged);
  //    self.addHeading(dragged);
  //  } 
  //  vbd.visual();
  //}, false);
  //this.box.addEventListener("mouseover", function(event) {
  //  if(vbd.lastClicked == null || event.target != self.box || vbd.lastClicked == self) return;
  //  self.box.style.border = "1px solid grey";
  //}, false);
  //this.box.addEventListener("mouseout", function(event) {
  //  if(vbd.lastClicked == null || event.target != self.box) return;
  //  self.box.style.border = "none";
  //}, false);
  //if(this.isRoot()) return this.box; //short circuit, the main book page can't be dragged.
  //this.box.addEventListener("mousedown", function(event) {
  //  if(vbd.lastClicked != null || event.target != self.box) return;
  //  self.box.style.border = "1px solid red";
  //  vbd.lastClicked = self;
  //  //vbd.startX = event.clientX;
  //  //vbd.startY = event.clientY;
  //  //vbd.offsetX = vbd.extractNumber(self.box.style.left);
  //  //vbd.offsetY = vbd.extractNumber(self.box.style.top);
  //  document.body.focus();
  //  //document.addEventListener("mousemove", vbd.moveMouse, false);
  //}, false);
  //</noinclude>
  return(this.box);
}
// </source>