move implementation to a glimmer component
This commit is contained in:
parent
89749d267a
commit
097409ba20
|
@ -1,16 +1,5 @@
|
|||
const WRAP_CLASS = "markdown-tabs";
|
||||
|
||||
function setupTabs(helper) {
|
||||
helper.allowList([
|
||||
"div.markdown-tabs",
|
||||
"div.markdown-tabs-wrapper",
|
||||
"div.markdown-tab",
|
||||
"div.markdown-tab-panels",
|
||||
"div.markdown-tab-panel",
|
||||
"a[data-tab-id]",
|
||||
"div[data-tab-id]",
|
||||
"div[data-selected]",
|
||||
]);
|
||||
helper.allowList(["div.markdown-tabs", "section", "section[data-selected]"]);
|
||||
|
||||
helper.registerPlugin((md) => {
|
||||
const ruler = md.block.bbcode.ruler;
|
||||
|
@ -21,10 +10,7 @@ function setupTabs(helper) {
|
|||
state.env.tabContent = [];
|
||||
|
||||
const token = state.push("tab_open", "div", 1);
|
||||
token.attrs = [["class", WRAP_CLASS]];
|
||||
|
||||
const wrapperToken = state.push("tab_wrapper_open", "div", 1);
|
||||
wrapperToken.attrs = [["class", "markdown-tabs-wrapper"]];
|
||||
token.attrs = [["class", "markdown-tabs"]];
|
||||
},
|
||||
|
||||
after(state) {
|
||||
|
@ -44,61 +30,25 @@ function setupTabs(helper) {
|
|||
state.env.tabContent[0].selected = true;
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
state.env.tabContent.forEach((tab) => {
|
||||
const tabId = `tab-${index}`;
|
||||
index++;
|
||||
|
||||
tab.id = tabId;
|
||||
|
||||
const tabToken = state.push("tab_button_open", "div", 1);
|
||||
tabToken.attrs = [
|
||||
["class", "markdown-tab"],
|
||||
["data-tab-id", tabId],
|
||||
];
|
||||
const section = state.push("tab_panel_open", "section", 1);
|
||||
if (tab.selected) {
|
||||
tabToken.attrs.push(["data-selected", ""]);
|
||||
section.attrs = [["data-selected", ""]];
|
||||
}
|
||||
|
||||
const linkToken = state.push("tab_link_open", "a", 1);
|
||||
linkToken.attrs = [["data-tab-id", tabId]];
|
||||
|
||||
// Add tab name
|
||||
state.push("tab_header_open", "h4", 1);
|
||||
const textToken = state.push("text", "", 0);
|
||||
textToken.content = tab.name;
|
||||
textToken.content = tab.name.toString().trim();
|
||||
state.push("tab_header_close", "h4", -1);
|
||||
|
||||
state.push("tab_link_close", "a", -1);
|
||||
state.push("tab_button_close", "div", -1);
|
||||
});
|
||||
}
|
||||
tab.content.forEach((token) => state.tokens.push(token));
|
||||
|
||||
state.push("tab_wrapper_close", "div", -1);
|
||||
state.push("panels_open", "div", 1).attrs = [
|
||||
["class", "markdown-tab-panels"],
|
||||
];
|
||||
|
||||
if (state.env.tabContent) {
|
||||
state.env.tabContent.forEach((tab) => {
|
||||
const panelToken = state.push("tab_panel_open", "div", 1);
|
||||
panelToken.attrs = [
|
||||
["class", "markdown-tab-panel"],
|
||||
["data-tab-id", tab.id],
|
||||
];
|
||||
if (tab.selected) {
|
||||
panelToken.attrs.push(["data-selected", ""]);
|
||||
}
|
||||
|
||||
if (tab.content) {
|
||||
tab.content.forEach((token) => state.tokens.push(token));
|
||||
}
|
||||
|
||||
state.push("tab_panel_close", "div", -1);
|
||||
state.push("tab_panel_close", "section", -1);
|
||||
});
|
||||
}
|
||||
|
||||
state.env.tabContent = null;
|
||||
|
||||
state.push("panels_close", "div", -1);
|
||||
state.push("tab_close", "div", -1);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { fn } from "@ember/helper";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
|
||||
class Tab {
|
||||
@tracked selected;
|
||||
|
||||
constructor(title, content, selected) {
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
this.selected = selected;
|
||||
}
|
||||
}
|
||||
|
||||
export default class MarkdownTabs extends Component {
|
||||
@tracked tabs;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.tabs = this.args.tabs.map((tab) => {
|
||||
return new Tab(tab.title, tab.content, tab.selected);
|
||||
});
|
||||
}
|
||||
|
||||
headerClass(tab) {
|
||||
return tab.selected
|
||||
? "markdown-tabs__header selected"
|
||||
: "markdown-tabs__header";
|
||||
}
|
||||
|
||||
panelClass(tab) {
|
||||
return tab.selected
|
||||
? "markdown-tabs__panel selected"
|
||||
: "markdown-tabs__panel";
|
||||
}
|
||||
|
||||
@action
|
||||
selectTab(tab, event) {
|
||||
this.tabs.forEach((t) => (t.selected = t === tab));
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
<template>
|
||||
<div class="markdown-tabs__headers" role="tablist">
|
||||
{{#each this.tabs as |tab|}}
|
||||
<div class={{this.headerClass tab}}>
|
||||
<a href {{on "click" (fn this.selectTab tab)}}>{{tab.title}}</a>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="markdown-tabs__panels">
|
||||
{{#each this.tabs as |tab|}}
|
||||
<div class={{this.panelClass tab}} role="tabpanel">
|
||||
{{htmlSafe tab.content}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import MarkdownTabs from "discourse/components/markdown-tabs";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
|
||||
function initializeMarkdownTabs(api) {
|
||||
api.decorateCookedElement(
|
||||
(elem, helper) => {
|
||||
for (const tabsElement of [...elem.querySelectorAll(".markdown-tabs")]) {
|
||||
const tabs = [...tabsElement.querySelectorAll("section")].map(
|
||||
(section) => {
|
||||
return {
|
||||
title: section.querySelector("h4").textContent.trim(),
|
||||
content: section.innerHTML
|
||||
.replace(section.querySelector("h4").outerHTML, "")
|
||||
.trim(),
|
||||
selected: section.hasAttribute("data-selected"),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
while (tabsElement.firstChild) {
|
||||
tabsElement.removeChild(tabsElement.firstChild);
|
||||
}
|
||||
|
||||
helper.renderGlimmer(tabsElement, <template>
|
||||
<MarkdownTabs @tabs={{tabs}} />
|
||||
</template>);
|
||||
}
|
||||
},
|
||||
{ id: "discourse-tabs", onlyStream: false }
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "discourse-tabs",
|
||||
initialize() {
|
||||
withPluginApi("0.8.7", initializeMarkdownTabs);
|
||||
},
|
||||
};
|
|
@ -1,50 +0,0 @@
|
|||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
|
||||
function initializeMarkdownTabs(api) {
|
||||
api.decorateCooked(
|
||||
($elem) => {
|
||||
const tabs = $elem[0].querySelectorAll(".markdown-tabs");
|
||||
if (!tabs.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
tabs.forEach((tabContainer) => {
|
||||
const tabButtons = tabContainer.querySelectorAll(".markdown-tab");
|
||||
const panels = tabContainer.querySelectorAll(".markdown-tab-panel");
|
||||
|
||||
tabButtons.forEach((tab) => {
|
||||
tab.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (tab.hasAttribute("data-selected")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tabId = tab.getAttribute("data-tab-id");
|
||||
|
||||
// Remove selected state from all tabs and panels
|
||||
tabButtons.forEach((t) => t.removeAttribute("data-selected"));
|
||||
panels.forEach((p) => p.removeAttribute("data-selected"));
|
||||
|
||||
// Set selected state for clicked tab and its panel
|
||||
tab.setAttribute("data-selected", "");
|
||||
const panel = tabContainer.querySelector(
|
||||
`.markdown-tab-panel[data-tab-id="${tabId}"]`
|
||||
);
|
||||
if (panel) {
|
||||
panel.setAttribute("data-selected", "");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
{ id: "discourse-tabs" }
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "discourse-tabs",
|
||||
initialize() {
|
||||
withPluginApi("0.8.7", initializeMarkdownTabs);
|
||||
},
|
||||
};
|
|
@ -1,17 +1,17 @@
|
|||
.markdown-tabs {
|
||||
margin: 1em 0;
|
||||
|
||||
.markdown-tabs-wrapper {
|
||||
&__headers {
|
||||
display: flex;
|
||||
gap: 0.2em;
|
||||
border-bottom: 2px solid var(--primary-low);
|
||||
padding: 0 0.2em;
|
||||
}
|
||||
|
||||
.markdown-tab {
|
||||
&__header {
|
||||
margin-bottom: -2px;
|
||||
|
||||
&[data-selected] {
|
||||
&.selected {
|
||||
a {
|
||||
color: var(--tertiary);
|
||||
font-weight: 500;
|
||||
|
@ -36,15 +36,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
.markdown-tab-panels {
|
||||
&__panels {
|
||||
padding: 0 0;
|
||||
}
|
||||
|
||||
.markdown-tab-panel {
|
||||
display: none;
|
||||
&__panel {
|
||||
display: none;
|
||||
|
||||
&[data-selected] {
|
||||
display: block;
|
||||
}
|
||||
&.selected {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2849,24 +2849,16 @@ HTML
|
|||
html = PrettyText.cook(md)
|
||||
expected = <<~HTML
|
||||
<div class="markdown-tabs">
|
||||
<div class="markdown-tabs-wrapper">
|
||||
<div class="markdown-tab" data-tab-id="tab-0">
|
||||
<a data-tab-id="tab-0">
|
||||
First Tab</a>
|
||||
</div>
|
||||
<div class="markdown-tab" data-tab-id="tab-1" data-selected="">
|
||||
<a data-tab-id="tab-1">
|
||||
Second Tab</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="markdown-tab-panels">
|
||||
<div class="markdown-tab-panel" data-tab-id="tab-0">
|
||||
<section>
|
||||
<h4>
|
||||
First Tab</h4>
|
||||
<p>Tab 1 <strong>content</strong></p>
|
||||
</div>
|
||||
<div class="markdown-tab-panel" data-tab-id="tab-1" data-selected="">
|
||||
</section>
|
||||
<section data-selected="">
|
||||
<h4>
|
||||
Second Tab</h4>
|
||||
<p>Tab 2 content</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
HTML
|
||||
expect(html).to match_html(expected)
|
||||
|
@ -2887,24 +2879,16 @@ HTML
|
|||
html = PrettyText.cook(md)
|
||||
expected = <<~HTML
|
||||
<div class="markdown-tabs">
|
||||
<div class="markdown-tabs-wrapper">
|
||||
<div class="markdown-tab" data-tab-id="tab-0" data-selected="">
|
||||
<a data-tab-id="tab-0">
|
||||
First Tab</a>
|
||||
</div>
|
||||
<div class="markdown-tab" data-tab-id="tab-1">
|
||||
<a data-tab-id="tab-1">
|
||||
Second Tab</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="markdown-tab-panels">
|
||||
<div class="markdown-tab-panel" data-tab-id="tab-0" data-selected="">
|
||||
<section>
|
||||
<h4>
|
||||
First Tab</h4>
|
||||
<p>Tab 1 <strong>content</strong></p>
|
||||
</div>
|
||||
<div class="markdown-tab-panel" data-tab-id="tab-1">
|
||||
</section>
|
||||
<section>
|
||||
<h4>
|
||||
Second Tab</h4>
|
||||
<p>Tab 2 content</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
HTML
|
||||
expect(html).to match_html(expected)
|
||||
|
|
Loading…
Reference in New Issue