মিডিয়াউইকি:Gadget-wkdesigner.js

উইকিবই থেকে

লক্ষ্য করুন: প্রকাশ করার পর, পরিবর্তনগুলো দেখতে আপনাকে আপনার ব্রাউজারের ক্যাশে পরিষ্কার করার প্রয়োজন হতে পারে।

  • ফায়ারফক্স / সাফারি: পুনরায় লোড-এ ক্লিক করার সময় শিফট টিপে ধরে রাখুন, অথবা হয় Ctrl-F5 বা Ctrl-R টিপুন (ম্যাকে ⌘-R টিপুন)
  • গুগল ক্রোম: Ctrl-Shift-R (ম্যাকে ⌘-Shift-R) টিপুন
  • ইন্টারনেট এক্সপ্লোরার / এজ: Ctrl ধরে রাখা অবস্থায় Refresh-এ ক্লিক করুন, অথবা Ctrl-F5 টিপুন
  • অপেরা: Ctrl-F5 টিপুন।
//Visual Book designer (vbd) class <source lang="javascript">
var vbd = {
  // Version numbers
  _require: 2.81,
  version:  3.73,
  bookpageversion: 0.00,
  pageheadversion: 0.00,

  // default name of all new books:
  pageTree: null,
  defName: 'New Book',
  defpagename: 'New Page',
  defheadname: 'New Heading', 
  newpagecnt: 1,
  newheadcnt: 1,

  //div IDs where the gadget is inserted into the page
  formspan: "WKVBDSpan",
  statspan: "WKVBDStatSpan",

  // Navigation templates and configuration parameters
  templates: new Array(
    ["Simple Header",    0, 0, "Simple header"],
    ["Page Nav Header",  1, 0, "Header with forward/back links"],
    ["Page Nav Header2", 1, 0, "Header with forward/back links (2)"],
    ["Page List Header", 0, 1, "Header with page list"],
    ["Page List Nav",    1, 1, "Header with page list and forward/back links"]
  ),
  template: 0,

  // Reading levels
  readingLevels: new Array(
    'Pre-Reader', 
    'Beginner', 
    'Intermediate', 
    'Advanced', 
    'Professional'
  ),
  readingLevel: 2,

  // Prerequisite and corequisite books
  prerequisites: new Array(),
  corequisites: new Array(),

  // Subjects for categorization
  subjects: new Array(),

  // whether to include certain special pages in the book by default
  useresources:    false,
  uselicensing:    false,
  useintroduction: false,
  usecollectionpreface: true,

  // other options
  defaultinherit:  false,
  defaultcollapse: false,
  commentsaspages: false,
  useexternaledit: false,
  
  // Drag-n-drop values
  lastClicked: null,
  offsetX: 0,
  offsetY: 0,
  startX: 0,
  startY: 0,
  box: null
};

//Basic array management functions
vbd.CopyArray = function(array) { return array.slice(0); }
vbd.FindPageNameInArray = function (array, name) {
  for(var i = 0; i < array.length; i++) {
    if(array[i].pagename == name) return i;
  }
  return -1;
}
vbd.FindHeadNameInArray = function(array, name) {
  for(var i = 0; i < array.length; i++) {
    if(array[i].label == name) return i;
  }
  return -1;
}

//Make a checkbox that corresponds to a boolean flag in vbd
vbd.makeOptionsCheckbox = function (field) {
  var cbox = wk.makeElement('input', {type: "checkbox"});
  cbox.checked = vbd[field];
  cbox.onclick = function () { vbd[field] = cbox.checked; }
  return cbox;
}

