This commit is contained in:
Joffrey JAFFEUX 2025-01-13 13:12:12 +01:00
parent 4fef35cd3f
commit b985dc8d25
19 changed files with 353 additions and 229 deletions

View File

@ -11,7 +11,7 @@ module DiscourseRewind
data: {
reading_time: reading_time,
book: best_book_fit(reading_time)[:title],
isbn: best_book_fit(reading_time)[:isbn]
isbn: best_book_fit(reading_time)[:isbn],
},
identifier: "reading-time",
}
@ -19,26 +19,86 @@ module DiscourseRewind
def popular_books
{
"The Hunger Games" => { reading_time: 19_740, isbn: "978-0439023481" },
"The Metamorphosis" => { reading_time: 3120, isbn: "978-0553213690" },
"To Kill a Mockingbird" => { reading_time: 22_800, isbn: "978-0061120084" },
"Pride and Prejudice" => { reading_time: 25_200, isbn: "978-1503290563" },
"1984" => { reading_time: 16_800, isbn: "978-0451524935" },
"The Lord of the Rings" => { reading_time: 108_000, isbn: "978-0544003415" },
"Harry Potter and the Sorcerer's Stone" => { reading_time: 24_600, isbn: "978-0590353427" },
"The Great Gatsby" => { reading_time: 12_600, isbn: "978-0743273565" },
"The Little Prince" => { reading_time: 5400, isbn: "978-0156012195" },
"Animal Farm" => { reading_time: 7200, isbn: "978-0451526342" },
"The Catcher in the Rye" => { reading_time: 18_000, isbn: "978-0316769488" },
"Jane Eyre" => { reading_time: 34_200, isbn: "978-0141441146" },
"Fahrenheit 451" => { reading_time: 15_000, isbn: "978-1451673319" },
"The Hobbit" => { reading_time: 27_000, isbn: "978-0547928227" },
"The Da Vinci Code" => { reading_time: 37_800, isbn: "978-0307474278" },
"Little Women" => { reading_time: 30_000, isbn: "978-0147514011" },
"One Hundred Years of Solitude" => { reading_time: 46_800, isbn: "978-0060883287" },
"And Then There Were None" => { reading_time: 16_200, isbn: "978-0062073488" },
"The Alchemist" => { reading_time: 10_800, isbn: "978-0061122415" },
"The Hitchhiker's Guide to the Galaxy" => { reading_time: 12_600, isbn: "978-0345391803" },
"The Hunger Games" => {
reading_time: 19_740,
isbn: "978-0439023481",
},
"The Metamorphosis" => {
reading_time: 3120,
isbn: "978-0553213690",
},
"To Kill a Mockingbird" => {
reading_time: 22_800,
isbn: "978-0061120084",
},
"Pride and Prejudice" => {
reading_time: 25_200,
isbn: "978-1503290563",
},
"1984" => {
reading_time: 16_800,
isbn: "978-0451524935",
},
"The Lord of the Rings" => {
reading_time: 108_000,
isbn: "978-0544003415",
},
"Harry Potter and the Sorcerer's Stone" => {
reading_time: 24_600,
isbn: "978-0590353427",
},
"The Great Gatsby" => {
reading_time: 12_600,
isbn: "978-0743273565",
},
"The Little Prince" => {
reading_time: 5400,
isbn: "978-0156012195",
},
"Animal Farm" => {
reading_time: 7200,
isbn: "978-0451526342",
},
"The Catcher in the Rye" => {
reading_time: 18_000,
isbn: "978-0316769488",
},
"Jane Eyre" => {
reading_time: 34_200,
isbn: "978-0141441146",
},
"Fahrenheit 451" => {
reading_time: 15_000,
isbn: "978-1451673319",
},
"The Hobbit" => {
reading_time: 27_000,
isbn: "978-0547928227",
},
"The Da Vinci Code" => {
reading_time: 37_800,
isbn: "978-0307474278",
},
"Little Women" => {
reading_time: 30_000,
isbn: "978-0147514011",
},
"One Hundred Years of Solitude" => {
reading_time: 46_800,
isbn: "978-0060883287",
},
"And Then There Were None" => {
reading_time: 16_200,
isbn: "978-0062073488",
},
"The Alchemist" => {
reading_time: 10_800,
isbn: "978-0061122415",
},
"The Hitchhiker's Guide to the Galaxy" => {
reading_time: 12_600,
isbn: "978-0345391803",
},
}.symbolize_keys
end
@ -54,16 +114,10 @@ module DiscourseRewind
reading_time_rest -= best_fit.last[:reading_time]
end
book_title = books.group_by { |book| book }
.transform_values(&:count)
.max_by { |_, count| count }
.first
# popular_books[book_title]
book_title =
books.group_by { |book| book }.transform_values(&:count).max_by { |_, count| count }.first
{
title: book_title,
isbn: popular_books[book_title][:isbn],
}
{ title: book_title, isbn: popular_books[book_title][:isbn] }
end
end
end

View File

@ -1,8 +1,7 @@
# frozen_string_literal: true
# User Word Cloud
module DiscourseRewind
class Rewind::Action::WordCloud < Rewind::Action::BaseReport
class Rewind::Action::TopWords < Rewind::Action::BaseReport
FakeData = {
data: [
{ word: "what", score: 100 },
@ -12,7 +11,7 @@ module DiscourseRewind
{ word: "this", score: 60 },
{ word: "week", score: 50 },
],
identifier: "word-cloud",
identifier: "top-words",
}
def call
@ -81,7 +80,7 @@ module DiscourseRewind
word_score = words.map { { word: _1.original_word, score: _1.ndoc + _1.nentry } }
{ data: word_score, identifier: "word-cloud" }
{ data: word_score, identifier: "top-words" }
end
end
end

View File

@ -1,6 +1,7 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import concatClass from "discourse/helpers/concat-class";
import { i18n } from "discourse-i18n";
const ROWS = 7;
const COLS = 53;
@ -22,6 +23,11 @@ export default class ActivityCalendar extends Component {
return rowsArray;
}
@action
getAbbreviatedMonth(monthIndex) {
return moment().month(monthIndex).format("MMM");
}
@action
computeClass(count) {
if (!count) {
@ -37,24 +43,62 @@ export default class ActivityCalendar extends Component {
<template>
<div class="rewind-report-page -activity-calendar">
<h2 class="rewind-report-title">Activity Calendar</h2>
<h2 class="rewind-report-title">{{i18n
"discourse_rewind.reports.activity_calendar.title"
}}</h2>
<div class="rewind-card">
<table class="rewind-calendar">
<thead>
<tr>
<td colspan="5" class="activity-header-cell">Jan</td>
<td colspan="4" class="activity-header-cell">Feb</td>
<td colspan="4" class="activity-header-cell">Mar</td>
<td colspan="5" class="activity-header-cell">Apr</td>
<td colspan="4" class="activity-header-cell">May</td>
<td colspan="4" class="activity-header-cell">Jun</td>
<td colspan="5" class="activity-header-cell">Jul</td>
<td colspan="4" class="activity-header-cell">Aug</td>
<td colspan="5" class="activity-header-cell">Sep</td>
<td colspan="4" class="activity-header-cell">Oct</td>
<td colspan="4" class="activity-header-cell">Nov</td>
<td colspan="4" class="activity-header-cell">Dec</td>
<td
colspan="5"
class="activity-header-cell"
>{{this.getAbbreviatedMonth 0}}</td>
<td
colspan="4"
class="activity-header-cell"
>{{this.getAbbreviatedMonth 1}}</td>
<td
colspan="4"
class="activity-header-cell"
>{{this.getAbbreviatedMonth 2}}</td>
<td
colspan="5"
class="activity-header-cell"
>{{this.getAbbreviatedMonth 3}}</td>
<td
colspan="4"
class="activity-header-cell"
>{{this.getAbbreviatedMonth 4}}</td>
<td
colspan="4"
class="activity-header-cell"
>{{this.getAbbreviatedMonth 5}}</td>
<td
colspan="5"
class="activity-header-cell"
>{{this.getAbbreviatedMonth 6}}</td>
<td
colspan="4"
class="activity-header-cell"
>{{this.getAbbreviatedMonth 7}}</td>
<td
colspan="5"
class="activity-header-cell"
>{{this.getAbbreviatedMonth 8}}</td>
<td
colspan="4"
class="activity-header-cell"
>{{this.getAbbreviatedMonth 9}}</td>
<td
colspan="4"
class="activity-header-cell"
>{{this.getAbbreviatedMonth 10}}</td>
<td
colspan="4"
class="activity-header-cell"
>{{this.getAbbreviatedMonth 11}}</td>
</tr>
</thead>
<tbody>

View File

@ -2,7 +2,8 @@ import Component from "@glimmer/component";
import { concat } from "@ember/helper";
import { get } from "@ember/object";
import { htmlSafe } from "@ember/template";
import dIcon from "discourse-common/helpers/d-icon";
import icon from "discourse-common/helpers/d-icon";
import { i18n } from "discourse-i18n";
export default class BestPosts extends Component {
rank(idx) {
@ -10,8 +11,12 @@ export default class BestPosts extends Component {
}
<template>
{{#if @report.data.length}}
<div class="rewind-report-page -best-posts">
<h2 class="rewind-report-title">Your 3 best posts</h2>
<h2 class="rewind-report-title">{{i18n
"discourse_rewind.reports.best_posts.title"
count=@report.data.length
}}</h2>
<div class="rewind-report-container">
{{#each @report.data as |post idx|}}
<div class={{concat "rewind-card" " rank-" (this.rank idx)}}>
@ -20,14 +25,17 @@ export default class BestPosts extends Component {
<div class="best-posts__post">{{htmlSafe (get post "5")}}</div>
<div class="best-posts__metadata">
<span class="best-posts__likes">
{{dIcon "heart"}}{{htmlSafe (get post "2")}}</span>
{{icon "heart"}}{{htmlSafe (get post "2")}}</span>
<span class="best-posts__replies">
{{dIcon "comment"}}{{htmlSafe (get post "3")}}</span>
<a href="/t/{{get post '1'}}/{{get post '0'}}">View post</a>
{{icon "comment"}}{{htmlSafe (get post "3")}}</span>
<a href="/t/{{get post '1'}}/{{get post '0'}}">{{i18n
"discourse_rewind.reports.best_posts.view_post"
}}</a>
</div>
</div>
{{/each}}
</div>
</div>
{{/if}}
</template>
}

View File

@ -1,45 +0,0 @@
import Component from "@glimmer/component";
import { concat } from "@ember/helper";
import { htmlSafe } from "@ember/template";
import emoji from "discourse/helpers/emoji";
export default class BestTopics extends Component {
rank(idx) {
return idx + 1;
}
emojiName(rank) {
if (rank + 1 === 1) {
return "1st_place_medal";
} else if (rank + 1 === 2) {
return "2nd_place_medal";
} else if (rank + 1 === 3) {
return "3rd_place_medal";
} else {
return "medal";
}
}
<template>
<div class="rewind-report-page -best-topics">
<h2 class="rewind-report-title">Your 3 best topics</h2>
<div class="rewind-report-container">
<div class="rewind-card">
{{#each @report.data as |topic idx|}}
<a
href={{concat "/t/-/" topic.topic_id}}
class={{concat "best-topics__topic" " rank-" (this.rank idx)}}
>
<span class="best-topics__rank">{{emoji
(this.emojiName idx)
}}</span><h2>{{topic.title}}</h2>
<span class="best-topics__excerpt">{{htmlSafe
topic.excerpt
}}</span>
</a>
{{/each}}
</div>
</div>
</div>
</template>
}

View File

@ -1,6 +1,7 @@
import Component from "@glimmer/component";
import { concat } from "@ember/helper";
import { htmlSafe } from "@ember/template";
import { i18n } from "discourse-i18n";
export default class BestTopics extends Component {
rank(idx) {
@ -8,8 +9,12 @@ export default class BestTopics extends Component {
}
<template>
{{#if @report.data.length}}
<div class="rewind-report-page -best-topics">
<h2 class="rewind-report-title">Your 3 best topics</h2>
<h2 class="rewind-report-title">{{i18n
"discourse_rewind.reports.best_topics.title"
count=@report.data.length
}}</h2>
<div class="rewind-report-container">
<div class="rewind-card">
{{#each @report.data as |topic idx|}}
@ -28,5 +33,6 @@ export default class BestTopics extends Component {
</div>
</div>
</div>
{{/if}}
</template>
}

View File

@ -1,10 +1,13 @@
import Component from "@glimmer/component";
import { concat } from "@ember/helper";
import { i18n } from "discourse-i18n";
export default class FavoriteCategories extends Component {
<template>
const FavoriteCategories = <template>
{{#if @report.data.length}}
<div class="rewind-report-page -favorite-categories">
<h2 class="rewind-report-title">Your favorite categories</h2>
<h2 class="rewind-report-title">{{i18n
"discourse_rewind.reports.favorite_categories.title"
count=@report.data.length
}}</h2>
<div class="rewind-report-container">
{{#each @report.data as |data|}}
<div class="rewind-card">
@ -13,5 +16,7 @@ export default class FavoriteCategories extends Component {
{{/each}}
</div>
</div>
</template>
}
{{/if}}
</template>;
export default FavoriteCategories;

View File

@ -1,10 +1,13 @@
import Component from "@glimmer/component";
import { concat } from "@ember/helper";
import { i18n } from "discourse-i18n";
export default class FavoriteTags extends Component {
<template>
const FavoriteTags = <template>
{{#if @report.data.length}}
<div class="rewind-report-page -favorite-tags">
<h2 class="rewind-report-title">Your favorite tags</h2>
<h2 class="rewind-report-title">{{i18n
"discourse_rewind.reports.favorite_categories.title"
count=@report.data.length
}}</h2>
<div class="rewind-report-container">
{{#each @report.data as |data|}}
<div class="rewind-card">
@ -13,5 +16,7 @@ export default class FavoriteTags extends Component {
{{/each}}
</div>
</div>
</template>
}
{{/if}}
</template>;
export default FavoriteTags;

View File

@ -1,12 +1,12 @@
import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import avatar from "discourse/helpers/bound-avatar-template";
import { i18n } from "discourse-i18n";
// eslint-disable-next-line ember/no-empty-glimmer-component-classes
export default class FBFF extends Component {
<template>
const FBFF = <template>
<div class="rewind-report-page -fbff">
<h2 class="rewind-report-title">Your FBFF (Forum Best Friend Forever)</h2>
<h2 class="rewind-report-title">{{i18n
"discourse_rewind.reports.fbff.title"
}}</h2>
<div class="rewind-report-container">
<div class="rewind-card">
<div class="fbff-avatar-container">
@ -34,5 +34,6 @@ export default class FBFF extends Component {
</div>
</div>
</div>
</template>
}
</template>;
export default FBFF;

View File

@ -1,11 +1,4 @@
import Component from "@glimmer/component";
import { service } from "@ember/service";
import dIcon from "discourse-common/helpers/d-icon";
export default class Rewind extends Component {
@service siteSettings;
<template>
const Introduction = <template>
<div class="rewind__introduction">
<img
class="rewind-logo-light"
@ -15,7 +8,7 @@ export default class Rewind extends Component {
class="rewind-logo-dark"
src="/plugins/discourse-rewind/images/discourse-rewind-logo-dark.png"
/>
</div>
</template>
}
</template>;
export default Introduction;

View File

@ -3,19 +3,24 @@ import { concat } from "@ember/helper";
import { action } from "@ember/object";
import { htmlSafe } from "@ember/template";
import replaceEmoji from "discourse/helpers/replace-emoji";
import { i18n } from "discourse-i18n";
export default class Reactions extends Component {
@action
cleanEmoji(emojiName) {
return emojiName.replaceAll(/_/g, " ");
}
get totalPostUsedReactions() {
return Object.values(
this.args.report.data.post_used_reactions ?? {}
).reduce((acc, count) => acc + count, 0);
}
get receivedReactions() {
return this.args.report.data.post_received_reactions ?? {};
}
@action
cleanEmoji(emojiName) {
return emojiName.replaceAll(/_/g, " ");
}
@action
computePercentage(count) {
return `${((count / this.totalPostUsedReactions) * 100).toFixed(2)}%`;
@ -26,13 +31,11 @@ export default class Reactions extends Component {
return htmlSafe(`width: ${this.computePercentage(count)}`);
}
get receivedReactions() {
return this.args.report.data.post_received_reactions ?? {};
}
<template>
<div class="rewind-report-page -post-received-reactions">
<h2 class="rewind-report-title">Most received reactions in topics</h2>
<h2 class="rewind-report-title">{{i18n
"discourse_rewind.reports.post_received_reactions.title"
}}</h2>
<div class="rewind-report-container">
{{#each-in this.receivedReactions as |emojiName count|}}
<div class="rewind-card scale">
@ -46,7 +49,9 @@ export default class Reactions extends Component {
</div>
<div class="rewind-report-page -post-used-reactions">
<h2 class="rewind-report-title">Most used reactions in topics</h2>
<h2 class="rewind-report-title">{{i18n
"discourse_rewind.reports.post_used_reactions.title"
}}</h2>
<div class="rewind-card">
<div class="rewind-reactions-chart">
{{#each-in @report.data.post_used_reactions as |emojiName count|}}
@ -64,8 +69,10 @@ export default class Reactions extends Component {
{{/each-in}}
<span class="rewind-total-reactions">
Total number of reactions:
{{this.totalPostUsedReactions}}
{{i18n
"discourse_rewind.reports.post_used_reactions.total_number"
count=this.totalPostUsedReactions
}}
</span>
</div>
</div>

View File

@ -1,4 +1,6 @@
import Component from "@glimmer/component";
import { htmlSafe } from "@ember/template";
import { i18n } from "discourse-i18n";
export default class ReadingTime extends Component {
get readTimeString() {
@ -10,13 +12,21 @@ export default class ReadingTime extends Component {
}
<template>
{{#if @report.data}}
<div class="rewind-report-page -reading-time">
<h2 class="rewind-report-title">Reading Time</h2>
<h2 class="rewind-report-title">{{i18n
"discourse_rewind.reports.reading_time.title"
}}</h2>
<div class="rewind-card">
<p class="reading-time__text">You spent
<code>{{this.readTimeString}}</code>
reading on our site! That's the time it would take to read through
<i>{{@report.data.book}}</i></p>
<p class="reading-time__text">
{{htmlSafe
(i18n
"discourse_rewind.reports.reading_time.book_comparison"
readingTitme=this.readTimeString
bookTitle=@report.data.book
)
}}
</p>
<div class="reading-time__book">
<div class="book">
<img
@ -27,5 +37,6 @@ export default class ReadingTime extends Component {
</div>
</div>
</div>
{{/if}}
</template>
}

View File

@ -1,5 +1,6 @@
import Component from "@glimmer/component";
import WordCard from "discourse/plugins/discourse-rewind/discourse/components/reports/word-card";
import { i18n } from "discourse-i18n";
import WordCard from "discourse/plugins/discourse-rewind/discourse/components/reports/top-words/word-card";
export default class WordCards extends Component {
get topWords() {
@ -7,9 +8,11 @@ export default class WordCards extends Component {
}
<template>
<div class="rewind-report-page -word-cloud">
<div class="rewind-report-page -top-words">
<div class="rewind-report-container">
<h2 class="rewind-report-title">Word Usage</h2>
<h2 class="rewind-report-title">{{i18n
"discourse_rewind.reports.top_words.title"
}}</h2>
<div class="cards-container">
{{#each this.topWords as |entry index|}}
<WordCard

View File

@ -18,7 +18,7 @@ import FBFF from "discourse/plugins/discourse-rewind/discourse/components/report
import Introduction from "discourse/plugins/discourse-rewind/discourse/components/reports/introduction";
import Reactions from "discourse/plugins/discourse-rewind/discourse/components/reports/reactions";
import ReadingTime from "discourse/plugins/discourse-rewind/discourse/components/reports/reading-time";
import WordCards from "discourse/plugins/discourse-rewind/discourse/components/reports/word-cards";
import TopWords from "discourse/plugins/discourse-rewind/discourse/components/reports/top-words";
export default class Rewind extends Component {
@service siteSettings;
@ -110,8 +110,8 @@ export default class Rewind extends Component {
<FBFF @report={{report}} />
{{else if (eq report.identifier "reactions")}}
<Reactions @report={{report}} />
{{else if (eq report.identifier "word-cloud")}}
<WordCards @report={{report}} />
{{else if (eq report.identifier "top-words")}}
<TopWords @report={{report}} />
{{else if (eq report.identifier "best-posts")}}
<BestPosts @report={{report}} />
{{else if (eq report.identifier "best-topics")}}

View File

@ -7,9 +7,9 @@
@import "activity-calendar";
@import "best-posts";
@import "best-topics";
@import "word-card";
@import "top-words";
@import "favorite-tags";
@import "favorite-categories";
@import "fonts";
@import "reading-time";
@import "bff";
@import "fbff";

View File

@ -1,4 +1,4 @@
.rewind-report-page.-word-cloud .rewind-report-container {
.rewind-report-page.-top-words .rewind-report-container {
border: none;
background-color: transparent;
width: 100%;

View File

@ -7,3 +7,36 @@ en:
js:
discourse_rewind:
placeholder: Discourse Rewind
reports:
activity_calendar:
title: Activity Calendar
top_words:
title: Word Usage
reading_time:
title: Reading time
book_comparison: "You spent <code>%{readingTitme}</code> reading on our site! That's the time it would take to read through <i>%{bookTitle}</i>"
post_used_reactions:
title: Most used reactions in topics
total_number: "Total number of reactions: %{count}"
post_received_reactions:
title: Most received reactions in topics
fbff:
title: Your FBFF (Forum Best Friend Forever)
favorite_categories:
title:
one: Your favorite category
other: Your %{count} favorite categories
favorite_tags:
title:
one: Your favorite tag
other: Your %{count} favorite tags
best_topics:
title:
one: Your best topic
other: Your %{count} best topics
best_posts:
view_post: View post
title:
one: Your best post
other: Your %{count} best posts