Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/compile/output/html.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import outputHTML from '../../output/html/index.js';
import { crossref, header, section, sticky } from '../../plugins/index.js';
import { crossref, header, pathid, section, sticky } from '../../plugins/index.js';
import { transformAST } from '../transform-ast.js';

export default async function(ast, context, options) {
const astHTML = await transformAST(ast, context, [
pathid,
crossref(options.numbered),
sticky,
header,
Expand Down
2 changes: 2 additions & 0 deletions src/output/html/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export const DATA_ATTR = 'data-attr';
export const DATA_CELL = 'data-cell';
export const DATA_BIND = 'data-bind';
export const DATA_BIND_SET = 'data-bind-set';
export const DATA_AST = 'data-ast';
export const DELIM_AST = '/';
122 changes: 122 additions & 0 deletions src/output/html/selection-manager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { DATA_AST, DELIM_AST } from './constants.js';

const TEXT_NODE = 3;

export function astRange(domRange) {
let startPath = resolveNode(domRange.startContainer);
const startOffset = startPath ? domRange.startOffset : 0;
if (!startPath) {
startPath = findPath(domRange.startContainer, n => n.nextSibling);
}

let endPath = resolveNode(domRange.endContainer);
const endOffset = endPath ? domRange.endOffset : -1;
if (!endPath) {
endPath = findPath(domRange.endContainer, n => n.prevSibling);
}

return { startPath, startOffset, endPath, endOffset };
}

function findPath(node, next) {
// climb until a registered ancestor is found
while (node.parentNode && !hasPath(node.parentNode)) {
node = node.parentNode;
}
// search siblings for a registered node
for (let curr = next(node); curr; curr = next(curr)) {
const path = resolveNode(curr);
if (path) return path;
}
// if search failure, return the ancestor
resolveNode(node.parentNode);
}

function resolveNode(node) {
if (node.nodeType === TEXT_NODE && hasPath(node.parentNode)) {
return [ ...extractPath(node.parentNode), childIndex(node) ];
} else if (hasPath(node)) {
return extractPath(node);
}
}

function hasPath(node) {
return node && node.hasAttribute && node.hasAttribute(DATA_AST);
}

function extractPath(node) {
const pstr = node.getAttribute(DATA_AST);
return pstr.slice(DELIM_AST.length).split(DELIM_AST).map(x => +x);
}

function childIndex(node) {
const list = node.parentNode.childNodes;
const n = list.length;
for (let i = 0; i < n; ++i) {
if (list[i] === node) {
return i;
}
}
return -1;
}

// TODO: map AST range to DOM range?

function visitRangeNodes(domRange, visitor) {
const ancestor = domRange.commonAncestorContainer;
const start = domRange.startContainer;
const end = domRange.endContainer;

let stack = [];
for (let node = start; node !== ancestor; node = node.parentNode) {
stack.push(node);
}
stack.push(ancestor);
stack.reverse();
for (let i = 0; i < stack.length - 1; ++i) {
visitor(stack[i]);
}

handleStart(start, domRange.startOffset, visitor);

while (stack.length) {
let curr = stack.pop();
while (curr = curr.nextSibling) {
if (curr === end) {
stack = [];
break;
}
visitor(curr);
if (curr.hasChildNodes()) {
stack.push(curr);
curr = curr.firstChild;
if (curr == end) {
stack = [];
} else {
visitor(curr);
stack.push(curr);
}
break;
}
}
}

handleEnd(end, domRange.endOffset, visitor);
}

function handleStart(node, offset, visitor) {
if (node.nodeType === TEXT_NODE && offset > 0) {
visitor(node.splitText(offset));
} else {
visitor(node);
}
}

function handleEnd(node, offset, visitor) {
if (node.nodeType === TEXT_NODE && offset < node.nodeValue.length) {
node.splitText(offset);
visitor(node);
} else {
visitor(node);
}
}
1 change: 1 addition & 0 deletions src/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { default as convert } from './convert/index.js';
export { default as crossref } from './crossref/index.js';
export { default as header } from './header/index.js';
export { default as notes } from './notes/index.js';
export { default as pathid } from './pathid/index.js';
export { default as runtime } from './runtime/index.js';
export { default as section } from './section/index.js';
export { default as sticky } from './sticky/index.js';
18 changes: 18 additions & 0 deletions src/plugins/pathid/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getChildren, setValueProperty } from '../../ast/index.js';

const DATA_AST = 'data-ast';
const DELIM_AST = '/';

export default function(ast) {
pathId(ast, []);
return ast;
}

function pathId(ast, path) {
setValueProperty(ast, DATA_AST, DELIM_AST + path.join(DELIM_AST));
getChildren(ast).forEach((child, index) => {
path.push(index);
pathId(child, path);
path.pop();
});
}
21 changes: 12 additions & 9 deletions src/plugins/section/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,22 @@ const aliases = new Map([
['references', 'References']
]);

// TODO maintain one-to-one top-level AST mapping (div.name)?
export default function(ast) {
visitNodes(ast, (node, parent) => {
if (aliases.has(node.name)) {
const alias = aliases.get(node.name);
replaceChild(parent, node, [
createComponentNode(
'h1',
createProperties({ nonumber: true }),
[ createTextNode(alias) ]
),
...node.children
]);
replaceChild(parent, node, createComponentNode(
'div',
node.properties,
[
createComponentNode(
'h1',
createProperties({ nonumber: true }),
[ createTextNode(alias) ]
),
...node.children
]
));
}
});
return ast;
Expand Down