REFACTOR: General component overhaul (#19)

This commit is contained in:
Penar Musaraj
2022-01-18 09:18:21 -05:00
committed by GitHub
parent 5b2f5a455e
commit 20366c671d
19 changed files with 2574 additions and 802 deletions
+1
View File
@@ -1 +1,2 @@
2.7.13: 5b2f5a455e1adf8ce5e8c1cfb7fbc3c388d3d82a
2.6.0.beta3: 68d40fe9f5b625cf465adc31b502a54e16d02cc6
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "eslint-config-discourse",
"ignorePatterns": [
"javascripts/vendor/*"
],
"globals": {
"settings": "readonly",
"themePrefix": "readonly"
}
}
+43
View File
@@ -0,0 +1,43 @@
name: Linting
on:
push:
branches:
- main
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v1
with:
node-version: 12
- name: Yarn install
run: yarn install
- name: ESLint
if: ${{ always() }}
run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,javascripts}
- name: Prettier
if: ${{ always() }}
shell: bash
run: |
yarn prettier -v
shopt -s extglob
if ls @(javascripts|desktop|mobile|common|scss)/**/*.@(scss|js|es6) &> /dev/null; then
yarn prettier --list-different "@(javascripts|desktop|mobile|common|scss)/**/*.{scss,js,es6}"
fi
if ls test/**/*.@(js|es6) &> /dev/null; then
yarn prettier --list-different "test/**/*.{js,es6}"
fi
- name: Ember template lint
if: ${{ always() }}
run: yarn ember-template-lint javascripts
+121
View File
@@ -0,0 +1,121 @@
name: Tests
on:
push:
branches:
- main
pull_request:
jobs:
build:
runs-on: ubuntu-latest
container: discourse/discourse_test:slim-browsers
timeout-minutes: 30
env:
DISCOURSE_HOSTNAME: www.example.com
RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072
RAILS_ENV: development
QUNIT_RAILS_ENV: development
PGHOST: postgres
PGUSER: discourse
PGPASSWORD: discourse
services:
postgres:
image: postgres:13
ports:
- 5432:5432
env:
POSTGRES_USER: discourse
POSTGRES_PASSWORD: discourse
options: >-
--mount type=tmpfs,destination=/var/lib/postgresql/data
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v2
with:
repository: discourse/discourse
fetch-depth: 1
- name: Install component
uses: actions/checkout@v2
with:
path: tmp/component
fetch-depth: 1
- name: Setup Git
run: |
git config --global user.email "[email protected]"
git config --global user.name "Discourse CI"
- name: Start redis
run: |
redis-server /etc/redis/redis.conf &
- name: Start Postgres
run: |
chown -R postgres /var/run/postgresql
sudo -E -u postgres script/start_test_db.rb
sudo -u postgres psql -c "CREATE ROLE $PGUSER LOGIN SUPERUSER PASSWORD '$PGPASSWORD';"
- name: Bundler cache
uses: actions/cache@v2
with:
path: vendor/bundle
key: ${{ runner.os }}-2.7-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-2.7-gem-
- name: Setup gems
run: |
bundle config --local path vendor/bundle
bundle config --local deployment true
bundle config --local without development
bundle install --jobs 4
bundle clean
- name: Get yarn cache directory
id: yarn-cache-dir
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn cache
uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.os }}-yarn-
- name: Yarn install
run: yarn install
- name: Migrate database
run: |
bin/rake db:create
bin/rake db:migrate
- name: Check qunit existence
id: check_qunit
shell: bash
run: |
shopt -s extglob
if ls tmp/component/test/**/*.@(js|es6) &> /dev/null; then
echo "::set-output name=files_exist::true"
fi
- name: Component QUnit
if: steps.check_qunit.outputs.files_exist == 'true'
run: |
bundle exec rake themes:install -- '--{"${{ github.event.repository.name }}": "tmp/component"}'
UNICORN_TIMEOUT=120 bundle exec rake themes:qunit[name,${{ github.event.repository.name }}]
timeout-minutes: 10
- name: Lint English locale
if: ${{ always() }}
run: bundle exec ruby script/i18n_lint.rb "tmp/component/locales/en.yml"
+3
View File
@@ -0,0 +1,3 @@
.discourse-site
.DS_Store
node_modules
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
plugins: ["ember-template-lint-plugin-discourse"],
extends: "discourse:recommended",
};
+167 -299
View File
File diff suppressed because it is too large Load Diff
-395
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
<a {{action "showTOCOverlay"}} href class="btn btn-primary">
{{theme-i18n "table_of_contents"}}
</a>
@@ -0,0 +1,7 @@
export default {
actions: {
showTOCOverlay() {
document.querySelector(".d-toc-wrapper").classList.toggle("overlay");
},
},
};
@@ -0,0 +1 @@
{{!-- TOC placeholder --}}
@@ -0,0 +1,47 @@
import I18n from "I18n";
import { withPluginApi } from "discourse/lib/plugin-api";
export default {
name: "disco-toc-composer",
initialize() {
withPluginApi("1.0.0", (api) => {
const currentUser = api.getCurrentUser();
if (!currentUser) {
return;
}
const minimumTL = settings.minimum_trust_level_to_create_TOC;
if (currentUser.trust_level >= minimumTL) {
if (!I18n.translations[I18n.currentLocale()].js.composer) {
I18n.translations[I18n.currentLocale()].js.composer = {};
}
I18n.translations[I18n.currentLocale()].js.composer.contains_dtoc = " ";
api.modifyClass("controller:composer", {
pluginId: "DiscoTOC",
actions: {
insertDtoc() {
this.get("toolbarEvent").applySurround(
`<div data-theme-toc="true">`,
`</div>`,
"contains_dtoc"
);
},
},
});
api.addToolbarPopupMenuOptionsCallback((controller) => {
return {
action: "insertDtoc",
icon: "align-left",
label: themePrefix("insert_table_of_contents"),
condition: controller.get("model.creatingTopic"),
};
});
}
});
},
};
@@ -0,0 +1,263 @@
import domUtils from "discourse-common/utils/dom-utils";
import { headerOffset } from "discourse/lib/offset-calculator";
import { iconHTML } from "discourse-common/lib/icon-library";
import { slugify } from "discourse/lib/utilities";
import { withPluginApi } from "discourse/lib/plugin-api";
import I18n from "I18n";
export default {
name: "disco-toc-main",
initialize() {
withPluginApi("1.0.0", (api) => {
api.decorateCookedElement(
(el, helper) => {
if (helper) {
const post = helper.getModel();
if (post.post_number !== 1) {
return;
}
if (!el.querySelector(`[data-theme-toc="true"]`)) {
return;
}
let dTocHeadingSelectors =
":scope > h1, :scope > h2, :scope > h3, :scope > h4, :scope > h5, :scope > h6";
const headings = el.querySelectorAll(dTocHeadingSelectors);
if (headings.length < 1) {
return;
}
headings.forEach((h) => {
const id =
h.getAttribute("id") ||
slugify(`toc-${h.nodeName}-${h.textContent}`);
h.setAttribute("id", id);
h.setAttribute("data-d-toc", id);
h.classList.add("d-toc-post-heading");
});
el.classList.add("d-toc-cooked");
const dToc = document.createElement("div");
dToc.classList.add("d-toc-main");
dToc.innerHTML = `<div class="d-toc-icons">
<a href="" class="scroll-to-bottom" title="${I18n.t(
themePrefix("post_bottom_tooltip")
)}">${iconHTML("downward")}</a>
<a href="" class="d-toc-close">${iconHTML("times")}</a></div>`;
const existing = document.querySelector(
".d-toc-wrapper .d-toc-main"
);
if (existing) {
document
.querySelector(".d-toc-wrapper")
.replaceChild(dToc, existing);
} else {
document.querySelector(".d-toc-wrapper").appendChild(dToc);
}
const startingLevel =
parseInt(headings[0].tagName.substring(1), 10) - 1;
let result = document.createElement("div");
result.setAttribute("id", "d-toc");
buildTOC(headings, result, startingLevel || 1);
document.querySelector(".d-toc-main").appendChild(result);
document.addEventListener("click", this.clickTOC, false);
}
},
{
id: "disco-toc",
onlyStream: true,
afterAdopt: true,
}
);
api.onAppEvent("topic:current-post-changed", (args) => {
if (!document.querySelector(".d-toc-cooked")) {
return;
}
if (args.post.post_number === 1) {
document.body.classList.add("d-toc-timeline-visible");
} else {
document.body.classList.remove("d-toc-timeline-visible");
}
});
api.onAppEvent("topic:current-post-scrolled", (args) => {
if (args.postIndex !== 1) {
return;
}
const headings = document.querySelectorAll(".d-toc-post-heading");
let closestHeadingDistance = null;
let closestHeading = null;
headings.forEach((heading) => {
const distance = Math.abs(
domUtils.offset(heading).top - headerOffset() - window.scrollY
);
if (
closestHeadingDistance == null ||
distance < closestHeadingDistance
) {
closestHeadingDistance = distance;
closestHeading = heading;
} else {
return false;
}
});
if (closestHeading) {
document.querySelectorAll("#d-toc li").forEach((listItem) => {
listItem.classList.remove("active");
listItem.classList.remove("direct-active");
});
const anchor = document.querySelector(
`#d-toc a[data-d-toc="${closestHeading.getAttribute("id")}"]`
);
anchor.parentElement.classList.add("direct-active");
parentsUntil(anchor, "#d-toc", ".d-toc-item").forEach((liParent) => {
liParent.classList.add("active");
});
}
});
api.cleanupStream(() => {
document.body.classList.remove("d-toc-timeline-visible");
document.removeEventListener("click", this.clickTOC, false);
});
});
},
clickTOC(e) {
if (!document.body.classList.contains("d-toc-timeline-visible")) {
return;
}
// link to each heading
if (e.target.hasAttribute("data-d-toc")) {
const target = `#${e.target.getAttribute("data-d-toc")}`;
const scrollTo = domUtils.offset(
document.querySelector(`.d-toc-cooked ${target}`)
).top;
window.scrollTo({
top: scrollTo - headerOffset() - 10,
behavior: "smooth",
});
document.querySelector(".d-toc-wrapper").classList.remove("overlay");
e.preventDefault();
return false;
}
if (e.target.closest("a")) {
// link to first post bottom
if (e.target.closest("a").classList.contains("scroll-to-bottom")) {
const rect = document
.querySelector(".d-toc-cooked")
.getBoundingClientRect();
if (rect) {
window.scrollTo({
top: rect.bottom + window.scrollY - headerOffset() - 10,
behavior: "smooth",
});
}
}
// close overlay
if (e.target.closest("a").classList.contains("d-toc-close")) {
document.querySelector(".d-toc-wrapper").classList.remove("overlay");
}
e.preventDefault();
return false;
}
if (!document.querySelector(".d-toc-wrapper.overlay")) {
return;
}
// clicking outside overlay
if (!e.target.closest(".d-toc-wrapper.overlay")) {
document.querySelector(".d-toc-wrapper").classList.remove("overlay");
}
},
};
function buildTOC(nodesList, elm, lv = 1) {
let nodes = Array.from(nodesList);
node = nodes.shift();
let node;
if (node) {
let li, cnt;
let curLv = parseInt(node.tagName.substring(1), 10);
if (curLv === lv) {
// same level
cnt = 0;
} else if (curLv < lv) {
// walk up then append
cnt = 0;
do {
elm = elm.parentNode.parentNode;
cnt--;
} while (cnt > curLv - lv);
} else if (curLv > lv) {
// add children
cnt = 0;
do {
li = elm.lastChild;
if (li == null) {
elm = elm.appendChild(document.createElement("ul"));
} else {
elm = li.appendChild(document.createElement("ul"));
}
cnt++;
} while (cnt < curLv - lv);
}
if (curLv === 1 && elm.lastChild === null) {
elm = elm.appendChild(document.createElement("ul"));
}
// append list item
li = elm.appendChild(document.createElement("li"));
li.classList.add("d-toc-item");
li.innerHTML = `<a data-d-toc="${node.getAttribute("id")}">${
node.textContent
}</a>`;
// recurse
buildTOC(nodes, elm, lv + cnt);
}
}
function parentsUntil(el, selector, filter) {
const result = [];
const matchesSelector =
el.matches ||
el.webkitMatchesSelector ||
el.mozMatchesSelector ||
el.msMatchesSelector;
// match start from parent
el = el.parentElement;
while (el && !matchesSelector.call(el, selector)) {
if (!filter) {
result.push(el);
} else {
if (matchesSelector.call(el, filter)) {
result.push(el);
}
}
el = el.parentElement;
}
return result;
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "DiscoTOC",
"version": "2.0.0",
"repository": "https://github.com/discourse/DiscoTOC",
"author": "Discourse",
"license": "MIT",
"devDependencies": {
"eslint-config-discourse": "^1.1.9"
}
}
-3
View File
@@ -1,3 +0,0 @@
.d-toc-timeline .topic-chat-float-container {
z-index: z("header");
}
-105
View File
@@ -1,105 +0,0 @@
.discourse-sidebar .d-toc-regular .d-toc-article .row {
width: 75%;
@media screen and (max-width: calc($large-width + 100px)) {
width: calc(100vw - 240px - 3em);
}
}
@media screen and (max-width: calc($large-width + 100px)) {
.discourse-sidebar {
.d-toc-regular {
.post-bottom-wrapper {
padding: 1em 0.75em;
&.desktop {
display: none;
}
}
#d-toc {
z-index: z("header") + 1;
background: var(--secondary);
position: fixed;
right: 0;
top: 0;
height: 100vh;
width: 100vw;
max-width: 500px;
overflow: scroll;
transition: transform 0.5s, opacity 0.25s;
transform: translatex(100%);
opacity: 0;
:not(.rtl) & {
margin-left: -1px;
}
.rtl & {
margin-right: -1px;
}
&.d-toc-mobile {
transform: translatex(0);
opacity: 1;
}
.d-toc-active {
&:before {
:not(.rtl) & {
margin-left: -1px;
}
.rtl & {
margin-right: -1px;
}
}
}
}
.d-toc-close-wrapper {
height: 3em;
background: var(--secondary);
color: var(--primary-med-or-secondary-med);
margin-bottom: 1em;
position: -webkit-sticky;
position: sticky;
top: 0;
display: flex;
align-items: center;
justify-content: flex-end;
.d-toc-close {
padding: 1em 0.75em;
}
}
.d-toc-toggle {
position: fixed;
bottom: 5px;
padding: 0.5em 1em;
background: var(--tertiary);
color: var(--secondary);
z-index: 3;
margin-bottom: env(safe-area-inset-bottom);
:not(.rtl) & {
right: 16px;
}
.rtl & {
left: 16px;
}
body.footer-nav-visible & {
bottom: 49px;
}
}
#d-toc > ul {
:not(.rtl) & {
margin-left: 20px;
border-left: 1px solid var(--primary-low);
}
.rtl & {
margin-right: 20px;
border-right: 1px solid var(--primary-low);
}
&:last-child {
margin-bottom: 5em;
}
}
}
}
}
.mobile-view .discourse-sidebar .d-toc-regular .d-toc-article .row {
width: 100%;
}
+27
View File
@@ -0,0 +1,27 @@
import { acceptance, query } from "discourse/tests/helpers/qunit-helpers";
import { click, visit } from "@ember/test-helpers";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import { test } from "qunit";
acceptance("DiscoTOC - Composer", function (needs) {
needs.user();
test("Can use TOC when creating a topic", async function (assert) {
await visit("/");
await click("#create-topic");
const toolbarPopupMenu = selectKit(".toolbar-popup-menu-options");
await toolbarPopupMenu.expand();
await toolbarPopupMenu.selectRowByValue("insertDtoc");
assert.ok(query(".d-editor-input").value.includes('data-theme-toc="true"'));
});
test("no TOC option when replying", async function (assert) {
await visit("/t/internationalization-localization/280");
await click(".create.reply");
const toolbarPopupMenu = selectKit(".toolbar-popup-menu-options");
await toolbarPopupMenu.expand();
assert.notOk(toolbarPopupMenu.rowByValue("insertDtoc").exists());
});
});
File diff suppressed because one or more lines are too long
+1635
View File
File diff suppressed because it is too large Load Diff