├── README.md ├── assets ├── javascripts │ └── discourse │ │ ├── helpers │ │ ├── latest-posts-category.js.es6 │ │ ├── leaderboard-list.js.es6 │ │ └── recent-replies.js.es6 │ │ ├── initializers │ │ └── home-sidebar.js.es6 │ │ ├── templates │ │ └── discovery.hbs │ │ └── widgets │ │ ├── sidebar-category-posts.js.es6 │ │ ├── sidebar-custom-content.js.es6 │ │ ├── sidebar-items.js.es6 │ │ ├── sidebar-latest-replies.js.es6 │ │ ├── sidebar-leaderboard.js.es6 │ │ ├── sidebar-post-item.js.es6 │ │ └── sidebar-reply-item.js.es6 └── stylesheets │ └── sidebar.scss ├── config ├── locales │ ├── client.en.yml │ ├── client.es.yml │ ├── client.sq.yml │ ├── server.en.yml │ └── server.es.yml └── settings.yml └── plugin.rb /README.md: -------------------------------------------------------------------------------- 1 | # discourse-sidebar-blocks 2 | 3 | A discourse plugin that adds a sidebar to topic lists (discovery) with several blocks available: posts from a category, recent replies 4 | 5 | ## Support and more info 6 | Please [read and discuss here](https://meta.discourse.org/t/discourse-sidebar-blocks/51457). 7 | 8 | ## ⚠️ This plugin is deprecated 9 | 10 | Please don't use this plugin, https://github.com/discourse/discourse-right-sidebar-blocks is now a much better option. 11 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/helpers/latest-posts-category.js.es6: -------------------------------------------------------------------------------- 1 | export function getLatestPosts(context) { 2 | const store = context.register.lookup('service:store'); 3 | var filter = "c/" + context.attrs.category; 4 | return store.findFiltered("topicList", {filter: filter}).then((result) => { 5 | return result.topic_list.topics; 6 | }).catch(() => { 7 | console.log('getting topic list failed') 8 | return []; 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/helpers/leaderboard-list.js.es6: -------------------------------------------------------------------------------- 1 | import { ajax } from 'discourse/lib/ajax'; 2 | 3 | export function getLeaderboardList(context) { 4 | const period = context.siteSettings.sidebar_leaderboard_period; 5 | const count = context.siteSettings.sidebar_leaderboard_count; 6 | return ajax('/directory_items.json?period='+period+'&order=likes_received').then(function (result) { 7 | // console.log("Leaderboard list :", result); 8 | return result.directory_items.splice(0,count); 9 | }).catch(() => { 10 | // console.log('getting user list failed') 11 | return []; 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/helpers/recent-replies.js.es6: -------------------------------------------------------------------------------- 1 | import { ajax } from 'discourse/lib/ajax'; 2 | 3 | export function getLatestReplies(context) { 4 | return ajax('/posts.json').then(function (result) { 5 | return result.latest_posts; 6 | }).catch(() => { 7 | console.log('getting topic list failed') 8 | return []; 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/initializers/home-sidebar.js.es6: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'home-sidebar', 3 | initialize(){ 4 | 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/discovery.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{discourse-banner user=currentUser banner=site.banner}} 3 |
4 | 5 |
6 |
7 | {{outlet "navigation-bar"}} 8 |
9 |
10 | 11 | 12 |
13 |
14 |
15 | {{conditional-loading-spinner condition=loading}} 16 |
17 | {{outlet "header-list-container"}} 18 |
19 |
20 |
21 |
22 |
23 |
24 | {{plugin-outlet name="discovery-list-container-top" 25 | args=(hash category=category) 26 | tagName="span" 27 | connectorTagName="div"}} 28 | {{outlet "list-container"}} 29 |
30 |
31 |
32 |
33 | 34 | {{#if siteSettings.sidebar_enable}} 35 | 38 | {{/if}} 39 | 40 | {{plugin-outlet name="discovery-below" tagName="span" connectorTagName="div"}} 41 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/widgets/sidebar-category-posts.js.es6: -------------------------------------------------------------------------------- 1 | import { createWidget } from "discourse/widgets/widget"; 2 | import { getLatestPosts } from "discourse/plugins/discourse-sidebar-blocks/discourse/helpers/latest-posts-category"; 3 | import { categoryBadgeHTML } from "discourse/helpers/category-link"; 4 | import RawHtml from "discourse/widgets/raw-html"; 5 | import { h } from "virtual-dom"; 6 | import Category from "discourse/models/category"; 7 | 8 | export default createWidget("sidebar-category-posts", { 9 | tagName: "div.sidebar-category-posts", 10 | buildKey: (attrs) => `sidebar-category-posts-${attrs.category}`, 11 | defaultState() { 12 | return { loading: false }; 13 | }, 14 | buildClasses(attrs) { 15 | const result = []; 16 | if (attrs.category) { 17 | result.push(`sidebar-c-${attrs.category}`); 18 | } 19 | return result; 20 | }, 21 | 22 | refreshTopics() { 23 | if (this.state.loading) { 24 | return; 25 | } 26 | this.state.loading = true; 27 | this.state.topics = "empty"; 28 | getLatestPosts(this).then((result) => { 29 | for (var i = result.length - 1; i >= 0; i--) { 30 | // remove archived posts 31 | if (result[i].archived) { 32 | result.splice(i, 1); 33 | } 34 | } 35 | 36 | if (result.length) { 37 | var max = parseInt(this.siteSettings.sidebar_num_results) - 1; 38 | for (var i = result.length - 1; i >= 0; i--) { 39 | if (i > max) { 40 | result.splice(i, 1); 41 | } 42 | } 43 | this.state.topics = result; 44 | } else { 45 | this.state.topics = "empty"; 46 | } 47 | this.state.loading = false; 48 | this.scheduleRerender(); 49 | }); 50 | }, 51 | 52 | html(attrs, state) { 53 | if (!state.topics) { 54 | this.refreshTopics(); 55 | } 56 | const result = []; 57 | if (state.loading) { 58 | result.push(h("div.spinner-container", h("div.spinner"))); 59 | } else if (state.topics !== "empty") { 60 | var category = Category.findBySlug(attrs.category); 61 | result.push(h("div", { innerHTML: categoryBadgeHTML(category) })); 62 | const topicItems = state.topics.map((t) => 63 | this.attach("sidebar-post-item", t) 64 | ); 65 | result.push(h("div", [topicItems])); 66 | } else { 67 | var category = Category.findBySlug(attrs.category); 68 | result.push(h("div", { innerHTML: categoryBadgeHTML(category) })); 69 | result.push(h("div.no-messages", "No posts in this category.")); 70 | } 71 | 72 | return result; 73 | }, 74 | }); 75 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/widgets/sidebar-custom-content.js.es6: -------------------------------------------------------------------------------- 1 | import { createWidget } from 'discourse/widgets/widget'; 2 | import { h } from 'virtual-dom'; 3 | 4 | createWidget('sidebar-custom-content', { 5 | tagName: 'div.sidebar-custom-content', 6 | 7 | html(attrs) { 8 | return h('div', {innerHTML: this.siteSettings.sidebar_custom_content}); 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/widgets/sidebar-items.js.es6: -------------------------------------------------------------------------------- 1 | import { createWidget } from 'discourse/widgets/widget'; 2 | import { h } from 'virtual-dom'; 3 | 4 | export default createWidget('sidebar-items', { 5 | tagName: 'div.sidebar-items', 6 | buildKey: () => 'sidebar-items', 7 | 8 | html(attrs, state) { 9 | if (!this.siteSettings.sidebar_enable || this.site.mobileView) 10 | return; 11 | 12 | var sidebarBlocks = this.siteSettings.sidebar_block_order.split("|"); 13 | 14 | const result = []; 15 | var self = this; 16 | 17 | sidebarBlocks.map(function(item) { 18 | if (item == 'latest_replies') { 19 | result.push(self.attach('sidebar-latest-replies')); 20 | } else if (item == 'custom_html') { 21 | result.push(self.attach('sidebar-custom-content')); 22 | } else if (item == 'leaderboard') { 23 | result.push(self.attach('sidebar-leaderboard')); 24 | } else { 25 | result.push(self.attach('sidebar-category-posts', {category: item})); 26 | } 27 | }); 28 | 29 | return result; 30 | }, 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/widgets/sidebar-latest-replies.js.es6: -------------------------------------------------------------------------------- 1 | import { createWidget } from 'discourse/widgets/widget'; 2 | import { getLatestReplies } from 'discourse/plugins/discourse-sidebar-blocks/discourse/helpers/recent-replies'; 3 | import { h } from 'virtual-dom'; 4 | 5 | export default createWidget('sidebar-latest-replies', { 6 | tagName: 'div.sidebar-latest-replies', 7 | buildKey: attrs => 'sidebar-latest-replies', 8 | defaultState() { 9 | return { loading: false }; 10 | }, 11 | 12 | refreshPosts() { 13 | if (this.state.loading) { return; } 14 | this.state.loading = true 15 | this.state.posts = 'empty' 16 | getLatestReplies(this).then((result) => { 17 | if (result.length) { 18 | for (var i = result.length - 1; i >= 0; i--) { 19 | // remove first post in a topic (not a reply) 20 | // remove any "post" that is merely an action 21 | // remove hidden posts 22 | if (result[i].post_number < 2 || result[i].action_code != undefined || result[i].hidden) { 23 | result.splice(i, 1); 24 | } 25 | } 26 | 27 | for (var i = result.length - 1; i >= 0; i--) { 28 | // limit to 5 max 29 | if (i > 4) { 30 | result.splice(i, 1); 31 | } 32 | } 33 | this.state.posts = result; 34 | } else { 35 | this.state.posts = 'empty' 36 | } 37 | this.state.loading = false 38 | this.scheduleRerender() 39 | }) 40 | }, 41 | 42 | html(attrs, state) { 43 | const messageBus = this.register.lookup('message-bus:main'); 44 | messageBus.subscribe("/latest", data => { 45 | this.refreshPosts(); 46 | }); 47 | 48 | if (!state.posts) { 49 | this.refreshPosts(); 50 | } 51 | const result = []; 52 | if (state.loading) { 53 | result.push(h('div.spinner-container', h('div.spinner'))); 54 | } else if (state.posts !== 'empty') { 55 | result.push(h('h3.sidebar-heading', I18n.t('sidebar_blocks.recent_replies'))); 56 | const replies = state.posts.map(t => this.attach('sidebar-reply-item', t)); 57 | result.push(replies); 58 | } else { 59 | result.push(h('div.no-messages', 'No recent replies.')) 60 | } 61 | 62 | return result; 63 | }, 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/widgets/sidebar-leaderboard.js.es6: -------------------------------------------------------------------------------- 1 | import { createWidget } from 'discourse/widgets/widget'; 2 | import { h } from 'virtual-dom'; 3 | import { getLeaderboardList } from 'discourse/plugins/discourse-sidebar-blocks/discourse/helpers/leaderboard-list'; 4 | import { iconNode } from "discourse-common/lib/icon-library"; 5 | 6 | createWidget('sidebar-leaderboard', { 7 | tagName: 'div.sidebar-leaderboard', 8 | buildKey: attrs => 'sidebar-leaderboard', 9 | defaultState() { 10 | return { loading: false }; 11 | }, 12 | refreshUsers() { 13 | if (this.state.loading) { return; } 14 | this.state.loading = true 15 | this.state.users = 'empty' 16 | getLeaderboardList(this).then((result) => { 17 | // console.log("refreshUsers List : ", result); 18 | if (result.length) { 19 | this.state.users = result; 20 | } else { 21 | this.state.users = 'empty' 22 | } 23 | this.state.loading = false 24 | this.scheduleRerender() 25 | }) 26 | }, 27 | 28 | leaderboardRow(user){ 29 | // console.log("user : ", user); 30 | return h("tr", 31 | { 32 | "attributes": { 33 | "data-user-card": user.user.username 34 | } 35 | }, 36 | [ 37 | h('td', [ 38 | h('div.useravatar', this.attach('topic-participant', user.user)), 39 | h('div.username.trigger-data-card', user.user.username) 40 | ]), 41 | h('td', h('span.points', user.likes_received+'')) 42 | ]); 43 | }, 44 | 45 | leaderboardHeader(){ 46 | return h( 47 | 'h3.sidebar-heading', h( 48 | 'a', { 49 | 'attributes':{ 50 | 'href':'/u', 51 | 'title':'Leaderboard' 52 | } 53 | }, 54 | 'Leaderboard' 55 | ) 56 | ); 57 | }, 58 | 59 | html(attrs, state) { 60 | if (!state.users) { 61 | this.refreshUsers(); 62 | } 63 | const result = []; 64 | if (state.loading) { 65 | result.push(h('div.spinner-container', h('div.spinner'))); 66 | } else if (state.users !== 'empty') { 67 | result.push(this.leaderboardHeader()); 68 | 69 | const users = state.users.map( user => this.leaderboardRow(user)) 70 | 71 | result.push( 72 | h("div.directory", 73 | h("table", [ 74 | h("tbody", [ 75 | h("tr", [ 76 | h("th", 'User'), 77 | h("th", [ 78 | iconNode('heart'), 79 | h('span','Received') 80 | ]) 81 | ]), 82 | users 83 | ]) 84 | ]) 85 | ) 86 | ); 87 | } else { 88 | result.push(h('div.no-messages', 'No users loaded.')) 89 | } 90 | 91 | return result; 92 | }, 93 | 94 | }); 95 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/widgets/sidebar-post-item.js.es6: -------------------------------------------------------------------------------- 1 | import { createWidget } from 'discourse/widgets/widget'; 2 | import { h } from 'virtual-dom'; 3 | import getURL from "discourse-common/lib/get-url"; 4 | 5 | createWidget('sidebar-post-item', { 6 | tagName: 'div.sidebar-post-item', 7 | 8 | html(attrs) { 9 | var url = getURL("/t/") + attrs.slug + "/" + attrs.id; 10 | return [ 11 | h('a.item-title', { 12 | attributes: { href: url} 13 | }, attrs.title), 14 | h('span.comment_count', {}, attrs.posts_count - 1), 15 | // h('span', this.attach('featured-link', {topic: attrs})) 16 | ] 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/widgets/sidebar-reply-item.js.es6: -------------------------------------------------------------------------------- 1 | import { createWidget } from 'discourse/widgets/widget'; 2 | import { h } from 'virtual-dom'; 3 | import { dateNode } from 'discourse/helpers/node'; 4 | import getURL from "discourse-common/lib/get-url"; 5 | 6 | createWidget('sidebar-reply-item', { 7 | tagName: 'div.sidebar-reply-item', 8 | 9 | html(attrs) { 10 | var url = getURL("/t/") + attrs.topic_slug + "/" + attrs.topic_id + "/" + attrs.id; 11 | 12 | var limit = 125; 13 | var excerpt = $(attrs.cooked).text(); 14 | if (excerpt.length > limit) { 15 | excerpt = $('
').html(attrs.cooked); 16 | excerpt.find("img,aside").remove(); 17 | excerpt = excerpt.text().substring(0, limit).trim(this) + "..."; 18 | } 19 | 20 | const createdAt = new Date(attrs.created_at); 21 | 22 | return [ 23 | h('span.avatar', this.attach('post-avatar', attrs)), 24 | h('span.reply-date', {}, dateNode(createdAt)), 25 | h('span.excerpt', excerpt), 26 | h('a.item-title', { 27 | attributes: { href: url} 28 | }, attrs.topic_title) 29 | ] 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /assets/stylesheets/sidebar.scss: -------------------------------------------------------------------------------- 1 | html:not(.mobile-view) { 2 | .has-sidebar + .sidebar { 3 | width: 30%; 4 | float: right; 5 | min-height: 100px; 6 | } 7 | 8 | .list-container.has-sidebar { 9 | width: 65%; 10 | float: left; 11 | } 12 | 13 | .sidebar-category-posts { 14 | margin-bottom: 30px; 15 | 16 | h3 { 17 | text-transform: capitalize; 18 | padding-bottom: 8px; 19 | } 20 | 21 | .sidebar-post-item { 22 | padding-bottom: 6px; 23 | border-bottom: 1px solid #F2F2F2; 24 | margin-bottom: 6px; 25 | } 26 | 27 | span.comment_count { 28 | margin-left: 6px; 29 | opacity: 0.5; 30 | } 31 | 32 | a.item-title { 33 | color: #222; 34 | font-size: 1.143em; 35 | } 36 | } 37 | 38 | .sidebar-latest-replies { 39 | margin-bottom: 30px; 40 | 41 | h3 { 42 | padding-bottom: 8px; 43 | } 44 | } 45 | 46 | .sidebar-reply-item { 47 | clear: both; 48 | padding: 6px 0px; 49 | border-bottom: 1px solid #F2F2F2; 50 | margin-bottom: 6px; 51 | font-size: 0.94em; 52 | line-height: 16px; 53 | 54 | > .avatar { 55 | display: block; 56 | float: left; 57 | width: 30px; 58 | margin-right: 10px; 59 | margin-bottom: 5px; 60 | 61 | .topic-avatar { 62 | width: 100%; 63 | border-top: none; 64 | padding-top: 2px; 65 | float: none; 66 | } 67 | 68 | img { 69 | width: 30px; 70 | height: 30px; 71 | } 72 | } 73 | 74 | > span.reply-date { 75 | float: right; 76 | display: block; 77 | opacity: 0.5; 78 | width: 35px; 79 | text-align: right; 80 | } 81 | 82 | > span.excerpt { 83 | margin-right: 35px; 84 | display: block; 85 | } 86 | 87 | a.item-title { 88 | margin-left: 40px; 89 | padding: 5px 0px; 90 | display: block; 91 | } 92 | } 93 | 94 | .sidebar-custom-content { 95 | margin-bottom: 30px; 96 | } 97 | 98 | .sidebar-leaderboard { 99 | td { 100 | .username { 101 | padding-left: 40px; 102 | padding-top: 3%; 103 | } 104 | 105 | .useravatar { 106 | float: left; 107 | } 108 | } 109 | 110 | .points { 111 | margin-left: 30px; 112 | } 113 | 114 | .fa-heart, .d-icon-heart { 115 | color: red; 116 | padding-right: 0.5em; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /config/locales/client.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | js: 3 | sidebar_blocks: 4 | recent_replies: "Recent replies" -------------------------------------------------------------------------------- /config/locales/client.es.yml: -------------------------------------------------------------------------------- 1 | es: 2 | js: 3 | sidebar_blocks: 4 | recent_replies: "Respuestas Recientes" 5 | -------------------------------------------------------------------------------- /config/locales/client.sq.yml: -------------------------------------------------------------------------------- 1 | sq: 2 | js: 3 | sidebar_blocks: 4 | recent_replies: "Komentet e fundit" -------------------------------------------------------------------------------- /config/locales/server.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | site_settings: 3 | sidebar_enable: "Show sidebar on the right list screens." 4 | sidebar_block_order: "Include blocks for the sidebar. Enter category slugs, 'latest_replies' for the latest replies block and/or 'custom_html' for the custom HTML from the field below." 5 | sidebar_custom_content: "Custom HTML content. To show this, you must include 'custom_html' in block list above." 6 | sidebar_num_results: "Number of category posts to display on each sidebar block." 7 | sidebar_leaderboard_count: "Number of users to show in leaderboard." 8 | sidebar_leaderboard_period: "Period for leaderboard - Possible values - daily,weekly,monthly,quarterly,yearly,all" 9 | -------------------------------------------------------------------------------- /config/locales/server.es.yml: -------------------------------------------------------------------------------- 1 | es: 2 | site_settings: 3 | sidebar_enable: "Mostrar sidebar en la parte derecha de la pantalla." 4 | sidebar_block_order: "incluir bloques para el sidebar. Ingrese 'latest_replies' para el bloque de últimas respuestas y/o 'custom_html' para el bloque de HTML personalizado." 5 | sidebar_custom_content: "Contenido HTML personalizado. Para mostrar esto, debes incluir 'custom_html' en el bloque listado arriba." 6 | sidebar_num_results: "Número de temas por categoría a mostrar en cada bloque del sidebar." 7 | sidebar_leaderboard_count: "Número de usuarios a mostrar en el leaderboard." 8 | sidebar_leaderboard_period: "Periodos para el leaderboard - Valores posibles: daily,weekly,monthly,quarterly,yearly,all" 9 | -------------------------------------------------------------------------------- /config/settings.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | sidebar_enable: 3 | default: false 4 | client: true 5 | sidebar_block_order: 6 | client: true 7 | type: list 8 | default: "latest_replies" 9 | sidebar_custom_content: 10 | type: text 11 | default: '' 12 | client: true 13 | sidebar_num_results: 14 | default: '6' 15 | client: true 16 | sidebar_leaderboard_count: 17 | default: '5' 18 | client: true 19 | sidebar_leaderboard_period: 20 | default: 'daily' 21 | client: true 22 | -------------------------------------------------------------------------------- /plugin.rb: -------------------------------------------------------------------------------- 1 | # name: discourse-sidebar-blocks 2 | # about: add a sidebar to topic lists (discovery) with several blocks available: posts from a category, recent replies 3 | # version: 0.1 4 | # authors: pmusaraj 5 | # url: https://github.com/pmusaraj/discourse-home-sidebar 6 | 7 | register_asset "stylesheets/sidebar.scss" --------------------------------------------------------------------------------