This repository has been archived by the owner on Jun 7, 2024. It is now read-only.
forked from speced/respec
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'develop' into gh-pages
* develop: v18.3.0 feat(core/list-sorter): teach ReSpec to sort OL + UL feat: teach ReSpec to sort definition lists chore(.travis): adhere to Node LTS
- Loading branch information
Showing
9 changed files
with
275 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { pub } from "core/pubsubhub"; | ||
export const name = "core/list-sorter"; | ||
|
||
function makeSorter(direction) { | ||
return ({ textContent: a }, { textContent: b }) => { | ||
return direction === "ascending" ? a.localeCompare(b) : b.localeCompare(a); | ||
}; | ||
} | ||
/** | ||
* Shallow sort list items in OL, and UL elements. | ||
* | ||
* @param {HTMLUListElement} elem | ||
* @returns {DocumentFragment} | ||
*/ | ||
export function sortListItems(elem, dir) { | ||
const elements = getDirectDescendents(elem, "li"); | ||
const sortedElements = elements.sort(makeSorter(dir)).reduce((frag, elem) => { | ||
frag.appendChild(elem); | ||
return frag; | ||
}, document.createDocumentFragment()); | ||
return sortedElements; | ||
} | ||
|
||
function getDirectDescendents(elem, wantedDescendentName) { | ||
let elements; | ||
try { | ||
elements = elem.querySelectorAll(`:scope > ${wantedDescendentName}`); | ||
} catch (err) { | ||
let tempId = ""; | ||
// We give a temporary id, to overcome lack of ":scope" support in Edge. | ||
if (!elem.id) { | ||
tempId = `temp-${Math.random()}`; | ||
elem.id = tempId; | ||
} | ||
const query = `#${elem.id} > ${wantedDescendentName}`; | ||
elements = elem.parentElement.querySelectorAll(query); | ||
if (tempId) { | ||
elem.id = ""; | ||
} | ||
} | ||
return [...elements]; | ||
} | ||
|
||
/** | ||
* Shallow sort a definition list based on its definition terms (dt) elements. | ||
* | ||
* @param {HTMLDListElement} dl | ||
* @returns {DocumentFragment} | ||
*/ | ||
export function sortDefinitionTerms(dl, dir) { | ||
const elements = getDirectDescendents(dl, "dt"); | ||
const sortedElements = elements.sort(makeSorter(dir)).reduce((frag, elem) => { | ||
const { nodeType, nodeName } = elem; | ||
const children = document.createDocumentFragment(); | ||
let { nextSibling: next } = elem; | ||
while (next) { | ||
if (!next.nextSibling) { | ||
break; | ||
} | ||
children.appendChild(next.cloneNode(true)); | ||
const { nodeType: nextType, nodeName: nextName } = next.nextSibling; | ||
const isSameType = nextType === nodeType && nextName === nodeName; | ||
if (isSameType) { | ||
break; | ||
} | ||
next = next.nextSibling; | ||
} | ||
children.prepend(elem.cloneNode(true)); | ||
frag.appendChild(children); | ||
return frag; | ||
}, document.createDocumentFragment()); | ||
return sortedElements; | ||
} | ||
|
||
export function run(conf, doc, cb) { | ||
for (const elem of document.querySelectorAll("[data-sort]")) { | ||
let sortedElems; | ||
const dir = elem.dataset.sort || "ascending"; | ||
switch (elem.localName) { | ||
case "dl": | ||
sortedElems = sortDefinitionTerms(elem, dir); | ||
break; | ||
case "ol": | ||
case "ul": | ||
sortedElems = sortListItems(elem, dir); | ||
break; | ||
default: | ||
pub("warning", `ReSpec can't sort ${elem.localName} elements.`); | ||
} | ||
if (sortedElems) { | ||
const range = document.createRange(); | ||
range.selectNodeContents(elem); | ||
range.deleteContents(); | ||
elem.appendChild(sortedElems); | ||
} | ||
} | ||
cb(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
"use strict"; | ||
describe("Core — list-sorter", () => { | ||
afterAll(flushIframes); | ||
let doc; | ||
beforeAll(async () => { | ||
const ops = { | ||
config: makeBasicConfig(), | ||
body: | ||
makeDefaultBody() + | ||
` | ||
<ol data-sort=ascending> | ||
<li>F</li> | ||
<li>Z</li> | ||
<li>a</li> | ||
<li>B</li> | ||
</ol> | ||
<ul data-sort=descending> | ||
<li>F</li> | ||
<li>Z</li> | ||
<li>a</li> | ||
<li>c</li> | ||
</ul> | ||
<ol id="ol-default" data-sort> | ||
<li>F</li> | ||
<li>Z</li> | ||
<li>a</li> | ||
<li>B</li> | ||
</ol> | ||
<ul data-sort=descending id="nested-list"> | ||
<li>F</li> | ||
<li>A | ||
<ol data-sort=ascending> | ||
<li>3</li> | ||
<li>9</li> | ||
<li>1</li> | ||
<li>5</li> | ||
</ol> | ||
</li> | ||
<li>z</li> | ||
<li>c</li> | ||
</ul> | ||
<dl data-sort="ascending"> | ||
<dt>Z</dt> | ||
<dd>First</dd> | ||
<dd>Second last</dd> | ||
<dd>Last when sorted.</dd> | ||
<dt>h</dt> | ||
<dt>a</dt> | ||
<dt>W</dt> | ||
</dl> | ||
<dl data-sort="descending"> | ||
<dt>3</dt> | ||
<dt>9</dt> | ||
<dd>First</dd> | ||
<dt>1</dt> | ||
<dd>First</dd> | ||
<dd>Second last</dd> | ||
<dd>Last when sorted.</dd> | ||
</dl> | ||
<dl id="dont-sort"> | ||
<dt>dont</dt> | ||
<dt>sort</dt> | ||
<dt>me</dt> | ||
</dl> | ||
<dl id="default-sort" data-sort> | ||
<dt>9</dt> | ||
<dt>3</dt> | ||
<dt>1</dt> | ||
</dl> | ||
`, | ||
}; | ||
doc = await makeRSDoc(ops); | ||
}); | ||
describe("Ordered and unordered lists", ()=>{ | ||
it("sorts ordered lists in ascending order", () => { | ||
const list = doc.querySelector("ol[data-sort='ascending']"); | ||
const first = list.querySelector("li:first-of-type"); | ||
const last = list.querySelector("li:last-of-type"); | ||
expect(first.textContent).toEqual("a"); | ||
expect(last.textContent).toEqual("Z"); | ||
}); | ||
|
||
it("sorts unordered lists in descending order", () => { | ||
const list = doc.querySelector("ul[data-sort='descending']"); | ||
const first = list.querySelector("li:first-of-type"); | ||
const last = list.querySelector("li:last-of-type"); | ||
expect(first.textContent).toEqual("Z"); | ||
expect(last.textContent).toEqual("a"); | ||
}); | ||
|
||
it("defaults to sorting in ascending order", () => { | ||
const list = doc.querySelector("#ol-default"); | ||
expect(list.firstElementChild.textContent).toEqual("a"); | ||
expect(list.lastElementChild.textContent).toEqual("Z"); | ||
}); | ||
|
||
it("sorts nested lists", ()=>{ | ||
const list = doc.querySelector("#nested-list"); | ||
const first = list.querySelector("li:first-of-type"); | ||
const last = list.querySelector("li:last-of-type"); | ||
expect(first.textContent).toEqual("z"); | ||
expect(last.firstChild.textContent.startsWith("A")).toBe(true); | ||
}); | ||
}); | ||
describe("Definition lists", ()=>{ | ||
it("sorts definition lists in ascending order", () => { | ||
const list = doc.querySelector("dl[data-sort='ascending']"); | ||
const firstDt = list.querySelector("dt:first-of-type"); | ||
const lastDt = list.querySelector("dt:last-of-type"); | ||
expect(firstDt.textContent).toEqual("a"); | ||
expect(lastDt.textContent).toEqual("Z"); | ||
expect(lastDt.nextElementSibling.textContent).toEqual("First"); | ||
expect(list.lastElementChild.textContent).toEqual("Last when sorted."); | ||
expect(list.lastElementChild.previousElementSibling.textContent).toEqual( | ||
"Second last" | ||
); | ||
}); | ||
|
||
it("sorts definition lists in descending order", () => { | ||
const list = doc.querySelector("dl[data-sort='descending']"); | ||
expect(list.firstElementChild.textContent).toEqual("9"); | ||
const lastDt = list.querySelector("dt:last-of-type"); | ||
expect(lastDt.nextElementSibling.textContent).toEqual("First"); | ||
expect(list.lastElementChild.textContent).toEqual("Last when sorted."); | ||
expect(list.lastElementChild.previousElementSibling.textContent).toEqual( | ||
"Second last" | ||
); | ||
}); | ||
|
||
it("defaults to sorting in definition lists in ascending order", () => { | ||
const list = doc.querySelector("#default-sort"); | ||
expect(list.firstElementChild.textContent).toEqual("1"); | ||
expect(list.lastElementChild.textContent).toEqual("9"); | ||
}); | ||
|
||
it("leaves unmarked lists alone", () => { | ||
const list = doc.querySelector("#dont-sort"); | ||
expect(list.firstElementChild.textContent).toEqual("dont"); | ||
expect(list.lastElementChild.textContent).toEqual("me"); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters