mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-07-07 20:44:23 +00:00
d338a54370
This is a half-way through a big refactoring of the parsing and rendering infrastructure. The main change is to separate the parse and render trees, which makes the code a lot cleaner. The new parser isn't yet functional enough to replace the existing parser so for the moment you have to manually invoke it with `$tw.testNewParser()` in your browser console. I really ought to use branches for this kind of thing...
142 lines
3.9 KiB
JavaScript
142 lines
3.9 KiB
JavaScript
/*\
|
|
title: $:/core/modules/parsers/wikiparser/rules/block/list.js
|
|
type: application/javascript
|
|
module-type: wikiblockrule
|
|
|
|
Wiki text block rule for lists. For example:
|
|
|
|
{{{
|
|
* This is an unordered list
|
|
* It has two items
|
|
|
|
# This is a numbered list
|
|
## With a subitem
|
|
# And a third item
|
|
|
|
; This is a term that is being defined
|
|
: This is the definition of that term
|
|
}}}
|
|
|
|
Note that lists can be nested arbitrarily:
|
|
|
|
{{{
|
|
#** One
|
|
#* Two
|
|
#** Three
|
|
#**** Four
|
|
#**# Five
|
|
#**## Six
|
|
## Seven
|
|
### Eight
|
|
## Nine
|
|
}}}
|
|
|
|
A CSS class can be applied to a list item as follows:
|
|
|
|
{{{
|
|
* List item one
|
|
*.active List item two has the class `active`
|
|
* List item three
|
|
}}}
|
|
|
|
\*/
|
|
(function(){
|
|
|
|
/*jslint node: true, browser: true */
|
|
/*global $tw: false */
|
|
"use strict";
|
|
|
|
var ListRule = function(parser,startPos) {
|
|
// Save state
|
|
this.parser = parser;
|
|
// Regexp to match
|
|
this.reMatch = /([\\*#;:]+)/mg;
|
|
// Get the first match
|
|
this.matchIndex = startPos-1;
|
|
this.findNextMatch(startPos);
|
|
};
|
|
|
|
ListRule.prototype.findNextMatch = function(startPos) {
|
|
if(this.matchIndex !== undefined && startPos > this.matchIndex) {
|
|
this.reMatch.lastIndex = startPos;
|
|
this.match = this.reMatch.exec(this.parser.source);
|
|
this.matchIndex = this.match ? this.match.index : undefined;
|
|
}
|
|
return this.matchIndex;
|
|
};
|
|
|
|
var listTypes = {
|
|
"*": {listTag: "ul", itemTag: "li"},
|
|
"#": {listTag: "ol", itemTag: "li"},
|
|
";": {listTag: "dl", itemTag: "dt"},
|
|
":": {listTag: "dl", itemTag: "dd"}
|
|
};
|
|
|
|
/*
|
|
Parse the most recent match
|
|
*/
|
|
ListRule.prototype.parse = function() {
|
|
// Array of parse tree nodes for the previous row of the list
|
|
var listStack = [];
|
|
// Cycle through the items in the list
|
|
while(true) {
|
|
// Match the list marker
|
|
var reMatch = /(^[\*#;:]+)/mg;
|
|
reMatch.lastIndex = this.parser.pos;
|
|
var match = reMatch.exec(this.parser.source);
|
|
if(!match || match.index !== this.parser.pos) {
|
|
break;
|
|
}
|
|
// Check whether the list type of the top level matches
|
|
var listInfo = listTypes[match[0].charAt(0)];
|
|
if(listStack.length > 0 && listStack[0].tag !== listInfo.listTag) {
|
|
break;
|
|
}
|
|
// Move past the list marker
|
|
this.parser.pos = match.index + match[0].length;
|
|
// Walk through the list markers for the current row
|
|
for(var t=0; t<match[0].length; t++) {
|
|
listInfo = listTypes[match[0].charAt(t)];
|
|
// Remove any stacked up element if we can't re-use it because the list type doesn't match
|
|
if(listStack.length > t && listStack[t].tag !== listInfo.listTag) {
|
|
listStack.splice(t,listStack.length - t);
|
|
}
|
|
// Construct the list element or reuse the previous one at this level
|
|
if(listStack.length <= t) {
|
|
var listElement = {type: "element", tag: listInfo.listTag, children: [
|
|
{type: "element", tag: listInfo.itemTag, children: []}
|
|
]};
|
|
// Link this list element into the last child item of the parent list item
|
|
if(t) {
|
|
var prevListItem = listStack[t-1].children[listStack[t-1].children.length-1];
|
|
prevListItem.children.push(listElement);
|
|
}
|
|
// Save this element in the stack
|
|
listStack[t] = listElement;
|
|
} else if(t === (match[0].length - 1)) {
|
|
listStack[t].children.push({type: "element", tag: listInfo.itemTag, children: []});
|
|
}
|
|
}
|
|
if(listStack.length > match[0].length) {
|
|
listStack.splice(match[0].length,listStack.length - match[0].length);
|
|
}
|
|
// Process the body of the list item into the last list item
|
|
var lastListChildren = listStack[listStack.length-1].children,
|
|
lastListItem = lastListChildren[lastListChildren.length-1],
|
|
classedRun = this.parser.parseClassedRun(/(\r?\n)/mg);
|
|
lastListItem.children.push.apply(lastListItem.children,classedRun.tree);
|
|
if(classedRun["class"]) {
|
|
lastListItem.attributes = lastListItem.attributes || {};
|
|
lastListItem.attributes["class"] = {type: "string", value: classedRun["class"]};
|
|
}
|
|
// Consume any whitespace following the list item
|
|
this.parser.skipWhitespace();
|
|
};
|
|
// Return the root element of the list
|
|
return [listStack[0]];
|
|
};
|
|
|
|
exports.ListRule = ListRule;
|
|
|
|
})();
|