Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Table of content #199

Merged
merged 53 commits into from
Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
f5d1a73
Create nodemon.json
talyguryn Jun 9, 2022
2074740
Add table of content
talyguryn Jun 9, 2022
443edb4
update view
talyguryn Jun 9, 2022
a106046
remove logs
talyguryn Jun 9, 2022
f055395
update tags var
talyguryn Jun 10, 2022
18aad62
update layout
talyguryn Jun 10, 2022
48ee9b0
Revert "update layout"
talyguryn Jun 16, 2022
0d70f70
Merge branch 'main' into feature/table-of-content
talyguryn Jun 21, 2022
88c8314
update layout
talyguryn Jun 21, 2022
6830472
Update layout.pcss
talyguryn Jun 28, 2022
a23c243
Merge branch 'main' into feature/table-of-content
talyguryn Jun 28, 2022
83182de
update from master
talyguryn Jun 29, 2022
b24fdb0
Update sidebar.twig
talyguryn Jun 29, 2022
cfba1b7
remove non valued changes
talyguryn Jun 29, 2022
ce8fdde
Update table-of-content.js
talyguryn Jun 29, 2022
c4c9a1b
Update table-of-content.pcss
talyguryn Jun 29, 2022
9fcbe26
Update table-of-content.pcss
talyguryn Jun 29, 2022
78a30d2
Update layout.pcss
talyguryn Jun 29, 2022
88b9cf3
Update table-of-content.js
talyguryn Jun 29, 2022
5177227
remove unused styles
talyguryn Jul 1, 2022
bcfef10
not module
talyguryn Jul 5, 2022
7e71079
rename var
talyguryn Jul 5, 2022
fa9e482
remove log
talyguryn Jul 5, 2022
8cf75b4
update structure
talyguryn Jul 6, 2022
e1d80fa
Update table-of-content.js
talyguryn Jul 7, 2022
67634a3
Update table-of-content.js
talyguryn Jul 7, 2022
fc014a0
Update layout.pcss
talyguryn Jul 12, 2022
8aa22bc
Update table-of-content.js
talyguryn Jul 12, 2022
62ae6e5
try not to use intersection observer
talyguryn Jul 12, 2022
1dfc5f0
Merge branch 'main' into feature/table-of-content
talyguryn Jul 12, 2022
8b100ee
Update table-of-content.js
talyguryn Jul 13, 2022
fd96043
fix scroll padding
talyguryn Jul 13, 2022
d1c64c2
fix header component layout
talyguryn Jul 13, 2022
4088fff
update logic
talyguryn Jul 13, 2022
9b4a53d
fix click area
talyguryn Jul 13, 2022
fe04c5b
Update table-of-content.js
talyguryn Jul 13, 2022
37fbf19
Update table-of-content.js
talyguryn Jul 13, 2022
d658954
small fixes
talyguryn Jul 20, 2022
3f413ad
remove unused
talyguryn Jul 20, 2022
eb5bdec
Update table-of-content.js
talyguryn Jul 20, 2022
416db18
Merge branch 'main' into feature/table-of-content
talyguryn Jul 20, 2022
03822f7
Update decorators.js
talyguryn Jul 20, 2022
d0a9771
Update table-of-content.js
talyguryn Jul 20, 2022
4ff756e
Update table-of-content.js
talyguryn Jul 20, 2022
c740b97
Update table-of-content.js
talyguryn Jul 20, 2022
6f6117d
Update table-of-content.js
talyguryn Jul 20, 2022
23be283
Update table-of-content.js
talyguryn Jul 20, 2022
1a10093
fix scroll issues, resolve eslit ts/js conflicts
neSpecc Jul 20, 2022
a402624
add some todos
neSpecc Jul 20, 2022
7b73720
handle up-direction scroll as well
neSpecc Jul 20, 2022
8bc5ae0
optimization
neSpecc Jul 20, 2022
65020ce
update offsets
talyguryn Jul 25, 2022
40b999d
Update header.pcss
talyguryn Jul 25, 2022
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
13 changes: 13 additions & 0 deletions nodemon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
nikmel2803 marked this conversation as resolved.
Show resolved Hide resolved
"verbose": true,
"ignore": [
".git",
"node_modules",
"public",
"src/frontend"
],
"watch": [
"**/*"
],
"ext": "js,twig"
}
1 change: 1 addition & 0 deletions src/backend/views/layout.twig
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
{% block body %}{% endblock %}
</div>
</div>
<aside class="docs__aside-right" id="layout-right-column"></aside>
</div>
<script src="/dist/main.bundle.js"></script>
{% if config.yandexMetrikaId is not empty %}
Expand Down
1 change: 1 addition & 0 deletions src/frontend/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import ModuleDispatcher from 'module-dispatcher';
import Writing from './modules/writing';
import Page from './modules/page';
import Extensions from './modules/extensions';
import TableOfContent from "./modules/table-of-content";

