Skip to content

Instantly share code, notes, and snippets.

@sunghwan2789
Last active January 26, 2021 15:39
Show Gist options
  • Save sunghwan2789/a5acb5431bfa36681788ba9f2b166019 to your computer and use it in GitHub Desktop.
Save sunghwan2789/a5acb5431bfa36681788ba9f2b166019 to your computer and use it in GitHub Desktop.
카테고리에 속하는 글을 위키 문서처럼 바꾸어 보아요 - 제목 태그 순번 매기기, 목차 만들기 를 자동으로 해드려요
.wiki-heading__collapsible {
cursor: pointer;
/* border-bottom: 1px solid #000; */
}
.wiki-heading__collapsible::before {
content: '^';
display: inline-block;
transform: translateY(-5%) rotate(180deg);
font-weight: 100;
filter: opacity(0.5);
}
.wiki-heading__collapsible.collapsed::before {
transform: translateX(-5%) rotate(90deg);
}
.wiki-heading__toc {
border: 1px solid #999;
display: inline-block;
padding: .5em;
}
.wiki-heading__toc-header {
font-size: 1.2em;
}
.wiki-heading__toc-item.h1 {
text-indent: 0em;
}
.wiki-heading__toc-item.h2 {
text-indent: 1em;
}
.wiki-heading__toc-item.h3 {
text-indent: 2em;
}
.wiki-heading__toc-item.h4 {
text-indent: 3em;
}
.wiki-heading__toc-item.h5 {
text-indent: 4em;
}
.wiki-heading__toc-item.h6 {
text-indent: 5em;
}
<link rel="stylesheet" href="wiki-headings.css">
<div class="article">
<div class="article-category">마르네</div>
<h2>0</h2>
<h1>1</h1>
<p>adsf<br>
eoewo</p>
<p>asdfar</p>
<h2>2</h2>
<h3>3</h3>
<h2>22</h2>
<h2>222</h2>
<h3>33</h3>
<h5>5</h5>
<h1>11</h1>
<h2>22</h2>
<div class="container_postbtn"></div>
<h3>agsdf</h3>
</div>
<script src="wiki-headings.js"></script>
(function applyWikiHeadings() {
const targetCategories = new Set([
'마르네',
]);
const collapsibleHeadingClassName = 'wiki-heading__collapsible';
const collapsibleHeadingCollapsedClassName = 'collapsed';
const tocClassName = 'wiki-heading__toc';
const tocHeaderClassName = 'wiki-heading__toc-header';
const tocItemClassName = 'wiki-heading__toc-item';
document.addEventListener('DOMContentLoaded', _ => {
getTargetArticles().forEach(applyWikiHeading);
}, false);
function getTargetArticles() {
const articles = Array.from(document.querySelectorAll('.article'));
return articles.filter(article => {
const { textContent: articleCategory } = article.querySelector('.article-category');
return targetCategories.has(articleCategory.split('/')[0]);
});
}
/**
* @param {Element} article
*/
function applyWikiHeading(article) {
const articleEndNode = article.querySelector('.container_postbtn');
const headings = Array.from(article.querySelectorAll('h1,h2,h3,h4,h5,h6'))
.filter(header => header.compareDocumentPosition(articleEndNode) & 4);
bindCollapsible(article, headings);
const headingTree = Array.from(buildHeadingTree(headings));
insertTableOfContent(article, headingTree);
injectHeadingNumber(article, headingTree);
}
/**
* @param {Element} article
* @param {Element[]} headings
*/
function bindCollapsible(article, headings) {
const articleEndNode = article.querySelector('.container_postbtn');
const breakpoints = [...headings.slice(1), articleEndNode];
const collapsibles = headings.map((heading, index) => [heading, breakpoints[index]]);
collapsibles.forEach(([heading, breakpoint]) => {
heading.classList.add(collapsibleHeadingClassName);
heading.addEventListener('click', e => {
if (e.target !== heading) {
return;
}
heading.classList.toggle(collapsibleHeadingCollapsedClassName);
let element = heading.nextElementSibling;
while (element !== breakpoint) {
element.toggleAttribute('hidden');
element = element.nextElementSibling;
}
});
})
}
/**
* @param {Element} article
* @param {*} headingTree
*/
function insertTableOfContent(article, headingTree) {
const tocItems = Array.from(buildTableOfContent(headingTree));
const { heading: firstHeading } = headingTree[0];
firstHeading.insertAdjacentHTML('beforebegin', `
<div class="${tocClassName}">
<div class="${tocHeaderClassName}">
${'목차'}
</div>
${tocItems.join('')}
</div>
`);
function* buildTableOfContent(tree, parentHeadingNumber) {
let internalNumber = 1;
for (const { heading, children } of tree) {
const headingNumber = `${parentHeadingNumber || ''}${internalNumber}.`;
yield createTocItem(heading, headingNumber);
yield* buildTableOfContent(children, headingNumber);
internalNumber += 1;
}
}
/**
* @param {Element} heading
* @param {string} headingNumber
*/
function createTocItem(heading, headingNumber) {
const headingLevel = headingNumber.split('.').length - 1;
return `
<div class="${tocItemClassName} h${headingLevel}">
<a href="#${getArticleHeadingId(article, headingNumber)}">${headingNumber}</a>
${' '}
${heading.textContent}
</div>
`;
}
}
function getArticleHeadingId(article, headingNumber) {
const headingNumberId = headingNumber.replace(/.$/, '');
return `toc${getArticleId(article)}-${headingNumberId}`;
}
let articleIdProvider = {};
let articleId = 1;
function getArticleId(article) {
if (typeof articleIdProvider[article] !== 'undefined') {
return articleIdProvider[article];
}
return articleIdProvider[article] = articleId++;
}
/**
*
* @param {Element} article
* @param {*} headingTree
* @param {string} parentHeadingNumber
*/
function injectHeadingNumber(article, headingTree, parentHeadingNumber) {
let internalNumber = 1;
for (const { heading, children } of headingTree) {
const headingNumber = `${parentHeadingNumber || ''}${internalNumber}.`;
heading.insertAdjacentHTML('afterbegin', createReverseHeadingAnchor(headingNumber));
injectHeadingNumber(article, children, headingNumber);
internalNumber += 1;
}
/**
* @param {string} headingNumber
*/
function createReverseHeadingAnchor(headingNumber) {
const headingId = getArticleHeadingId(article, headingNumber);
return `
<a id="${headingId}" href="#${headingId}">${headingNumber}</a>
${' '}
`;
}
}
/**
* @param {Element[]} headings
*/
function* buildHeadingTree(headings) {
if (headings.length < 1) {
return;
}
const [heading, ...nextHeadings] = headings;
const children = Array.from(takeWhile(nextHeadings, nextHeading => isChildHeading(nextHeading, heading)));
yield {
heading,
children: Array.from(buildHeadingTree(children)),
};
const siblings = nextHeadings.slice(children.length);
yield* buildHeadingTree(siblings);
}
/**
* @param {Element} child
* @param {Element} parent
*/
function isChildHeading(child, parent) {
const { nodeName: childLevel } = child;
const { nodeName: parentLevel } = parent;
return childLevel > parentLevel;
}
/**
* @param {T[]} array
* @param {Function<T, boolean>} predicate
*/
function* takeWhile(array, predicate) {
for (const item of array) {
if (!predicate(item)) {
return;
}
yield item;
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment