Browse Source

Add definition lists

pull/14/head
Alex Kocharin 10 years ago
parent
commit
3f16868edd
  1. 1
      lib/parser_block.js
  2. 20
      lib/renderer.js
  3. 207
      lib/rules_block/deflist.js
  4. 1
      lib/rules_block/state_block.js
  5. 144
      test/fixtures/remarkable/deflist.txt

1
lib/parser_block.js

@ -19,6 +19,7 @@ var _rules = [
[ 'lheading', require('./rules_block/lheading') ],
[ 'htmlblock', require('./rules_block/htmlblock'), [ 'paragraph', 'blockquote' ] ],
[ 'table', require('./rules_block/table'), [ 'paragraph' ] ],
[ 'deflist', require('./rules_block/deflist'), [ 'paragraph' ] ],
[ 'paragraph', require('./rules_block/paragraph') ]
];

20
lib/renderer.js

@ -305,6 +305,26 @@ rules.footnote_anchor = function (tokens, idx) {
};
rules.dl_open = function() {
return '<dl>\n';
};
rules.dt_open = function() {
return '<dt>';
};
rules.dd_open = function() {
return '<dd>';
};
rules.dl_close = function() {
return '</dl>\n';
};
rules.dt_close = function() {
return '</dt>\n';
};
rules.dd_close = function() {
return '</dd>\n';
};
// Renderer class
function Renderer() {
// Clone rules object to allow local modifications

207
lib/rules_block/deflist.js

@ -0,0 +1,207 @@
// Definition lists
'use strict';
// Search `[:~][\n ]`, returns next pos after marker on success
// or -1 on fail.
function skipMarker(state, line) {
var pos, marker,
start = state.bMarks[line] + state.tShift[line],
max = state.eMarks[line];
if (start >= max) { return -1; }
// Check bullet
marker = state.src.charCodeAt(start++);
if (marker !== 0x7E/* ~ */ && marker !== 0x3A/* : */) { return -1; }
pos = state.skipSpaces(start);
// require space after ":"
if (start === pos) { return -1; }
// no empty definitions, e.g. " : "
if (pos >= max) { return -1; }
return pos;
}
function markTightParagraphs(state, idx) {
var i, l,
level = state.level + 2;
for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
state.tokens[i + 2].tight = true;
state.tokens[i].tight = true;
i += 2;
}
}
}
module.exports = function deflist(state, startLine, endLine, silent) {
var contentStart,
ddLine,
dtLine,
itemLines,
listLines,
listTokIdx,
nextLine,
oldIndent,
oldDDIndent,
oldParentType,
oldTShift,
oldTight,
prevEmptyEnd,
tight;
if (silent) {
// quirk: validation mode validates a dd block only, not a whole deflist
if (state.ddIndent < 0) { return false; }
return skipMarker(state, startLine) >= 0;
}
nextLine = startLine + 1;
if (state.isEmpty(nextLine)) {
if (++nextLine > endLine) { return false; }
}
if (state.tShift[nextLine] < state.blkIndent) { return false; }
contentStart = skipMarker(state, nextLine);
if (contentStart < 0) { return false; }
if (state.level >= state.options.maxNesting) { return false; }
// Start list
listTokIdx = state.tokens.length;
state.tokens.push({
type: 'dl_open',
lines: listLines = [ startLine, 0 ],
level: state.level++
});
//
// Iterate list items
//
dtLine = startLine;
ddLine = nextLine;
// One definition list can contain multiple DTs,
// and one DT can be followed by multiple DDs.
//
// Thus, there is two loops here, and label is
// needed to break out of the second one
//
/*eslint no-labels:0,block-scoped-var:0*/
OUTER:
for (;;) {
tight = true;
prevEmptyEnd = false;
state.tokens.push({
type: 'dt_open',
lines: [ dtLine, dtLine ],
level: state.level++
});
state.tokens.push({
type: 'inline',
content: state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim(),
level: state.level + 1,
lines: [ dtLine, dtLine ],
children: []
});
state.tokens.push({
type: 'dt_close',
level: --state.level
});
for (;;) {
state.tokens.push({
type: 'dd_open',
lines: itemLines = [ nextLine, 0 ],
level: state.level++
});
oldTight = state.tight;
oldDDIndent = state.ddIndent;
oldIndent = state.blkIndent;
oldTShift = state.tShift[ddLine];
oldParentType = state.parentType;
state.blkIndent = state.ddIndent = state.tShift[ddLine] + 2;
state.tShift[ddLine] = contentStart - state.bMarks[ddLine];
state.tight = true;
state.parentType = 'deflist';
state.parser.tokenize(state, ddLine, endLine, true);
// If any of list item is tight, mark list as tight
if (!state.tight || prevEmptyEnd) {
tight = false;
}
// Item become loose if finish with empty line,
// but we should filter last element, because it means list finish
prevEmptyEnd = (state.line - ddLine) > 1 && state.isEmpty(state.line - 1);
state.tShift[ddLine] = oldTShift;
state.tight = oldTight;
state.parentType = oldParentType;
state.blkIndent = oldIndent;
state.ddIndent = oldDDIndent;
state.tokens.push({
type: 'dd_close',
level: --state.level
});
itemLines[1] = nextLine = state.line;
if (nextLine >= endLine) { break OUTER; }
if (state.tShift[nextLine] < state.blkIndent) { break OUTER; }
contentStart = skipMarker(state, nextLine);
if (contentStart < 0) { break; }
ddLine = nextLine;
// go to the next loop iteration:
// insert DD tag and repeat checking
}
if (nextLine >= endLine) { break; }
dtLine = nextLine;
if (state.isEmpty(dtLine)) { break; }
if (state.tShift[dtLine] < state.blkIndent) { break; }
ddLine = dtLine + 1;
if (ddLine >= endLine) { break; }
if (state.isEmpty(ddLine)) { ddLine++; }
if (ddLine >= endLine) { break; }
if (state.tShift[ddLine] < state.blkIndent) { break; }
contentStart = skipMarker(state, ddLine);
if (contentStart < 0) { break; }
// go to the next loop iteration:
// insert DT and DD tags and repeat checking
}
// Finilize list
state.tokens.push({
type: 'dl_close',
level: --state.level
});
listLines[1] = nextLine;
state.line = nextLine;
// mark paragraphs tight if needed
if (tight) {
markTightParagraphs(state, listTokIdx);
}
return true;
};

