FEATURE: Toggle between timeline and TOC (#64)

This commit is contained in:
Kris 2023-10-25 12:08:24 -04:00 committed by GitHub
parent f2f309b552
commit 7c19d41864
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 240 additions and 49 deletions

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg">
<symbol id="downward" viewBox="0 0 512 512"> <symbol id="downward" viewBox="0 0 512 512">
<g> <g>
<g> <g>
@ -14,5 +14,12 @@
<path d="M401.067,443.733H110.933c-18.825,0-34.133,15.309-34.133,34.133S92.109,512,110.933,512h290.133 <path d="M401.067,443.733H110.933c-18.825,0-34.133,15.309-34.133,34.133S92.109,512,110.933,512h290.133
c18.825,0,34.133-15.309,34.133-34.133S419.883,443.733,401.067,443.733z"/> c18.825,0,34.133-15.309,34.133-34.133S419.883,443.733,401.067,443.733z"/>
</g> </g>
</g> </g>
</symbol></svg> </symbol>
<symbol id="timeline" viewBox="0 0 3.24 10.5">
<path d="M0 4.26v1.98c0 .74.5 1.34 1.12 1.34h1c.62 0 1.12-.6 1.12-1.34V4.26c0-.74-.5-1.34-1.12-1.34h-1C.5 2.92 0 3.52 0 4.26Z" class="cls-1"/>
<rect width="1.08" height="10.5" x="1.08" class="cls-1" rx=".38" ry=".38"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 850 B

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,26 +1,35 @@
$padding-basis: 0.75em; $padding-basis: 0.75em;
.d-toc-main { .d-toc-available .d-toc-wrapper {
display: none; // the toc increases the timeline container's width
// so as long as the toc is available in a topic
// we want this width to remain for the timeline
// so if the toc is toggled off, the position doesn't shift
width: 225px; width: 225px;
@media screen and (max-width: 1045px) { @media screen and (max-width: 1045px) {
.desktop-view & { .desktop-view & {
width: 150px; width: 150px;
} }
} }
}
.d-toc-main {
display: none;
border-left: 1px solid var(--primary-low); border-left: 1px solid var(--primary-low);
box-sizing: border-box; box-sizing: border-box;
a { a {
display: block; display: block;
padding: 0.15em 0; padding: 0.15em 0;
color: var(--primary-medium); color: var(--primary-700);
&.scroll-to-bottom { &.scroll-to-bottom {
color: var(--tertiary);
padding-left: $padding-basis; padding-left: $padding-basis;
margin-top: 0.5em;
font-size: var(--font-down-1);
} }
} }
#d-toc { #d-toc {
max-height: calc(100vh - 4.5em - var(--header-offset)); max-height: calc(100dvh - 7em - var(--header-offset));
padding-bottom: 0.5em;
overflow: auto; overflow: auto;
ul { ul {
list-style-type: none; list-style-type: none;
@ -47,7 +56,7 @@ $padding-basis: 0.75em;
} }
} }
> a:hover { > a:hover {
color: var(--primary-high); color: var(--primary);
} }
&.direct-active > a { &.direct-active > a {
position: relative; position: relative;
@ -124,6 +133,53 @@ a.d-toc-close {
display: none; display: none;
} }
.d-toc-timeline-toggle {
display: none;
position: fixed;
bottom: 0;
@include ellipsis;
.d-icon-timeline {
margin-right: 0.25em;
}
@media screen and (min-height: 1200px) {
// on tall screens we don't want this button
// to be incredibly distant
position: absolute;
.d-toc-timeline-visible & {
bottom: -4em;
}
bottom: -6.5em;
}
}
.topic-navigation.with-timeline:has(.timeline-docked) {
// hide toggle when timeline is docked
// (firefox doesn't support :has yet)
.d-toc-timeline-toggle {
display: none;
}
}
.d-toc-timeline-toggleable {
.d-toc-timeline-toggle {
display: block;
z-index: z("timeline");
@media screen and (max-height: 480px) {
// avoid overlapping timeline
display: none;
}
}
.with-topic-progress {
.d-toc-main {
display: block;
}
.d-toc-timeline-toggle {
display: none;
}
}
}
.d-toc-timeline-visible { .d-toc-timeline-visible {
.d-toc-main, .d-toc-main,
.d-toc-mini { .d-toc-mini {
@ -134,7 +190,7 @@ a.d-toc-close {
.d-toc-wrapper { .d-toc-wrapper {
position: fixed; position: fixed;
margin-top: 0.25em; margin-top: 0.25em;
height: calc(100vh - 50px - var(--header-offset)); height: calc(100dvh - 50px - var(--header-offset));
opacity: 0.5; opacity: 0.5;
right: -100vw; right: -100vw;
top: var(--header-offset); top: var(--header-offset);
@ -149,18 +205,25 @@ a.d-toc-close {
padding: 0.5em; padding: 0.5em;
height: 100%; height: 100%;
#d-toc { #d-toc {
max-height: calc(100% - 3em); max-height: calc(100% - 2.25em);
} }
} }
&.overlay { &.overlay {
right: 0; right: 0;
width: 75vw; width: 75vw;
opacity: 1; opacity: 1;
.d-toc-main #d-toc li.d-toc-item ul { .d-toc-main {
transition: none; display: block;
#d-toc li.d-toc-item ul {
transition: none;
}
} }
} }
a.scroll-to-bottom {
margin-top: 0.33em;
}
a.scroll-to-bottom, a.scroll-to-bottom,
a.d-toc-close { a.d-toc-close {
display: inline-block; display: inline-block;
@ -168,14 +231,17 @@ a.d-toc-close {
} }
.d-toc-icons { .d-toc-icons {
text-align: right; position: absolute;
background: var(--secondary);
right: 1.5em;
top: 0.25em;
z-index: z("timeline");
} }
} }
} }
// core overrides when timeline is active // core overrides when timeline is active
.timeline-container, .timeline-container {
#topic-progress {
display: none; display: none;
} }
.container.posts .topic-navigation.with-topic-progress { .container.posts .topic-navigation.with-topic-progress {
@ -183,6 +249,26 @@ a.d-toc-close {
} }
} }
#topic-progress-wrapper {
align-items: stretch;
.d-toc-mini {
display: none;
.d-toc-timeline-visible & {
display: block;
}
height: 100%;
.btn {
height: 100%;
}
}
.staff & {
.topic-admin-menu-button-container {
margin-left: 0.5em;
}
}
}
// core sets first child's top margin to 0 // core sets first child's top margin to 0
// ensure it's also 0 when TOC markup is first // ensure it's also 0 when TOC markup is first
.cooked > div[data-theme-toc]:first-child + * { .cooked > div[data-theme-toc]:first-child + * {
@ -232,11 +318,5 @@ a.d-toc-close {
.below-docs-topic-outlet.d-toc-wrapper { .below-docs-topic-outlet.d-toc-wrapper {
position: sticky; position: sticky;
top: calc(var(--header-offset, 60px) + 1em); top: calc(var(--header-offset, 60px) + 1em);
max-height: calc(100vh - 2em - var(--header-offset, 60px)); max-height: calc(100dvh - 2em - var(--header-offset, 60px));
.mobile-view & {
display: none;
}
.d-toc-main {
display: block;
}
} }

View File

@ -1,7 +0,0 @@
<div class="d-toc-mini">
<DButton
class="btn-primary"
@action={{this.showTOCOverlay}}
@label={{theme-prefix "table_of_contents"}}
/>
</div>

View File

@ -0,0 +1,3 @@
<div class="d-toc-mini" {{did-insert this.resetBodyClass}}>
<DButton class="btn-primary" @action={{this.showTOCOverlay}} @icon="stream" />
</div>

View File

@ -6,4 +6,9 @@ export default class DTocMini extends Component {
showTOCOverlay() { showTOCOverlay() {
document.querySelector(".d-toc-wrapper").classList.toggle("overlay"); document.querySelector(".d-toc-wrapper").classList.toggle("overlay");
} }
@action
resetBodyClass() {
document.body.classList.add("d-toc-timeline-visible");
}
} }

View File

@ -6,6 +6,8 @@ import { slugify } from "discourse/lib/utilities";
import { withPluginApi } from "discourse/lib/plugin-api"; import { withPluginApi } from "discourse/lib/plugin-api";
import I18n from "I18n"; import I18n from "I18n";
let TOChidden = false;
export default { export default {
name: "disco-toc-main", name: "disco-toc-main",
@ -80,13 +82,29 @@ export default {
); );
api.onAppEvent("topic:current-post-changed", (args) => { api.onAppEvent("topic:current-post-changed", (args) => {
// manages the timeline area width via CSS
if (!document.querySelector(".d-toc-cooked")) { if (!document.querySelector(".d-toc-cooked")) {
return; return document.body.classList.remove("d-toc-available");
}
if (args.post.post_number === 1) {
document.body.classList.add("d-toc-timeline-visible");
} else { } else {
document.body.classList.remove("d-toc-timeline-visible"); document.body.classList.add("d-toc-available");
}
// manages timeline visibility
if (args.post.post_number === 1) {
if (!TOChidden) {
handleButtonAndBody("show");
}
// don't show the toggle if there's only 1 post
if (args.post.topic.posts_count !== 1) {
document.body.classList.add("d-toc-timeline-toggleable");
}
} else {
handleButtonAndBody("hide");
}
if (args.post.topic.posts_count === 1) {
document.body.classList.remove("d-toc-timeline-toggleable");
} }
}); });
@ -103,7 +121,8 @@ export default {
}); });
api.cleanupStream(() => { api.cleanupStream(() => {
document.body.classList.remove("d-toc-timeline-visible"); handleButtonAndBody("hide");
TOChidden = false;
document.removeEventListener("click", this.clickTOC, false); document.removeEventListener("click", this.clickTOC, false);
}); });
}); });
@ -150,30 +169,67 @@ export default {
} }
}, },
insertTOC(headings) { createMainDiv() {
const dToc = document.createElement("div"); const dToc = document.createElement("div");
dToc.classList.add("d-toc-main"); dToc.classList.add("d-toc-main");
dToc.innerHTML = `<div class="d-toc-icons"> dToc.innerHTML = `<div class="d-toc-icons"><a href="#" class="d-toc-close">${iconHTML(
<a href="#" class="scroll-to-bottom" title="${I18n.t( "times"
themePrefix("post_bottom_tooltip") )}</a></div>`;
)}">${iconHTML("downward")}</a> return dToc;
<a href="#" class="d-toc-close">${iconHTML("times")}</a></div>`; },
createScrollLink() {
const scrollLink = document.createElement("a");
scrollLink.href = "#";
scrollLink.className = "scroll-to-bottom";
scrollLink.title = I18n.t(themePrefix("post_bottom_tooltip"));
scrollLink.innerHTML = `${iconHTML("downward")} ${I18n.t(
themePrefix("jump_bottom")
)}`;
return scrollLink;
},
createToggleButton() {
const toggleButton = document.createElement("button");
toggleButton.className =
"d-toc-timeline-toggle btn btn-default btn-icon-text";
toggleButton.innerHTML =
iconHTML("timeline") + I18n.t(themePrefix("topic_timeline"));
return toggleButton;
},
insertTOC(headings) {
const dToc = this.createMainDiv();
const scrollLink = this.createScrollLink();
this.toggleButton = this.createToggleButton();
const existing = document.querySelector(".d-toc-wrapper .d-toc-main"); const existing = document.querySelector(".d-toc-wrapper .d-toc-main");
const wrapper = document.querySelector(".d-toc-wrapper");
if (existing) { if (existing) {
document.querySelector(".d-toc-wrapper").replaceChild(dToc, existing); wrapper.replaceChild(dToc, existing);
} else { } else {
document.querySelector(".d-toc-wrapper").appendChild(dToc); wrapper.appendChild(dToc);
} }
const result = this.buildTOC(Array.from(headings)); const result = this.buildTOC(Array.from(headings));
document.querySelector(".d-toc-main").appendChild(result); dToc.appendChild(result);
dToc.appendChild(scrollLink);
wrapper.appendChild(this.toggleButton);
document.addEventListener("click", this.clickTOC, false); document.addEventListener("click", this.clickTOC, false);
}, },
clickTOC(e) { clickTOC(e) {
const classNames = ["d-toc-timeline-visible", "archetype-docs-topic"]; const classNames = ["d-toc-timeline-visible", "archetype-docs-topic"];
// toggle timeline visibility
if (e.target.closest(".d-toc-timeline-toggle")) {
handleButtonAndBody("toggle");
e.preventDefault();
return false;
}
if ( if (
!classNames.some((className) => !classNames.some((className) =>
document.body.classList.contains(className) document.body.classList.contains(className)
@ -212,7 +268,7 @@ export default {
top: rect.bottom + window.scrollY - headerOffset() - 10, top: rect.bottom + window.scrollY - headerOffset() - 10,
behavior: "smooth", behavior: "smooth",
}); });
document.querySelector(".d-toc-wrapper").classList.remove("overlay");
e.preventDefault(); e.preventDefault();
return false; return false;
} }
@ -317,3 +373,40 @@ function parentsUntil(el, selector, filter) {
} }
return result; return result;
} }
function handleButtonAndBody(action) {
const body = document.body;
const button = document.querySelector("button.d-toc-timeline-toggle");
switch (action) {
case "toggle":
body.classList.toggle("d-toc-timeline-visible");
TOChidden = !TOChidden;
break;
case "hide":
body.classList.remove(
"d-toc-timeline-visible",
"d-toc-timeline-toggleable"
);
break;
case "show":
body.classList.add("d-toc-timeline-visible", "d-toc-timeline-toggleable");
break;
}
if (button) {
const translationKey = body.classList.contains("d-toc-timeline-visible")
? "topic_timeline"
: "table_of_contents";
const icon = body.classList.contains("d-toc-timeline-visible")
? "timeline"
: "stream";
button.innerHTML = `${iconHTML(icon)}${I18n.t(
themePrefix(translationKey)
)} `;
}
}

