diff --git a/javascripts/discourse/components/card/topic-author-avatar-column.gjs b/javascripts/discourse/components/card/topic-author-avatar-column.gjs
new file mode 100644
index 0000000..561e2d4
--- /dev/null
+++ b/javascripts/discourse/components/card/topic-author-avatar-column.gjs
@@ -0,0 +1,14 @@
+import Component from "@glimmer/component";
+import avatar from "discourse/helpers/avatar";
+
+export default class TopicAuthorColumn extends Component {
+ constructor() {
+ super(...arguments);
+ }
+
+
+
+ {{avatar @topic.creator imageSize="large"}}
+
+
+}
diff --git a/javascripts/discourse/components/card/topic-author-column.gjs b/javascripts/discourse/components/card/topic-author-column.gjs
new file mode 100644
index 0000000..8899749
--- /dev/null
+++ b/javascripts/discourse/components/card/topic-author-column.gjs
@@ -0,0 +1,11 @@
+import Component from "@glimmer/component";
+
+export default class TopicAuthorColumn extends Component {
+ constructor() {
+ super(...arguments);
+ }
+
+
+ @{{@topic.creator.username}}
+
+}
diff --git a/javascripts/discourse/components/card/topic-category-column.gjs b/javascripts/discourse/components/card/topic-category-column.gjs
new file mode 100644
index 0000000..1e40247
--- /dev/null
+++ b/javascripts/discourse/components/card/topic-category-column.gjs
@@ -0,0 +1,12 @@
+import Component from "@glimmer/component";
+import { categoryLinkHTML } from "discourse/helpers/category-link";
+
+export default class TopicAuthorColumn extends Component {
+ constructor() {
+ super(...arguments);
+ }
+
+
+ {{categoryLinkHTML @topic.category}}
+
+}
diff --git a/javascripts/discourse/components/card/topic-likes-column.gjs b/javascripts/discourse/components/card/topic-likes-column.gjs
new file mode 100644
index 0000000..60f582f
--- /dev/null
+++ b/javascripts/discourse/components/card/topic-likes-column.gjs
@@ -0,0 +1,14 @@
+import Component from "@glimmer/component";
+import icon from "discourse/helpers/d-icon";
+
+export default class TopicLikesColumn extends Component {
+ constructor() {
+ super(...arguments);
+ }
+
+
+ {{#if @topic.like_count}}
+ {{icon "heart"}}{{@topic.like_count}}
+ {{/if}}
+
+}
diff --git a/javascripts/discourse/components/card/topic-replies-column.gjs b/javascripts/discourse/components/card/topic-replies-column.gjs
new file mode 100644
index 0000000..d67a6ef
--- /dev/null
+++ b/javascripts/discourse/components/card/topic-replies-column.gjs
@@ -0,0 +1,14 @@
+import Component from "@glimmer/component";
+import icon from "discourse/helpers/d-icon";
+
+export default class TopicRepliesColumn extends Component {
+ constructor() {
+ super(...arguments);
+ }
+
+
+ {{#if @topic.posts_count}}
+ {{icon "reply"}}{{@topic.posts_count}}
+ {{/if}}
+
+}
diff --git a/javascripts/discourse/components/card/topic-status-column.gjs b/javascripts/discourse/components/card/topic-status-column.gjs
new file mode 100644
index 0000000..7a2d926
--- /dev/null
+++ b/javascripts/discourse/components/card/topic-status-column.gjs
@@ -0,0 +1,96 @@
+import Component from "@glimmer/component";
+import { on } from "@ember/modifier";
+import { action } from "@ember/object";
+import { service } from "@ember/service";
+import { and } from "truth-helpers";
+import icon from "discourse/helpers/d-icon";
+
+export default class TopicStatusColumn extends Component {
+ @service currentUser;
+ @service siteSettings;
+
+ get canAct() {
+ return this.currentUser && !this.args.disableActions;
+ }
+
+ get statusClass() {
+ let classes = ["topic-status-card"];
+ if (this.args.topic.bookmarked) {
+ classes.push("--bookmark");
+ } else if (this.args.topic.closed && this.args.topic.archived) {
+ classes.push("--locked --archived");
+ } else if (this.args.topic.closed) {
+ classes.push("--locked");
+ } else if (this.args.topic.archived) {
+ classes.push("--archived");
+ } else if (this.args.topic.is_warning) {
+ classes.push("--warning");
+ } else if (
+ this.args.showPrivateMessageIcon &&
+ this.args.topic.isPrivateMessage
+ ) {
+ classes.push("--private-message");
+ } else if (this.args.topic.pinned) {
+ classes.push("--pinned");
+ } else if (this.args.topic.unpinned) {
+ classes.push("--unpinned");
+ }
+ return classes.join(" ");
+ }
+
+ get heatMap() {
+ return this.args.topic.views > this.siteSettings.topic_views_heat_medium;
+ }
+
+ @action
+ togglePinned(e) {
+ e.preventDefault();
+ this.args.topic.togglePinnedForUser();
+ }
+
+
+ {{#if @topic.bookmarked}}
+ {{icon "bookmark"}}Bookmarked
+ {{/if}}
+ {{#if (and @topic.closed @topic.archived)~}}
+ Locked and Archived
+ {{else if @topic.closed}}
+ Locked
+ {{else if @topic.archived}}
+ Archived
+ {{/if}}
+ {{#if @topic.is_warning}}
+ Warning
+ {{else if (and @showPrivateMessageIcon @topic.isPrivateMessage)}}
+ Private Message
+ {{/if}}
+ {{#if @topic.pinned}}
+ {{#if this.canAct}}
+
+ {{else}}
+ {{icon "thumbtack"}}Pinned
+ {{/if}}
+ {{else if @topic.unpinned}}
+ {{#if this.canAct}}
+
+ {{else}}
+ {{icon
+ "thumbtack"
+ class="unpinned"
+ }}Unpinned
+ {{/if}}
+ {{/if}}
+
+ {{#if this.heatMap}}
+ {{icon "fa-fire"}}Hot
+ {{/if}}
+
+}
diff --git a/javascripts/discourse/initializers/topic-list-columns.gjs b/javascripts/discourse/initializers/topic-list-columns.gjs
new file mode 100644
index 0000000..0fa2e50
--- /dev/null
+++ b/javascripts/discourse/initializers/topic-list-columns.gjs
@@ -0,0 +1,81 @@
+import { withPluginApi } from "discourse/lib/plugin-api";
+import TopicAuthorAvatarColumn from "../components/card/topic-author-avatar-column";
+import TopicAuthorColumn from "../components/card/topic-author-column";
+import TopicCategoryColumn from "../components/card/topic-category-column";
+import TopicLikesColumn from "../components/card/topic-likes-column";
+import TopicRepliesColumn from "../components/card/topic-replies-column";
+import TopicStatusColumn from "../components/card/topic-status-column";
+
+const TopicAuthor =
+ |
+
+ |
+;
+
+const TopicAuthorAvatar =
+
+
+ |
+;
+
+const TopicCategoryStatus =
+
+
+
+ |
+;
+
+const TopicLikesReplies =
+
+
+
+ |
+;
+
+export default {
+ name: "topic-list-customizations",
+
+ initialize() {
+ withPluginApi("1.39.0", (api) => {
+ api.registerValueTransformer(
+ "topic-list-columns",
+ ({ value: columns }) => {
+ columns.add("topic-author", {
+ item: TopicAuthor,
+ after: "activity",
+ });
+ columns.add("topic-category-status", {
+ item: TopicCategoryStatus,
+ after: "topic-author",
+ });
+ columns.add("topic-author-avatar", {
+ item: TopicAuthorAvatar,
+ after: "topic-category-status",
+ });
+ columns.add("topic-likes-replies", {
+ item: TopicLikesReplies,
+ after: "topic-author-avatar",
+ });
+ columns.delete("posters");
+ columns.delete("views");
+ columns.delete("replies");
+ return columns;
+ }
+ );
+
+ api.registerValueTransformer(
+ "topic-list-item-class",
+ ({ value: classes, context }) => {
+ if (context.topic.pinned || context.topic.pinned_globally) {
+ classes.push("--pinned");
+ }
+ return classes;
+ }
+ );
+
+ api.registerValueTransformer("topic-list-item-mobile-layout", () => {
+ return false;
+ });
+ });
+ },
+};
diff --git a/scss/topic-cards.scss b/scss/topic-cards.scss
index 0804dff..345c60f 100644
--- a/scss/topic-cards.scss
+++ b/scss/topic-cards.scss
@@ -1,6 +1,331 @@
+// 390x844 – mobile/portrait – (Figma iPhone 13 & 14)
+// 744x1133 – tablet/portrait – (Figma iPad mini 8.3)
+// 1280x832 – desktop small – (Figma MacBook Air)
+
+:root {
+ --hot-color: oklch(63.79% 0.1823 34.77);
+}
+
+$extra-small: 435px;
+$small: 576px;
+$medium: 980px;
+$extra-large: 1280px;
+
+.topic-list .topic-list-item-separator {
+ display: none;
+}
+
+.topic-list > .topic-list-body > .topic-list-item.last-visit {
+ border-bottom: 1px solid var(--primary-300);
+}
+
+.topic-list-body {
+ border: none;
+ display: flex;
+ flex-direction: column;
+ gap: 1em;
+ @media screen and (max-width: $extra-small) {
+ gap: 0.5em;
+ }
+}
+
+body.user-messages-page .topic-list-item {
+ .topic-category-status-data {
+ display: none;
+ }
+ grid-template-areas:
+ "avatar author status status . . activity"
+ ". topic-title topic-title topic-title likes-replies likes-replies likes-replies";
+ &.excerpt-expanded {
+ grid-template-columns: 44px repeat(6, 1fr) auto;
+ grid-template-rows: 22px auto auto 30px;
+ grid-template-areas:
+ "avatar author status status . . . activity"
+ "avatar topic-title topic-title topic-title topic-title . . ."
+ ". excerpt excerpt excerpt excerpt excerpt . ."
+ ". excerpt excerpt excerpt excerpt excerpt likes-replies likes-replies";
+ @media screen and (max-width: $extra-large) {
+ grid-template-areas:
+ "avatar author status status . . . activity"
+ "avatar topic-title topic-title topic-title topic-title . . ."
+ ". excerpt excerpt excerpt excerpt excerpt . likes-replies"
+ ". excerpt excerpt excerpt excerpt excerpt . likes-replies";
+ }
+ }
+ @media screen and (max-width: $small) {
+ grid-template-columns: 25px auto repeat(6, 1fr);
+ grid-template-rows: auto auto;
+ grid-template-areas:
+ "topic-title topic-title topic-title topic-title topic-title topic-title topic-title activity"
+ "avatar author . . . . . likes-replies";
+ .topic-excerpt {
+ display: none;
+ }
+ }
+}
+
.topic-list-item {
+ -webkit-font-smoothing: antialiased;
+ text-overflow: ellipsis;
+ padding: 0.75em 1rem;
+ border: 1px solid var(--primary-300);
+ display: grid;
+ grid-template-columns: 44px min-content min-content auto min-content min-content min-content;
+ grid-template-rows: 22px minmax(22px, auto);
+ grid-template-areas:
+ "avatar author status status . . activity"
+ ". topic-title topic-title topic-title likes-replies likes-replies category";
+ &.excerpt-expanded {
+ grid-template-columns: 44px repeat(6, 1fr) auto;
+ grid-template-rows: 22px auto auto 30px;
+ grid-template-areas:
+ "avatar author status status . . . activity"
+ "avatar topic-title topic-title topic-title topic-title . . ."
+ ". excerpt excerpt excerpt excerpt excerpt . ."
+ ". excerpt excerpt excerpt excerpt excerpt likes-replies category";
+ @media screen and (max-width: $extra-large) {
+ grid-template-areas:
+ "avatar author status status . . . activity"
+ "avatar topic-title topic-title topic-title topic-title . . ."
+ ". excerpt excerpt excerpt excerpt excerpt . likes-replies"
+ ". excerpt excerpt excerpt excerpt excerpt . category";
+ }
+ @media screen and (max-width: $small) {
+ grid-template-columns: 25px auto repeat(6, 1fr);
+ grid-template-rows: auto auto auto;
+ grid-template-areas:
+ "category-status category-status category-status . . . . activity"
+ "topic-title topic-title topic-title topic-title topic-title topic-title topic-title topic-title"
+ "avatar author . . . . . likes-replies";
+ .topic-excerpt {
+ display: none;
+ }
+ }
+ }
+ grid-column-gap: 12px;
+ grid-row-gap: 8px;
+ border-radius: var(--d-border-radius);
+ @media screen and (max-width: $medium) {
+ grid-template-columns: 44px min-content min-content auto min-content min-content min-content;
+ grid-template-rows: 22px minmax(22px, auto);
+ grid-template-areas:
+ "avatar author status status . . activity"
+ ". topic-title topic-title topic-title . . likes-replies"
+ ". topic-title topic-title topic-title . . category";
+ }
+ @media screen and (max-width: $small) {
+ grid-template-columns: 25px auto repeat(6, 1fr);
+ grid-template-rows: auto auto auto;
+ grid-template-areas:
+ "category-status category-status category-status . . . . activity"
+ "topic-title topic-title topic-title topic-title topic-title topic-title topic-title topic-title"
+ "avatar author . . . . . likes-replies";
+ }
+ @media screen and (max-width: $extra-small) {
+ border: none;
+ border-bottom: 1px solid var(--primary-200);
+ }
+
background: var(--d-content-background);
&:hover {
border: 1px solid var(--accent-color);
}
+
+ // display contents
+ td.main-link,
+ td.posters,
+ td.posts,
+ td.views,
+ td.activity {
+ display: contents;
+ }
+
+ td.num.posts a {
+ padding: 0;
+ }
+
+ // avatar & author
+ .topic-author-avatar-data {
+ grid-area: avatar;
+ margin: 0;
+ }
+ .topic-author-avatar img.avatar {
+ width: 44px;
+ height: 44px;
+ border-radius: var(--d-border-radius);
+ @media screen and (max-width: $small) {
+ width: 25px;
+ height: 25px;
+ }
+ }
+ td.topic-author-data {
+ grid-area: author;
+ display: flex;
+ gap: 0.5em;
+ align-items: center;
+ }
+ .topic-author-data .topic-author {
+ color: var(--primary-500);
+ }
+
+ // status
+ .topic-status-card {
+ display: flex;
+ flex-direction: row;
+ gap: 4px;
+ align-items: center;
+ padding: 0 6px;
+ font-size: var(--font-down-2);
+ font-weight: 600;
+ border-radius: var(--d-border-radius);
+ border: 1px solid var(--status-color);
+ color: var(--status-color);
+ height: min-content;
+ grid-area: status;
+ width: min-content;
+ @media screen and (max-width: $small) {
+ height: calc(100% - 2px);
+ }
+ svg {
+ font-size: var(--font-down-1);
+ color: var(--status-color);
+ }
+ }
+
+ .topic-status-card.--bookmark {
+ display: none;
+ }
+ .topic-status-card.--pinned,
+ .topic-status-card.--unpinned {
+ --status-color: var(--primary-500);
+ cursor: pointer;
+ background-color: transparent;
+ line-height: unset;
+ }
+ .topic-status-card.--hot {
+ --status-color: var(--hot-color);
+ }
+
+ // title
+ td.main-link .link-top-line {
+ font-size: var(--font-0);
+ grid-area: topic-title;
+ font-weight: 500;
+ }
+
+ .link-top-line .event-date {
+ font-size: var(--font-down-3);
+ }
+
+ td.main-link a.topic-status {
+ display: none;
+ }
+
+ td.main-link .link-top-line a.raw-topic-link {
+ padding: 0;
+ }
+
+ .topic-post-badges .badge-notification.unread-posts {
+ background-color: var(--tertiary);
+ color: var(--tertiary);
+ overflow: hidden;
+ height: 8px;
+ width: 8px;
+ padding: 0;
+ top: -2px;
+ min-width: unset;
+ }
+
+ // excerpt
+ .topic-excerpt {
+ grid-area: excerpt;
+ margin: 0;
+ font-size: var(--font-down-2);
+ }
+
+ // timestamp
+ td.activity .post-activity {
+ grid-area: activity;
+ font-size: var(--font-down-1);
+ color: var(--primary-500);
+ margin-left: auto;
+ padding: 0;
+ }
+
+ // metadata
+ // metadata - category
+ td.main-link .link-bottom-line {
+ display: none;
+ }
+
+ td.topic-category-status-data {
+ display: contents;
+ @media screen and (max-width: $small) {
+ grid-area: category-status;
+ display: flex;
+ gap: 0.5em;
+ align-items: center;
+ }
+ }
+ td.topic-category-status-data .badge-category__wrapper {
+ grid-area: category;
+ }
+
+ td.topic-category-status-data .badge-category__wrapper {
+ overflow: unset;
+ border-radius: var(--d-border-radius);
+ padding: 6px;
+ align-self: flex-end;
+ background-color: light-dark(
+ oklch(from var(--category-badge-color) 97% calc(c * 0.3) h),
+ oklch(from var(--category-badge-color) 45% calc(c * 0.5) h)
+ );
+
+ @media screen and (max-width: $small) {
+ padding: 2px 6px;
+ }
+
+ .badge-category__name {
+ color: light-dark(
+ oklch(from var(--category-badge-color) 20% calc(c * 1) h),
+ oklch(from var(--category-badge-color) 100% calc(c * 0.9) h)
+ );
+ }
+ }
+ td.main-link .discourse-tags {
+ display: none;
+ }
+
+ // metadata - likes and replies
+ td.posts .badge-posts {
+ grid-area: replies;
+ align-self: center;
+ font-weight: normal;
+ }
+
+ td.topic-likes-replies-data {
+ grid-area: likes-replies;
+ display: flex;
+ flex-direction: row;
+ gap: 0.5em;
+ justify-content: flex-end;
+ height: min-content;
+ align-self: flex-end;
+ padding-bottom: 4px;
+ }
+ .topic-likes-replies-data .topic-likes,
+ .topic-likes-replies-data .topic-replies {
+ display: flex;
+ flex-direction: row;
+ gap: 0.5em;
+ align-items: center;
+ color: var(--primary-500);
+ svg {
+ color: var(--primary-600);
+ }
+ }
+}
+
+.topic-list-header {
+ display: none;
}