/**
* Main app class
Expand Down
26 changes: 22 additions & 4 deletions src/frontend/js/modules/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
* @class Page
* @classdesc Class for page module
*/
export default class Writing {
export default class Page {
/**
* Creates base properties
*/
constructor() {
this.codeStyler = null;
this.tableOfContent = null;
}

/**
Expand All @@ -21,7 +22,8 @@ export default class Writing {
*/
init(settings = {}, moduleEl) {
this.codeStyler = this.createCodeStyling();
};
this.tableOfContent = this.createTableOfContent();
}

/**
* Init code highlighting
Expand All @@ -30,7 +32,23 @@ export default class Writing {
const { default: CodeStyler } = await import(/* webpackChunkName: "code-styling" */ './../classes/codeStyler');

return new CodeStyler({
selector: '.block-code__content'
selector: '.block-code__content',
});
}

/**
* Init table of content
* @return {Promise<TableOfContent>}
*/
async createTableOfContent() {
const { default: TableOfContent } = await import(/* webpackChunkName: "table-of-content" */ './table-of-content');

return new TableOfContent({
tagSelector:
'h2.block-header--anchor,' +
'h3.block-header--anchor,' +
'h4.block-header--anchor',
tocWrapperSelector: '#layout-right-column',
});
};
}
}
214 changes: 214 additions & 0 deletions src/frontend/js/modules/table-of-content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/**
talyguryn marked this conversation as resolved.
Show resolved Hide resolved
* Generate dynamic table of content
*/
export default class TableOfContent {
/**
* Initialize table of content
*/
constructor({ tagSelector, tocWrapperSelector }) {
neSpecc marked this conversation as resolved.
Show resolved Hide resolved
this.tagSelector = tagSelector || 'h2,h3,h4';
this.tocWrapperSelector = tocWrapperSelector;

this.init();
}

/**
* Initialize table of content
*/
init() {
this.findTagsOnThePage();
talyguryn marked this conversation as resolved.
Show resolved Hide resolved

/**
* Check if no tags found
*/
if (this.tags.length === 0) {
talyguryn marked this conversation as resolved.
Show resolved Hide resolved
console.info('Table of content is not needed');
talyguryn marked this conversation as resolved.
Show resolved Hide resolved
return;
}

this.createTableOfContent();
this.addTableOfContent();
this.initIntersectionObserver();
}

/**
* Find all tags on the page
*/
findTagsOnThePage() {
this.tags = Array.from(document.querySelectorAll(this.tagSelector));
}

/**
* Create table of content
*
* <section>
* <header>On this page</header>
* <ul>
* <li><a href="#"></a></li>
* ...
* </ul>
* </section>
*/
createTableOfContent() {
this.tocElement = document.createElement('section');
this.tocElement.classList.add('table-of-content__list');
talyguryn marked this conversation as resolved.
Show resolved Hide resolved

this.tags.forEach((tag) => {
const linkTarget = tag.querySelector('a').getAttribute('href');

const linkWrapper = document.createElement('li');
const linkBlock = document.createElement('a');

linkBlock.innerText = tag.innerText;
linkBlock.href = `${linkTarget}`;

linkWrapper.classList.add('table-of-content__list-item');

// additional indent for h3-h6
linkWrapper.classList.add(`table-of-content__list-item--${tag.tagName.toLowerCase()}`);

linkWrapper.appendChild(linkBlock);
this.tocElement.appendChild(linkWrapper);
});
}

/**
* Add table of content to the page
*/
addTableOfContent() {
const header = document.createElement('header');
const container = document.createElement('section');

header.innerText = 'On this page';
header.classList.add('table-of-content__header');
container.appendChild(header);

container.classList.add('table-of-content');
container.appendChild(this.tocElement);

const tocWrapper = document.querySelector(this.tocWrapperSelector);

if (!tocWrapper) {
throw new Error('Table of content wrapper not found');
}

tocWrapper.appendChild(container);
}

/**
* Init intersection observer
*/
initIntersectionObserver() {
const options = {
rootMargin: '-5% 0 -80%',
};

const callback = (entries) => {
entries.forEach((entry) => {
const target = entry.target;
const targetLink = target.querySelector('a').getAttribute('href');

/**
* Intersection state of block
*
* @type {boolean}
*/
const isVisible = entry.isIntersecting;

/**
* Calculate scroll direction whith the following logic:
*
* DOWN: if block top is BELOW (coordinate value is greater) the intersection root top
* and block is NOT VISIBLE
*
* DOWN: if block top is ABOVE (coordinate value is less) the intersection root top
* and block is VISIBLE
*
* UP: if block top is ABOVE (coordinate value is less) the intersection root top
* and block is visible
*
* UP: if block top is BELOW (coordinate value is greater) the intersection root top
* and block is NOT VISIBLE
*
* Therefore we can use XOR operator for
* - is block's top is above root's top
* - is block visible
*
* @type {string}
*/
const scrollDirection = ((entry.boundingClientRect.top < entry.rootBounds.top) ^ (entry.isIntersecting)) ? 'down' : 'up';
talyguryn marked this conversation as resolved.
Show resolved Hide resolved

/**
* If a header becomes VISIBLE on scroll DOWN
* then highlight its link
*
* = moving to the new chapter
*/
if (isVisible && scrollDirection === 'down') {
this.setActiveLink(targetLink);
}

/**
* If a header becomes NOT VISIBLE on scroll UP
* then highlight previous link
*
* = moving to the previous chapter
*/
if (!isVisible && scrollDirection === 'up') {
this.setActiveLink(targetLink, true);
}
});
};

/**
* Create intersection observer
*/
this.observer = new IntersectionObserver(callback, options);

/**
* Add observer to found tags
*/
this.tags.reverse().forEach((tag) => {
this.observer.observe(tag);
});
}

/**
* Highlight link's item with a given href
*
* @param {string} targetLink - href of the link
* @param {boolean} [needHighlightPrevious=false] - need to highlight previous link
talyguryn marked this conversation as resolved.
Show resolved Hide resolved
*/
setActiveLink(targetLink, needHighlightPrevious = false) {
/**
* Clear all links
*/
this.tocElement.querySelectorAll('li').forEach((link) => {
talyguryn marked this conversation as resolved.
Show resolved Hide resolved
link.classList.remove('table-of-content__list-item--active');
});

/**
* Looking for a target link
*/
const targetElement = this.tocElement.querySelector(`a[href="${targetLink}"]`);

/**
* Getting link's wrapper
*/
let listItem = targetElement.parentNode;

/**
* Change target list item if it is needed
*/
if (needHighlightPrevious) {
listItem = listItem.previousSibling;
}

/**
* If target list item is found then highlight it
*/
if (listItem) {
listItem.classList.add('table-of-content__list-item--active');
}
}
}
60 changes: 60 additions & 0 deletions src/frontend/styles/components/table-of-content.pcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
.table-of-content {
position: sticky;
max-height: 100vh;
overflow-y: auto;

margin-top: 30px;
top: 30px;
width: 300px;
border-left: 1px solid #E8E8EB;
padding-left: 22px;
talyguryn marked this conversation as resolved.
Show resolved Hide resolved

&__header {
font-size: 16px;
font-weight: 600;
line-height: 21px;
letter-spacing: -0.01em;

margin-bottom: 12px;
padding: 0 6px;
}

&__list {
margin: 0;

display: flex;
flex-direction: column;
align-items: flex-start;
padding: 0;

list-style: none;

gap: 6px;

&-item {
border-radius: 8px;
talyguryn marked this conversation as resolved.
Show resolved Hide resolved

&:hover {
background-color: #F3F6F8;
talyguryn marked this conversation as resolved.
Show resolved Hide resolved
cursor: pointer;
}

&--active {
background-color: #F3F6F8;
}

&--h3 { margin-left: 6px; }
&--h4 { margin-left: 12px; }
&--h5 { margin-left: 18px; }
&--h6 { margin-left: 24px; }

& > a {
margin: 4px 8px;
display: block;
font-size: 14px;
letter-spacing: -0.01em;
line-height: 150%;
}
}
}
}
10 changes: 10 additions & 0 deletions src/frontend/styles/layout.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@
max-width: var(--layout-width-main-col);
margin: 0 auto;
}

@media not (--mobile) {
margin-bottom: 80vh;
}
}

&__aside-right {
@media (--mobile) {
display: none;
}
}

&__aside,
Expand Down
1 change: 1 addition & 0 deletions src/frontend/styles/main.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
@import './components/landing.pcss';
@import './components/auth.pcss';
@import './components/button.pcss';
@import './components/table-of-content.pcss';

body {
font-family: system-ui, Helvetica, Arial, Verdana;
Expand Down