├── assets ├── javascripts │ ├── discourse │ │ ├── templates │ │ │ └── sidebar │ │ │ │ ├── search_help.js.handlebars │ │ │ │ ├── free_text.js.handlebars │ │ │ │ ├── subcategories.js.handlebars │ │ │ │ ├── signup.js.handlebars │ │ │ │ ├── featured_users.js.handlebars │ │ │ │ ├── category_list.js.handlebars │ │ │ │ ├── user_notifications.js.handlebars │ │ │ │ ├── fb_page.js.handlebars │ │ │ │ ├── create_button.js.handlebars │ │ │ │ ├── suggested_topics.js.handlebars │ │ │ │ ├── unanswered_topics.js.handlebars │ │ │ │ ├── forum_news.js.handlebars │ │ │ │ ├── category_info.js.handlebars │ │ │ │ ├── topic_stats.js.handlebars │ │ │ │ └── user_stats.js.handlebars │ │ ├── helpers │ │ │ ├── sidebar-helpers.js.es6 │ │ │ └── sidebar_widgets.js.es6 │ │ ├── initializers │ │ │ └── hide-sidebar.js.es6 │ │ └── views │ │ │ └── sidebar.js.es6 │ └── reply-new-menu.js └── stylesheets │ └── sidebar_styles.scss ├── lib └── discourse-plugin-sidebar │ ├── version.rb │ └── engine.rb ├── .gitignore ├── config ├── locales │ ├── client.en.yml │ ├── client.de.yml │ └── server.en.yml └── settings.yml ├── app ├── views │ └── sidebar │ │ └── _sidebar.html.erb ├── controllers │ └── concerns │ │ └── sidebar_concern.rb └── models │ └── sidebar_box.rb ├── plugin.rb ├── WidgetDocs.md └── README.md /assets/javascripts/discourse/templates/sidebar/search_help.js.handlebars: -------------------------------------------------------------------------------- 1 |

Suche

