Skip to content

Commit

Permalink
Dev/header and sticky sidebar (#18)
Browse files Browse the repository at this point in the history
* DEV: Add routeType for content header

* DEV: Make conent header dynamic

* DEV: Add topic filter to content header

* DEV: Category badge theme settings

* UX: Update styling for content header

* FIX: Remove commented out lines

* DEV: Add back button to topic content header

* UX: Capitalize categories route

* DEV: Add more filter types

* DEV: Adjust filter to work with site settings

* DEV: Add sticky sidebar

* DEV: Use localized filter titles

* DEV: Sticky sidebar
  • Loading branch information
sketchius authored Jun 20, 2024
1 parent 3fcb06b commit 818474e
Show file tree
Hide file tree
Showing 10 changed files with 360 additions and 37 deletions.
17 changes: 17 additions & 0 deletions javascripts/discourse/components/sticky-sidebar.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="sticky-sidebar">
<div
{{did-insert this.didInsert}}
{{scroll this.onScroll}}
style={{concat

Check failure on line 5 in javascripts/discourse/components/sticky-sidebar.hbs

View workflow job for this annotation

GitHub Actions / ci / linting

Concatenated styles must be marked as `htmlSafe`.
"position: "
this.position
"; top: "
this.top
"px; bottom: "
this.bottom
"px"
}}
>
{{yield}}
</div>
</div>
78 changes: 78 additions & 0 deletions javascripts/discourse/components/sticky-sidebar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";

export default class StickySidebar extends Component {
@tracked top = 0;
@tracked bottom = 0;
@tracked position = "relative";

offset = 0;
prevScrollTop = 0;
yOrigin = 0;
mode = "unset";

@action
onScroll() {
const scrollY = window.scrollY;
const scrollingUp = scrollY < this.prevScrollTop;
const scrollingDown = scrollY > this.prevScrollTop;
const element = this.element;
if (!this.yOrigin) {
// save the initial vertical position
this.yOrigin = getYOrigin(this.element);
}
const stickyTop = this.yOrigin;

if (scrollingUp) {
if (isTopInView(element, this.yOrigin)) {
this.mode = "top";
this.position = "fixed";
this.top = stickyTop;
this.bottom = "unset";
}
if (this.mode === "bottom") {
this.mode = "between";
const top = element.getBoundingClientRect().top;
this.position = "relative";
this.top = top + scrollY - this.yOrigin;
}
} else if (scrollingDown) {
if (isBottomInView(element, this.yOrigin)) {
this.mode = "bottom";
this.position = "fixed";
this.bottom = 0;
this.top = "unset";
}
if (this.mode === "top") {
this.mode = "between";
const top = element.getBoundingClientRect().top;
this.position = "relative";
this.top = top + scrollY - this.yOrigin;
}
}
this.prevScrollTop = scrollY;
}

@action
didInsert(element) {
this.element = element;
this.offset = this.element.offsetTop;
}
}

function isTopInView(element, yOffset) {
const rect = element.getBoundingClientRect();
return rect.top >= yOffset && rect.top <= window.innerHeight;
}

function isBottomInView(element) {
const rect = element.getBoundingClientRect();
return rect.bottom >= 0 && rect.bottom <= window.innerHeight;
}

function getYOrigin(el) {
const rect = el.getBoundingClientRect();
const scrollTop = window.scrollY || window.pageYOffset;
return rect.top + scrollTop;
}
39 changes: 21 additions & 18 deletions javascripts/discourse/connectors/before-main-outlet/blocks.gjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import BlockProfile from "../../components/blocks/profile";
import BlockTime from "../../components/blocks/time";
import BlockTopContributors from "../../components/blocks/top-contributors";
import BlockTopTopics from "../../components/blocks/top-topics";
import StickySidebarComponent from "../../components/sticky-sidebar";

export default class BlocksComponent extends Component {
@service currentUser;
Expand Down Expand Up @@ -39,24 +40,26 @@ export default class BlocksComponent extends Component {

<template>
{{!log this.blocks}}
<div class="blocks">
<div class="blocks__wrapper">
{{#each this.blocks as |row|}}
<div class="blocks__row">
{{#each row.blocks as |block|}}
{{#let (this.blockify block) as |BlockComponent|}}
{{#if BlockComponent}}
{{component
BlockComponent
size=block.size
period=block.period
}}
{{/if}}
{{/let}}
{{/each}}
</div>
{{/each}}
<StickySidebarComponent>
<div class="blocks">
<div class="blocks__wrapper">
{{#each this.blocks as |row|}}
<div class="blocks__row">
{{#each row.blocks as |block|}}
{{#let (this.blockify block) as |BlockComponent|}}
{{#if BlockComponent}}
{{component
BlockComponent
size=block.size
period=block.period
}}
{{/if}}
{{/let}}
{{/each}}
</div>
{{/each}}
</div>
</div>
</div>
</StickySidebarComponent>
</template>
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,186 @@
import Component from "@glimmer/component";
// import { tracked } from "@glimmer/tracking";
// import { concat, get } from "@ember/helper";
// import { action } from "@ember/object";
import { tracked } from "@glimmer/tracking";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { service } from "@ember/service";
// import { htmlSafe } from "@ember/template";
// import { eq, or } from "truth-helpers";
// import avatar from "discourse/helpers/avatar";
// import categoryLink from "discourse/helpers/category-link";
// import concatClass from "discourse/helpers/concat-class";
// import number from "discourse/helpers/number";
// import replaceEmoji from "discourse/helpers/replace-emoji";
// import { ajax } from "discourse/lib/ajax";
// import Category from "discourse/models/category";
import { capitalize } from "@ember/string";
import { eq } from "truth-helpers";
import i18n from "discourse-common/helpers/i18n";

export default class Breadcrumbs extends Component {
@service router;
@service site;

@tracked routeType;

@action
updateRouteType() {
if (this.router?.currentRoute?.parent?.name === "discovery") {
switch (this.router?.currentRoute?.localName) {
case "latest":
case "hot":
case "top":
case "new":
case "unread":
this.routeType = "home";
break;
case "category":
case "latestCategory":
case "hotCategory":
case "topCategory":
case "newCategory":
case "unreadCategory":
this.routeType = "category";
break;
case "categories":
this.routeType = "categories";
break;
default:
this.routeType = null;
break;
}
} else {
this.routeType = null;
}
}

get filterType() {
if (this.router?.currentRoute?.localName === "categories") {
return "categories";
}
return this.router?.currentRoute?.attributes?.filterType || "";
}

get isHomepage() {
return this.router.currentRouteName === "discovery.latest";
this.updateRouteType();
return this.routeType === "home";
}

get isCategoryView() {
this.updateRouteType();
return this.routeType === "category";
}

get isCategoryList() {
this.updateRouteType();
return this.routeType === "categories";
}

get categoryName() {
return this.router?.currentRoute?.attributes?.category?.name || "Category";
}

get categoryBadge() {
const defaultBadge = settings.default_category_badge || "📁";

const badge = settings.category_icons?.find(
(category) =>
category.id[0] === this.router?.currentRoute?.attributes?.category?.id
)?.emoji;

if (!badge) {
return defaultBadge;
}

try {
// check for valid emoji
const regex = /\p{Emoji}/u;
if (regex.test(badge)) {
return badge;
}
return defaultBadge;
} catch (e) {
// \p{Emoji} not supported -> skip validation
return badge;
}
}

@action
home() {
this.router.transitionTo("/");
}

<template>
<div class="breadcrumbs">
{{#if this.isHomepage}}
<h2 data-name="home" class="breadcrumbs__title">
<h2 class="breadcrumbs__title">
<div data-badge-type="icon" class="badge">
home
</div>
{{i18n "js.home"}}
</h2>
{{else if this.isCategoryView}}
<h2 class="breadcrumbs__title">
<div
data-badge-type="emoji"
data-clickable="true"
class="badge"
{{on "click" this.home}}

Check failure on line 118 in javascripts/discourse/connectors/discovery-navigation-bar-above/navigation.gjs

View workflow job for this annotation

GitHub Actions / ci / linting

Interaction added to non-interactive element
>
{{this.categoryBadge}}
</div>
{{this.categoryName}}
</h2>
{{else if this.isCategoryList}}
<h2 class="breadcrumbs__title">
<div
data-badge-type="emoji"
data-clickable="true"
class="badge"
{{on "click" this.home}}

Check failure on line 130 in javascripts/discourse/connectors/discovery-navigation-bar-above/navigation.gjs

View workflow job for this annotation

GitHub Actions / ci / linting

Interaction added to non-interactive element
>
🗃️
</div>
{{capitalize (i18n "js.categories.categories_label")}}
</h2>
{{/if}}
<TopicFilter
@filterType={{this.filterType}}
@routeType={{this.routeType}}
/>
</div>
</template>
}
class TopicFilter extends Component {
@service router;
@service site;

@tracked filterOptions;

constructor() {
super(...arguments);

this.filterOptions =
this.site.siteSettings?.top_menu?.split("|").map((item) => {
return { name: item, localization: `js.filters.${item}.title` };
}) || [];
}

@action
filterTopics(event) {
const routeType = this.args.routeType;
const category = this.router?.currentRoute?.attributes?.category;

if (routeType === "category" && event.target.value !== "categories") {
this.router.transitionTo(
`/c/${category.slug}/${category.id}/l/${event.target.value}`
);
} else {
this.router.transitionTo(`/${event.target.value}`);
}
}

<template>
<select
class="breadcrumbs__select"
value={{@filterType}}
onchange={{this.filterTopics}}
>
{{#each this.filterOptions as |filterOption|}}
<option value={{filterOption.name}} selected={{eq filterOption.name @filterType}}>
{{i18n filterOption.localization}}
</option>
{{/each}}
</select>
</template>
}
4 changes: 4 additions & 0 deletions javascripts/discourse/connectors/topic-title/back.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{{#unless this.model.isPrivateMessage}}
<a href={{this.model.category.url}} role="button" class="topic-back-button">

Check failure on line 2 in javascripts/discourse/connectors/topic-title/back.hbs

View workflow job for this annotation

GitHub Actions / ci / linting

Links should have descriptive text
</a>
{{/unless}}
11 changes: 11 additions & 0 deletions javascripts/discourse/modifiers/scroll.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { modifier } from 'ember-modifier';

export default modifier((element, [callback]) => {
const handleScroll = () => callback();

window.addEventListener('scroll', handleScroll);

return () => {
window.removeEventListener('scroll', handleScroll);
};
});
Loading

0 comments on commit 818474e

Please sign in to comment.