1
lib/rules_block/state_block.js

@ -32,6 +32,7 @@ function StateBlock(src, parser, options, env, tokens) {
this.lineMax = 0; // lines count
this.tight = false; // loose/tight mode for lists
this.parentType = 'root'; // if `list`, block parser stops on two newlines
this.ddIndent = -1; // indent of the current dd block (-1 if there isn't any)
this.level = 0;

144
test/fixtures/remarkable/deflist.txt

@ -0,0 +1,144 @@
Pandoc (with slightly changed indents):
.
Term 1
: Definition 1
Term 2 with *inline markup*
: Definition 2
{ some code, part of Definition 2 }
Third paragraph of definition 2.
.
<dl>
<dt>Term 1</dt>
<dd><p>Definition 1</p>
</dd>
<dt>Term 2 with <em>inline markup</em></dt>
<dd><p>Definition 2</p>
<pre><code>{ some code, part of Definition 2 }
</code></pre>
<p>Third paragraph of definition 2.</p>
</dd>
</dl>
.
Pandoc again:
.
Term 1
: Definition
with lazy continuation.
Second paragraph of the definition.
.
<dl>
<dt>Term 1</dt>
<dd><p>Definition
with lazy continuation.</p>
<p>Second paragraph of the definition.</p>
</dd>
</dl>
.
Well, I might just copy-paste the third one while I'm at it:
.
Term 1
~ Definition 1
Term 2
~ Definition 2a
~ Definition 2b
.
<dl>
<dt>Term 1</dt>
<dd>Definition 1
</dd>
<dt>Term 2</dt>
<dd>Definition 2a
</dd>
<dd>Definition 2b
</dd>
</dl>
.
Now, with our custom ones. Spaces after a colon:
.
Term 1
: paragraph
Term 2
: code block
.
<dl>
<dt>Term 1</dt>
<dd>paragraph
</dd>
<dt>Term 2</dt>
<dd><pre><code>code block
</code></pre>
</dd>
</dl>
.
There should be something after a colon by the way:
.
Non-term 1
:
Non-term 2
:
.
<p>Non-term 1
:</p>
<p>Non-term 2
:</p>
.
Definition lists should be second last in the queue. Exemplī grātiā, this isn't a valid one:
.
# test
: just a paragraph with a colon
.
<h1>test</h1>
<p>: just a paragraph with a colon</p>
.
Nested definition lists:
.
test
: foo
: bar
: baz
: bar
: foo
.
<dl>
<dt>test</dt>
<dd><dl>
<dt>foo</dt>
<dd><dl>
<dt>bar</dt>
<dd>baz
</dd>
</dl>
</dd>
<dd>bar
</dd>
</dl>
</dd>
<dd>foo
</dd>
</dl>
.
Loading…
Cancel
Save