2 | Ein Test Text 3 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/sidebar/free_text.js.handlebars: -------------------------------------------------------------------------------- 1 | {{{Discourse.SiteSettings.sidebar_free_text}}} -------------------------------------------------------------------------------- /lib/discourse-plugin-sidebar/version.rb: -------------------------------------------------------------------------------- 1 | version.rbmodule DiscoursePluginSidebar 2 | VERSION = "0.0.1" 3 | end 4 | -------------------------------------------------------------------------------- /lib/discourse-plugin-sidebar/engine.rb: -------------------------------------------------------------------------------- 1 | module DiscoursePluginSidebar 2 | class Engine < ::Rails::Engine 3 | isolate_namespace DiscoursePluginSidebar 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/sidebar/subcategories.js.handlebars: -------------------------------------------------------------------------------- 1 | {{#if view.subcategories.length }} 2 |

Subcategories

3 | 4 | {{#each view.subcategories}} 5 | {{this.name}} 6 | {{/each}} 7 | {{/if}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | log/*.log 3 | pkg/ 4 | test/dummy/db/*.sqlite3 5 | test/dummy/db/*.sqlite3-journal 6 | test/dummy/log/*.log 7 | test/dummy/tmp/ 8 | test/dummy/.sass-cache 9 | auto_generated 10 | Gemfile.lock 11 | .DS_Store 12 | -------------------------------------------------------------------------------- /config/locales/client.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | admin_js: 3 | admin: 4 | site_settings: 5 | categories: 6 | sidebar: Sidebar 7 | js: 8 | forum_news: 9 | title: "News" 10 | no_unread_pm: No unread private messages -------------------------------------------------------------------------------- /app/views/sidebar/_sidebar.html.erb: -------------------------------------------------------------------------------- 1 | <%- if object.plugins_sidebar.present? %> 2 | 7 | <%- end %> 8 | -------------------------------------------------------------------------------- /config/locales/client.de.yml: -------------------------------------------------------------------------------- 1 | de: 2 | admin_js: 3 | admin: 4 | site_settings: 5 | categories: 6 | sidebar: Sidebar 7 | js: 8 | forum_news: 9 | title: "News" 10 | no_unread_pm: Keine ungelesenen Privatnachrichten -------------------------------------------------------------------------------- /app/controllers/concerns/sidebar_concern.rb: -------------------------------------------------------------------------------- 1 | module SidebarConcern 2 | extend ActiveSupport::Concern 3 | 4 | included do |base| 5 | before_action :sidebar 6 | end 7 | 8 | def sidebar 9 | if !request.xhr? && current_user 10 | @sidebar ||= SidebarBox.new(params) 11 | end 12 | end 13 | end -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/sidebar/signup.js.handlebars: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/sidebar/featured_users.js.handlebars: -------------------------------------------------------------------------------- 1 | {{#if view.featured_users }} 2 |

Featured Users

3 | 4 | 9 | {{/if}} -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/sidebar/category_list.js.handlebars: -------------------------------------------------------------------------------- 1 | {{#if view.categories }} 2 |

Categories

3 | 8 | {{/if}} 9 | -------------------------------------------------------------------------------- /config/locales/server.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | site_settings: 3 | sidebar_overwrite_template: Overwrite the application template with our own (you need to rebuild the assets after changing) 4 | sidebar_widgets: sidebar widgets to be loaded 5 | sidebar_fb_page: Facebook Page ID for sidebar widget 6 | sidebar_forum_news_category: Category to fetch the latest Forum News from 7 | -------------------------------------------------------------------------------- /app/models/sidebar_box.rb: -------------------------------------------------------------------------------- 1 | class SidebarBox 2 | attr_accessor :plugins_sidebar 3 | def initialize(params) 4 | @plugins_sidebar = [] 5 | SiteSetting.sidebar_widgets.split("|").each do |element| 6 | begin 7 | @plugins_sidebar << "::Sidebar::#{element.camelcase}".constantize.new(params) 8 | rescue NameError => e 9 | end 10 | end 11 | end 12 | 13 | def to_partial_path 14 | "sidebar/sidebar" 15 | end 16 | 17 | end -------------------------------------------------------------------------------- /plugin.rb: -------------------------------------------------------------------------------- 1 | # name: sidebar 2 | # about: Introduces a configurable sidebar in discourse 3 | # version: 0.1 4 | # authors: Benjamin Kampmann 5 | 6 | require File.expand_path('../lib/discourse-plugin-sidebar/engine', __FILE__) 7 | 8 | register_asset "javascripts/reply-new-menu.js" 9 | 10 | register_asset 'stylesheets/sidebar_styles.scss' 11 | register_asset 'javascripts/discourse/helpers/sidebar_widgets.js.es6' 12 | 13 | after_initialize do 14 | ::ApplicationController.send(:include, SidebarConcern) 15 | end 16 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/sidebar/user_notifications.js.handlebars: -------------------------------------------------------------------------------- 1 |

{{i18n 'user.notifications'}}

2 | 15 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/helpers/sidebar-helpers.js.es6: -------------------------------------------------------------------------------- 1 | Ember.Handlebars.helper('restrictDescription', function(description) { 2 | if (description && description.length > 150) { 3 | return new Handlebars.SafeString(description.substr(0, 150) + '…'); 4 | } else { 5 | return new Handlebars.SafeString(description); 6 | } 7 | }, 'description'); 8 | 9 | Ember.Handlebars.helper('digitGrouping', function(number) { 10 | number = parseFloat(number); 11 | return number.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1" + '.'); 12 | }, 'number'); 13 | -------------------------------------------------------------------------------- /config/settings.yml: -------------------------------------------------------------------------------- 1 | sidebar: 2 | sidebar_free_text: 3 | default: "Free Text" 4 | client: true 5 | sidebar_widgets: 6 | default: facebook_page|subcategories|category_list|signup|user_stats|free_text|unanswered_topics|user_notifications|suggested_topics|category_featured_users|category_info|create_button|admin_menu|forum_news|topic_stats 7 | client: true 8 | sidebar_fb_page: 9 | default: "" 10 | client: true 11 | sidebar_forum_news_category: 12 | default: "" 13 | client: true 14 | default_moderator: 15 | default: "" 16 | client: true 17 | -------------------------------------------------------------------------------- /assets/javascripts/reply-new-menu.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | Discourse.PostMenuView.reopen({ 4 | 5 | shouldRerenderReplyAsNewTopicButton: Discourse.View.renderIfChanged("post.cooked"), 6 | 7 | renderReplyAsNewTopic: function(post, buffer) { 8 | if (!Discourse.User.current()) return; 9 | buffer.push(""); 10 | }, 11 | 12 | clickReplyAsNewTopic: function() { 13 | this.get('controller').send('replyAsNewTopic', this.get('post')); 14 | } 15 | 16 | }); 17 | 18 | })(); 19 | 20 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/sidebar/fb_page.js.handlebars: -------------------------------------------------------------------------------- 1 | {{#if view.fb_page}} 2 | 3 |
4 |
5 |
6 | wer-weiss-was.de 7 |
8 |
9 |
10 | 11 | {{else}} 12 | {{#if currentUser.admin }} 13 | Please configure the facebook page handle 14 | {{/if}} 15 | {{/if}} 16 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/sidebar/create_button.js.handlebars: -------------------------------------------------------------------------------- 1 | {{!-- {{#if view.canCreateTopic}} 2 | 3 | {{/if}} 4 | 5 | {{#if view.canChangeCategoryNotificationLevel}} 6 | {{view 'category-notifications-button' category=view.category}} 7 | {{/if}} --}} 8 | 9 | {{#if view.canCreateCategory}} 10 | 11 | {{/if}} 12 | 13 | {{!-- {{#if view.canEditCategory}} 14 | 15 | {{/if}} 16 | --}} 17 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/sidebar/suggested_topics.js.handlebars: -------------------------------------------------------------------------------- 1 |

{{i18n 'suggested_topics.title'}}

2 |
3 | 20 |
21 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/sidebar/unanswered_topics.js.handlebars: -------------------------------------------------------------------------------- 1 |

{{i18n 'unanswered_topics.title'}}

2 |
3 | 20 |
21 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/sidebar/forum_news.js.handlebars: -------------------------------------------------------------------------------- 1 |

{{i18n 'forum_news.title'}}

2 |
3 | {{#if view.loading}} 4 | 5 | {{else}} 6 | 23 | {{/if}} 24 |
25 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/sidebar/category_info.js.handlebars: -------------------------------------------------------------------------------- 1 | {{#if view.category.short_description}} 2 |

{{i18n 'category.description'}}

3 |

4 | {{restrictDescription view.category.short_description}} 5 | {{#if view.isNotTopLevelCategory}} 6 | {{i18n 'read_more'}} 7 | {{/if}} 8 |

9 | {{/if}} 10 | 11 |
12 |
{{i18n 'categories.topics'}}
13 |
{{digitGrouping view.category.topic_count}}
14 |
{{i18n 'categories.answers'}}
15 |
{{digitGrouping view.total_reply_count}}
16 |
17 | 18 |
19 | {{i18n 'category.mod_description'}} 20 | {{#each moderator in view.category.moderators}} 21 | {{#if _view.contentIndex}},{{/if}} 22 | {{moderator.username}} 23 | {{else}} 24 | {{#if view.defaultModerator}} 25 | {{view.defaultModerator}} 26 | {{/if}} 27 | {{/each}} 28 | 29 |
30 | 31 |
32 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/sidebar/topic_stats.js.handlebars: -------------------------------------------------------------------------------- 1 | {{#if view.topic}} 2 |

{{i18n 'topic_stats.title' }}

3 |
4 |
{{i18n 'topic_stats.created'}}
5 |
{{bound-date view.topic.created_at}}
6 |
{{i18n 'topic_stats.last_answer'}}
7 |
{{bound-date view.topic.last_posted_at}}
8 |
{{i18n 'topic_stats.views'}}
9 |
{{digitGrouping view.topic.views}}
10 | {{#if view.topic.total_reply_count}} 11 |
{{i18n 'categories.answers'}}
12 |
{{digitGrouping view.topic.total_reply_count}}
13 | {{else}} 14 |
{{i18n 'topic_stats.posts'}}
15 |
{{digitGrouping view.topic.posts_count}}
16 | {{/if}} 17 |
18 |
19 |

{{i18n 'topic_stats.participants'}}

20 |
21 | {{#each view.participants}} 22 | {{topic-participant participant=this}} 23 | {{/each}} 24 |
25 |
26 | {{#if view.category.moderators}} 27 |
28 | {{i18n 'category.mod_description'}} 29 | {{#each moderator in view.category.moderators}} 30 | {{#if _view.contentIndex}},{{/if}} 31 | {{moderator.username}} 32 | {{/each}} 33 | 34 |
35 | {{/if}} 36 | {{/if}} 37 | 38 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/initializers/hide-sidebar.js.es6: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "hide-sidebar-defaults", 3 | 4 | initialize: function(container, application) { 5 | var AdminController = container.lookupFactory("controller:admin"), 6 | ApplicationController = container.lookupFactory("controller:application"), 7 | UserController = container.lookupFactory("controller:user"), 8 | TaggerAdminController = container.lookupFactory("controller:tagger_admin"); 9 | 10 | ApplicationController.reopen({ 11 | hideSidebar: function(){ 12 | return Discourse.Mobile.mobileView || this.get("askedToHideSidebar"); 13 | }.property("askedToHideSidebar"), 14 | askedToHideSidebar: false, 15 | actions: { 16 | showSidebar: function(name){ 17 | if (name === "global"){ 18 | this.set("askedToHideSidebar", false); 19 | } 20 | }, 21 | hideSidebar: function(name){ 22 | if (name === "global"){ 23 | this.set("askedToHideSidebar", true); 24 | } 25 | } 26 | } 27 | }); 28 | 29 | UserController.reopen({ 30 | hide_global_sidebar: true 31 | }); 32 | 33 | AdminController && AdminController.reopen({ 34 | hide_global_sidebar: true 35 | }); 36 | TaggerAdminController && TaggerAdminController.reopen({ 37 | hide_global_sidebar: true 38 | }); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/sidebar/user_stats.js.handlebars: -------------------------------------------------------------------------------- 1 |
2 |

My Stats

3 |
4 | {{#if view.badges}} 5 |
Badges
6 |
{{#each view.badges}}{{name}} {{/each}}
7 | {{/if}} 8 | {{#if view.likes_received }} 9 |
Likes received
10 |
{{digitGrouping view.likes_received}}
11 | {{/if}} 12 | 13 | {{#if view.likes_given }} 14 |
Likes given
15 |
{{digitGrouping view.likes_given}}
16 | {{/if}} 17 | 18 | {{#if view.topics }} 19 |
Topics
20 |
{{digitGrouping view.topics}}
21 | {{/if}} 22 | 23 | {{#if view.posts }} 24 |
Posts
25 |
{{digitGrouping view.posts}}
26 | {{/if}} 27 | 28 | {{#if view.replies }} 29 |
Replies
30 |
{{digitGrouping view.replies}}
31 | {{/if}} 32 | {{#if view.edits }} 33 |
Edits
34 |
{{digitGrouping view.edits}}
35 | {{/if}} 36 | 37 | {{#if view.quotes }} 38 |
Quotes
39 |
{{digitGrouping view.quotes}}
40 | {{/if}} 41 | 42 | {{#if view.mentions }} 43 |
Mentions
44 |
{{digitGrouping view.mentions}}
45 | {{/if}} 46 | 47 | {{#if view.bookmarks }} 48 |
Bookmarks
49 |
{{digitGrouping view.bookmarks}}
50 | {{/if}} 51 | 52 | {{#if view.starred }} 53 |
Starred
54 |
{{digitGrouping view.starred}}
55 | {{/if}} 56 | 57 | {{view.category.topic.excerpt}} 58 |
59 | -------------------------------------------------------------------------------- /assets/stylesheets/sidebar_styles.scss: -------------------------------------------------------------------------------- 1 | @import "common/foundation/variables.scss"; 2 | 3 | .global-side-bar.no-side-bar > aside.side-bar { 4 | display: none; 5 | } 6 | 7 | .global-side-bar.no-side-bar > section.main-outlet-wrap { 8 | width: 100%; 9 | } 10 | 11 | .side-bar-wrap { 12 | 13 | section.main-outlet-wrap { 14 | width: 73%; 15 | display: inline-block; 16 | } 17 | 18 | aside.side-bar { 19 | display: inline-block; 20 | width: 25%; 21 | float: right; 22 | .suggested-topics { 23 | .num { 24 | display: none; 25 | } 26 | } 27 | } 28 | 29 | // fix hard-coded overflow issues 30 | .full-width { 31 | width: auto; 32 | } 33 | // unfortunately that means we don't have space for the gutter, hide it 34 | .topic-post .gutter { 35 | display: none; 36 | } 37 | 38 | // but we make the other things bigger: yay \o/ 39 | .container.posts { 40 | padding-left: 3%; 41 | } 42 | 43 | .container.posts .topic-body { 44 | width: 90%; 45 | } 46 | 47 | #topic-title { 48 | padding-left: 20px; 49 | } 50 | 51 | // user profiles 52 | .user-main { 53 | max-width: 68%; 54 | } 55 | .user-navigation { 56 | max-width: 29%; 57 | } 58 | 59 | .unanswered-topics ul, 60 | .suggested-topics ul { 61 | list-style: none; 62 | } 63 | .unanswered-topics .like-count, 64 | .suggested-topics .like-count { 65 | display: inline-block; 66 | color: white; 67 | margin-left: -1.3em; 68 | margin-right: 1.25em; 69 | line-height: 2em; 70 | vertical-align: top; 71 | } 72 | aside.side-bar .notification-options { 73 | display: inline-block 74 | } 75 | 76 | aside.side-bar .notification-options .dropdown-toggle { 77 | float: none; 78 | } 79 | } 80 | 81 | // Sidebar Widget default designs: 82 | 83 | .side-bar-wrap aside.side-bar { 84 | ul.categories-list { 85 | list-style: none; 86 | margin: 0; 87 | li { 88 | margin: 0.5em 0; 89 | display: inline-block; 90 | .clear-badge { 91 | color: $primary; 92 | } 93 | } 94 | } 95 | 96 | } 97 | 98 | @media screen and (min-width: 1550px) { 99 | section.main-outlet-wrap { 100 | .full-width { 101 | width: auto; 102 | } 103 | } 104 | } 105 | 106 | @media screen and (min-width: 967px) and (max-width: 1139px) { 107 | .side-bar-wrap { 108 | .container, 109 | .full-width { 110 | width: auto; 111 | } 112 | } 113 | } 114 | 115 | 116 | @media all and (max-width: 850px) { 117 | // body { 118 | // min-width: 760px; 119 | // } 120 | .side-bar-wrap { 121 | .container, 122 | .full-width { 123 | min-width: auto; 124 | } 125 | } 126 | } 127 | 128 | @media screen and (max-width: 966px) { 129 | 130 | .side-bar-wrap { 131 | .container, 132 | .full-width { 133 | width: auto; 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/views/sidebar.js.es6: -------------------------------------------------------------------------------- 1 | //= require ../helpers/sidebar_widgets 2 | /** 3 | This view acts as container for sidebar widgets 4 | 5 | @class ContainerView 6 | @extends Discourse.ContainerView 7 | @namespace Discourse 8 | @module Discourse 9 | **/ 10 | 11 | var SIDEBAR_DEBUG = false; 12 | 13 | import Widgets from "discourse/plugins/sidebar/discourse/helpers/sidebar_widgets"; 14 | 15 | export default Discourse.ContainerView.extend({ 16 | name: "global", 17 | updateOnRouting: true, 18 | classNames: ['sidebar-view'], 19 | init: function(){ 20 | this._super(); 21 | 22 | SIDEBAR_DEBUG && console.log("Init Sidebar"); 23 | 24 | var widgets = Discourse.SiteSettings.sidebar_widgets.split("|") || ["stats"], 25 | externalSidebarWidgets = Discourse['external:SidebarWidgets'] || {}, 26 | sidebar = this; 27 | 28 | widgets.forEach(function(item, idx){ 29 | if (!item) return; 30 | var view = externalSidebarWidgets[item] || sidebar.get(item) || Widgets[item]; 31 | SIDEBAR_DEBUG && console.log(view, item); 32 | if (!view) return; 33 | sidebar.pushObject(sidebar.createChildView(view)); 34 | }.bind(this)); 35 | }, 36 | 37 | willInsertElement: function() { 38 | if (this.get("updateOnRouting")) { 39 | var router = Discourse.URL.get("router"); 40 | router.addObserver("url", this, "urlChanged"); 41 | } 42 | }, 43 | 44 | didInsertElement: function() { 45 | this.urlChanged(Discourse.URL.get("router")); 46 | }, 47 | 48 | urlChanged: function(router) { 49 | Ember.run.next(function() { 50 | if (this._state != "inDOM") return; 51 | 52 | var url = router.get("url"), 53 | name = this.get("name"), 54 | hide = false, 55 | me = this, 56 | handlerInfos = router.router.currentHandlerInfos, 57 | deepest = handlerInfos.length ? handlerInfos[handlerInfos.length -1] : "", 58 | controllerName = deepest.name, 59 | data = { 60 | url: url, handlerInfos: handlerInfos, 61 | currentControllerName: controllerName, 62 | currentController: deepest 63 | }; 64 | 65 | if (handlerInfos.length){ 66 | hide = !!handlerInfos.find(function(info) { 67 | return info.handler.controller.get("hide_" + name + "_sidebar") 68 | }); 69 | } 70 | 71 | if (this.get("_last_hide_state") !== hide) { 72 | this.set("_last_hide_state", hide); 73 | try { 74 | if (hide){ 75 | SIDEBAR_DEBUG && console.debug("hiding Sidebar: " + name); 76 | this.get("controller").send("hideSidebar", name); 77 | } else { 78 | SIDEBAR_DEBUG && console.debug("showing Sidebar: " + name); 79 | this.get("controller").send("showSidebar", name); 80 | } 81 | } catch (e) { 82 | SIDEBAR_DEBUG && console.error("No one is interested in hiding " + name + " sidebar", e); 83 | } 84 | } 85 | 86 | this.forEach(function(view) { 87 | view.setProperties(data); 88 | view.trigger("urlChanged"); 89 | }); 90 | }.bind(this)); 91 | } 92 | }); 93 | -------------------------------------------------------------------------------- /WidgetDocs.md: -------------------------------------------------------------------------------- 1 | # Discourse Sidebar Plugin Widget Development Documentation 2 | 3 | **For general installation instruction, please see Readme.md** 4 | 5 | ## Introduction 6 | 7 | The Sidebar Plugin itself is pluggable by design and allows third parties to provider further widgets through extension. These widgets are just common Ember-Views that just need to register with the plugin in a certain way and provide some more helper methods to make interaction easier. 8 | 9 | ## Concept 10 | 11 | Essentially when the plugin starts it replaces the main view with a wrapper adding the `Discourse.SidebarView` (as defined in `javascript/sidebar_injects.js.erb`) in an `