//Initialization functions. If the wk object is not found, the noinit method is called. Otherwise, the
//initialize method is called to start the gadget.
vbd.noinit = function() {
  wk.spanText(vbd.formspan, "Cannot load VBD. WKGadgetsCore is missing or is wrong version. "
    + "Please install WKGadgetsCore, or update it to version " + vbd._require + ".");
}
vbd.initialize = function () {
  vbd.pageTree = new BookPage(vbd.defName);
  vbd.status = wk.spanEditor(vbd.statspan);
  vbd.status("Use the outline tool below to create a new book");
  var query = wk.getQueryHash();
  if(typeof query["book"] == "undefined") {
    vbd.visual();
    return;
  }
  vbd.status("Loading the outline for " + query["book"] + "...");
  wk.loadWikiText(query["book"], function (text) { 
    var start = text.indexOf('{{User:Whiteknight/SavedOutlineStart}}');
    var end = text.indexOf('{{User:Whiteknight/SavedOutlineEnd}}');
    if(start == -1 || end == -1) {
      vbd.visual();
      return;
    }
    vbd.loadNodeTree(text.substring(start + 38, end));
    vbd.status("Use the outline tool below to create a new book");
  });
}
if(typeof wk == 'object' && wk.testVersion(vbd._require)) {
  $(vbd.initialize);
} else {
  $(vbd.noinit);
}

//rebuild the outline
vbd.visual = function() {
  vbd.box = wk.spanText(vbd.formspan, "");
  if(vbd.box == null) return;
  vbd.box.appendChild(vbd.pageTree.makeNode());
}

//Clear the outline completely and create a new one
vbd.clear = function() {
  vbd.pageTree = new BookPage(vbd.defName);
  vbd.subjects = new Array();
  vbd.readingLevel = 2;
  vbd.newpagecnt = 1;
  vbd.newheadcnt = 1;
  vbd.visual();
}

//Load a saved outline from a save page
vbd.loadNodeTree = function (text) {
  vbd.clear();
  var lastn = 0;
  var last = new Array();
  last[0] = vbd.pageTree;
  var lines = text.split("\n");
  for(i = 0; i < lines.length; i++) {
    lines[i] = lines[i].replace(/^[ \t]+/, "");
    lines[i] = lines[i].replace(/[ \t]+$/, "");
    lines[i] = lines[i].replace(/\r\n/g, "");
    if(lines[i] == "") continue;
    if(lines[i].match(/^=/)) { //heading
      var levels = lines[i].match(/^=+/);
      var n = levels[0].length;
      var k = n - 1;
      var head = new PageHeading(lines[i].substr(n));
      last[k].addHeading(head);
      last[k] = head;
      lastn = k;
    } else if(lines[i].match(/^\*+/)) { //subpage
      var stars = lines[i].match(/^\*+/);
      var n = stars[0].length;
      var k = n - 1;
      if(last[k] == null) { k = 0; }
      last[n] = new BookPage(lines[i].substr(n));
      last[k].addSubpage(last[n]);
      lastn = n;
    } else if(lines[i].match(/^&/)) { //pagetext
      last[lastn].pagetext += lines[i].substr(1);
    } else if(lines[i].match(/^%/)) { //comment
      last[lastn].comments += lines[i].substr(1);
    } else {  //main title
      vbd.pageTree.pagename = lines[i];
      lastn = 0;
    }
  }
  vbd.pageTree.formspan.innerHTML = "";
  vbd.visual();
  vbd.pageTree.formspan.appendChild(document.createTextNode(
    'Successfully loaded saved outline'
  ));
}