View File

@ -1,5 +1,7 @@
en: en:
table_of_contents: table of contents table_of_contents: Table of contents
topic_timeline: Topic timeline
jump_bottom: Jump to replies
insert_table_of_contents: Insert table of contents insert_table_of_contents: Insert table of contents
post_bottom_tooltip: Navigate to post controls post_bottom_tooltip: Navigate to post controls
theme_metadata: theme_metadata:

View File

@ -3,7 +3,7 @@ import {
exists, exists,
query, query,
} from "discourse/tests/helpers/qunit-helpers"; } from "discourse/tests/helpers/qunit-helpers";
import { visit } from "@ember/test-helpers"; import { click, visit } from "@ember/test-helpers";
import { test } from "qunit"; import { test } from "qunit";
import topicFixtures from "discourse/tests/fixtures/topic"; import topicFixtures from "discourse/tests/fixtures/topic";
import { cloneJSON } from "discourse-common/lib/object"; import { cloneJSON } from "discourse-common/lib/object";
@ -66,6 +66,14 @@ acceptance("DiscoTOC - main", function (needs) {
"heading gets an ID even when it has no Latin characters" "heading gets an ID even when it has no Latin characters"
); );
}); });
test("TOC can be toggled to reveal timeline", async function (assert) {
await visit("/t/internationalization-localization/280");
assert.ok(exists(".d-toc-timeline-toggle"), "TOC toggle exists");
await click(".d-toc-timeline-toggle");
assert.ok(exists(".topic-timeline"), "The timeline is shown on toggle");
});
}); });
acceptance("DiscoTOC - off", function (needs) { acceptance("DiscoTOC - off", function (needs) {