//Try to load in a saved collection
vbd.loadNodeTreeCollection = function(text) {
  vbd.clear();
  var last = vbd.pageTree;
  var lines = text.split("\n");
  for(var i = 0; i < lines.length; i++) {
    if(lines[i].match(/^===/) || lines[i].match(/\[\[category:/i)) {
      continue;
    } else if(lines[i].match(/^==.+==/)) {
      vbd.pageTree.pagename = vbd.extractHeadingName(lines[i]);
    } else if(lines[i].match(/^;/)) {
      var head = new PageHeading(lines[i].substring(1));
      last.addHeading(head);
      last = head;
    } else if(lines[i].match(/^:\[\[/)) {
      if(lines[i].match(/Resources/)) { vbd.useresources = true; continue; }
      if(lines[i].match(/Licensing/)) { vbd.uselicensing = true; continue; }
      if(lines[i].match(/Wikibooks:/)) { vbd.usecollectionpreface = true; continue; }
      var page = new BookPage(vbd.extractLinkPageName(lines[i], vbd.pageTree.pagename));
      last.addSubpage(page);
    }
  }
  vbd.visual();
  vbd.pageTree.formspan.innerHTML = 'Successfully loaded collection!';
}

//take the wikitext of an arbitrary page and try to load a reasonable outline from it.
vbd.loadNodeTreeTOC = function (text, title) {
  vbd.clear();
  var lastn = 0;
  var last = new Array();
  last[0] = vbd.pageTree;
  vbd.pageTree.pagename = title;
  var lines = text.split("\n");
  for(i = 0; i < lines.length; i++) {
    lines[i] = lines[i].replace(/^[ \t]+/, "");
    lines[i] = lines[i].replace(/[ \t]+$/, "");
    lines[i] = lines[i].replace(/\r\n/g, "");
    if(lines[i] == "") continue;
    if(lines[i].match(/^===.+===/)) { //heading
      var head = new PageHeading(vbd.extractHeadingName(lines[i]));
      last[0].addHeading(head);
      last[0] = head;
    } else if(lines[i].match(/^[\*\#]*\s*\[\[.+\]\]/)) { //subpage
      if(lines[i].match(/Resources/)) { vbd.useresources = true; continue; }
      if(lines[i].match(/Licensing/)) { vbd.uselicensing = true; continue; }
      if(lines[i].match(/Category:/i)) continue;
      var stars = lines[i].match(/^[\*\#]*/);
      var n = stars[0].length;
      if(n == 0) n = 1;
      var k = n - 1;
      if(last[k] == null) { k = 0; }
      last[n] = new BookPage(vbd.extractLinkPageName(lines[i], title));
      last[k].addSubpage(last[n]);
      lastn = n;
    }
  }
  vbd.pageTree.formspan.innerHTML = "";
  vbd.visual();
  vbd.pageTree.formspan.appendChild(document.createTextNode(
    'Successfully loaded outline from ' + title + ' TOC!'
  ));
}

//try to get the name of a level-3 heading
vbd.extractHeadingName = function(line) {
  line = line.substring(3);
  line = line.substring(0, line.indexOf("==="));
  return line;
}

//Try to get the page name from a link. Very limited ability to deal with relative links
vbd.extractLinkPageName = function(line, title) {
  line = line.substring(line.indexOf("[[") + 2);
  var end = line.indexOf("|");
  if(end == -1) {
    end = line.indexOf("]]");
  }
  line = line.substring(0, end)
  if(line.charAt(line.length - 1) == '/') line = line.substring(0, line.length - 1);
  if(line.charAt(0) == '/') line = title + line;
  for(end = line.indexOf("/") ; end != -1; end = line.indexOf("/")) {
    line = line.substring(end + 1);
  }
  return line;
}

//when saving an outline, find and remove an old save, if one is located on the page.
vbd.killOldSaves = function(text) {
  var start = text.indexOf('{{User:Whiteknight/SavedOutlineStart}}');
  var end = text.indexOf('{{User:Whiteknight/SavedOutlineEnd}}');
  if(start == -1 || end == -1) return text; //no previous save
  var prefix = text.slice(0, start);
  var suffix = text.slice(end + 36);
  return prefix + suffix;
}

//force a page name to use appropriate capitalization
vbd.forceCaps = function(title, isRoot) {
  if(isRoot) return vbd.forceTitleCaps(title);
  else return vbd.forceFirstCaps(title);
}

//determines if the word needs to be capitalized. Returns 1 if it should be, 0 otherwise.
vbd.isRealWord = function (word) {
  var preps = new Array('the', 'in', 'of', 'for', 'to', 'is', 'a', 'an');
  for(var i = 0; i < preps.length; i++) {
    if(word == preps[i]) return 0;
  }
  return 1;
}

//book names are forced to title caps
vbd.forceTitleCaps = function(title) {
  title.replace("_", " ");
  var words = title.split(" ");
  for(var i = 0; i < words.length; i++) {
    if(words[i].length > 3 || i == 0 || vbd.isRealWord(words[i]))
      words[i] = words[i].charAt(0).toUpperCase() + words[i].slice(1);
  }
  title = words.join(" ");
  return title;
}

//page names are forced to have the first letter capitalized, but are not forced to have title-caps
vbd.forceFirstCaps = function(title) {
  title.replace("_", " ");
  return title.charAt(0).toUpperCase() + title.slice(1);
}

//Create the wikitext of the selected navigation template
vbd.makeTemplateText = function () {
  var template = vbd.templates[vbd.template];
  var book = vbd.pageTree.pagename;
  var text = "{"+"{subst:User:Whiteknight/" + template[0] + "|" + 
    ((template[1])?("book="):("")) + book;
  if(template[2] == 1) {
    text += "|" + ((template[1] == 1)?("list="):("")) + vbd.pageTree.makeTemplateLinks();
    text += "[[" + book + "/Resources|Resources]] - " + 
            "[[" + book + "/Licensing|Licensing]] - " +
            "[[Talk:" + book + "|Discuss]]";
  }
  return text + "}}";
}

//

//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=\"//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() {
    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:" + mw.config.get('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:" + mw.config.get('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:" + mw.config.get('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:" + mw.config.get('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;
  //
  return(this.box);
}

function PageHeading(name) {
  if(name.match(/\s$/)) name = name.substring(0, name.length - 1);
  this.label = name;
  this.subpages = new Array();
  this.text = "";
  this.formspan = wk.makeElement('div');
  this.parent = null;
  this.pagetext = "";
  this.comments = "";
  this.collapse = 0;
  this.box = null;
}

PageHeading.prototype.getTextNode = function () {
  var self = this;
  var div = wk.makeElement('small', {style: "padding-left: 2em;"}, 
    ((this.pagetext.length == 0)?('[click here to edit heading text]'):(this.pagetext)));
  div.onclick = function() {
    var edit = wk.makeElement('textarea', {rows:10});
    edit.value = self.pagetext;
    self.formspan.innerHTML = "";
    self.formspan.appendChild(document.createTextNode('Enter some text for the page:'));
    self.formspan.appendChild(edit);
    self.formspan.appendChild(wk.makeButton('', 'Save', function() {
      self.pagetext = edit.value;
      if(self.pagetext.match(/^\s*$/)) self.pagetext = "";
      self.formspan.innerHTML = "";
      vbd.visual();
    }));
    self.closeButton();
    edit.focus(); 
  }
  div.style.cursor = "pointer";
  return div;
}

//make a link to modify the list of subpages
PageHeading.prototype.makeSubpagesLinks = 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 the names of all 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();
  }
  var add = wk.makeElement('a', null, [' [ + ]']);
  add.onclick = function() {
    self.addSubpage(new BookPage(vbd.defpagename + " " + vbd.newpagecnt));
    vbd.newpagecnt++;
    vbd.visual();
  }
  return [link, add];
}

//the name of the heading
PageHeading.prototype.getTitleNode = function () {
  var container = wk.makeElement('span');
  var collapse = wk.makeElement('small', null, 
    ((this.collapse == 0)?('[-] '):('[+] ')));
  var span = wk.makeElement('big', null, this.label);
  span.style.cursor = "pointer";
  var self = this;
  collapse.onclick = function () {
    self.collapse = (self.collapse == 1)?(0):(1);
    vbd.visual();
  }
  span.onclick = function() {
    var edit = wk.makeElement('input', {type:'text', value:(self.label), size:50});
    self.formspan.innerHTML = "";
    self.formspan.appendChild(document.createTextNode('Enter the new name: '));
    self.formspan.appendChild(edit);
    self.formspan.appendChild(wk.makeButton('', 'Rename', function() {
      self.formspan.innerHTML = "Renaming...";
      var pagename = vbd.forceFirstCaps(edit.value);
      self.label = pagename.replace("\n", "");
      self.formspan.innerHTML = "";
      vbd.visual();
    }));
    self.formspan.appendChild(wk.makeButton('', 'Delete', function () {
      if(confirm("delete heading '" + self.label + "'?")) {
        self.parent.removeHeading(self); 
        vbd.visual();
      }
    }));
    self.closeButton();
    edit.focus();
  }
  if(this.subpages.length != 0) container.appendChild(collapse);
  container.appendChild(span);
  return(container);
}

//get the depth of the page the heading is on
PageHeading.prototype.getDepth = function () {
  return this.parent.getDepth();
}

//make wikitext of pages under a heading, but not of the heading itself
PageHeading.prototype.makeWikitextLinkStars = function (parent) {
  var text = "";
  for(var i = 0; i < this.subpages.length; i++) {
    text = text + this.subpages[i].makeWikitextLinkStars(parent);
  }
  return text;
}

//make the links for the pages under a heading, as formatted for a template
PageHeading.prototype.makeTemplateLinks = function () {
  var text = "";
  for(var i = 0; i < this.subpages.length; i++) {
    text = text + this.subpages[i].makeTemplateLinks();
  }
  return text;
}

//create a list of all subpages of the given heading and append them to list
PageHeading.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]);
    }
  }
  return list;
} 

//make the wikitext for a heading
PageHeading.prototype.makeWikitext = function () {
  var text = "";
  if(this.parent.isRoot()) text = "=== " + this.label + " ===\n\n";
  else text = "== " + this.label + " ==\n\n";
  text += this.pagetext + "\n";
  for(var i = 0; i < this.subpages.length; i++) {
    text = text + this.subpages[i].makeWikitextLinkStars(this);
  }
  return text;
}

//create the print version text for this heading
PageHeading.prototype.makePrintVersionText = function () {
  var text = "{{Print unit page|" + this.label + "|" + this.pagetext + "}}\n\n";
  for(var x = 0; x < this.subpages.length; x++) {
    text = text + this.subpages[x].makePrintVersionText();
  }
  return text;
}

//make the collection text for this heading
PageHeading.prototype.makeCollectionText = function () {
  var text = ";" + this.label + "\n";
  for(var i = 0; i < this.subpages.length; i++) {
    text = text + this.subpages[i].makeCollectionText();
  }
  return text;
}

//create a close button 
PageHeading.prototype.closeButton = function () {
  var self = this;
  this.formspan.appendChild(wk.makeButton('', 'Close', function() {
    self.formspan.innerHTML = "";
  }));  
}

//prepare the text to save a heading from the outline
PageHeading.prototype.makeSaveText = function () {
  var depth = this.getDepth();
  var text = "";
  for(var i = 0; i < depth; i++) {
    text += "=";
  }
  text += "=" + this.label + "\n";
  if(this.pagetext.length != 0) {
    var pagetext = this.pagetext;
    pagetext.replace(/\n/g, "\n&");
    text += "&" + pagetext + "\n";
  }
  if(this.comments.length != 0) {
    var comments = this.comments;
    comments.replace(/\n/g, "\n%");
    text += "%" + comments + "\n";
  }
  for(var i = 0; i < this.subpages.length; i++) {
    text += this.subpages[i].makeSaveText();
  }
  return text; 
}

//make a heading node in the outline
PageHeading.prototype.makeNode = function () {
  this.box = wk.makeElement('div', null, [
    this.getTitleNode(), " - ",
    wk.makeElement('small', null, this.makeSubpagesLinks()),
    wk.makeElement('br'),
    this.getTextNode(),
    this.formspan
  ]);
  this.box.style.position = "relative";
  this.box.style.padding = "5px";
  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);
    }
  }
  container.style.borderBottom = "1px dashed #AAAAAA";
  container.style.borderLeft = "1px dashed #AAAAAA";
  this.box.appendChild(container);
  var self = this;
  //
  return this.box;
}

//add a subpage to this heading
PageHeading.prototype.addSubpage = function (subpage) {
  subpage.parent = this.parent;
  subpage.parent2 = this;
  this.subpages.push(subpage);
}

//add a new heading to the parent of this (headings cannot contain headings)
PageHeading.prototype.addHeading = function (heading) {
  heading.parent = this.parent;
  this.parent.headings.push(heading);
}

//remove a subpage from this heading
PageHeading.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;
  }
}
// </source>