├── .gitignore ├── README.md ├── assets ├── javascripts │ ├── discourse │ │ ├── components │ │ │ └── blog-post-header.js.es6 │ │ ├── connectors │ │ │ └── topic-above-post-stream │ │ │ │ └── blog-post-header-image.hbs │ │ ├── initializers │ │ │ └── extend-for-discourse-blog-post.js.es6 │ │ └── templates │ │ │ └── components │ │ │ └── blog-post-header.hbs │ └── lib │ │ └── discourse-markdown │ │ └── blog-post-whitelist.js.es6 └── stylesheets │ └── blog-post-styles.scss ├── config ├── locales │ ├── client.en.yml │ └── server.en.yml └── settings.yml └── plugin.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Note:** I'm no longer supporting this plugin. I think the [WP Discourse](https://github.com/discourse/wp-discourse) WordPress plugin is a better alternative for combining styled posts with a Discourse forum. If anyone would like to take over maintaining the Discourse Blog Post plugin, that would be great. 2 | 3 | ## Discourse Blog Post 4 | 5 | This plugin is for adding 'blog post' styles to the first post in a Discourse topic. If the post contains images, it places the first image 6 | in a full width header above the topic. It adds the css class `blog-post` to the topic's `body` tag, and the class `blog-post-content` to 7 | the cooked content of the post. 8 | 9 | The ability to create blog-posts on a forum is controlled by group membership. By default, only members 10 | of the `admins` and `moderators` groups are allowed to create blog-posts. To allow all users, trust level 11 | 1 and up, to be able to create blog posts, add the group 'trust_level_1' to the 'blog post allowed groups' setting. 12 | 13 | Blog posts must be enabled for a category before they can be created. This is done on the plugin's settings page. 14 | Enter the category names of the categories you want to use into the 'blog post allowed categories' setting. 15 | If a category is removed from the allowed categories list, blog posts that have already been created will remain. 16 | They can be converted to regular posts either by their creator or by a site admin. 17 | 18 | To create a blog-post in an enabled category, open the hidden ('...') post menu buttons underneath the first post 19 | in a topic and click on the 'book' icon. That will convert the post into a blog-post and turn the 'book' icon yellow. 20 | To convert a blog-post back into a regular post, click the 'book' icon again. 21 | 22 | After first creating a blog-post, you will need to either revisit or refresh the page for the header image to 23 | be hidden from the post content. Only the post creator will see the unhidden image. It's hidden from the post 24 | by adding the css class `blog-post-image` when the post content is rendered. 25 | 26 | The plugin adds some basic css rules for styling blog-posts. It allows you to use `` for 27 | creating large leading letters for paragraphs. 28 | 29 | To add further styling to blog posts on a forum, add styles to the `blog-post` class, for general page styles, 30 | or to the `blog-post-content` class for styling the content of the posts. For example, this will increase the font 31 | size to `20px` and set the `max-height` of the header image to `440px`. 32 | 33 | .blog-post-content { 34 | font-size: 20px; 35 | } 36 | 37 | .blog-post .blog-post-header-container { 38 | max-height: 440px; 39 | } 40 | 41 | The plugin's default styles can be disabled by deselecting the 'blog post use default styles' checkbox on the settings 42 | page. This could also be useful if you only wish to use the plugin's header-image function. 43 | 44 |  45 |  46 | 47 | ### Installation 48 | 49 | Follow the [Install a Plugin](https://meta.discourse.org/t/install-a-plugin/19157) howto, using 50 | `git clone https://github.com/scossar/discourse-blog-post` as the plugin command. 51 | 52 | Once you've installed it, go to the plugin settings page to enable the plugin and configure which groups should be 53 | allowed to create blog posts and which categories they should be allowed to create them in. 54 | 55 | If you have any problems with this plugin, feel free to create an issue here. Any improvements to the plugin's css would be greatly appreciated. 56 | Pull requests are welcome. 57 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/blog-post-header.js.es6: -------------------------------------------------------------------------------- 1 | import {on} from 'ember-addons/ember-computed-decorators' 2 | import Scrolling from 'discourse/mixins/scrolling'; 3 | 4 | export default Ember.Component.extend(Scrolling, { 5 | tagName: 'div', 6 | classNames: 'discourse-blog-post-header', 7 | 8 | @on('didInsertElement', 'didReceiveAttrs') 9 | addBodyClass() { 10 | let bodyClasses = Discourse.SiteSettings.blog_post_use_default_styles ? 'blog-post blog-post-docked use-blog-post-styles' : 'blog-post blog-post-docked'; 11 | $('body').addClass(bodyClasses); 12 | 13 | this.bindScrolling({name: 'blog-post-header'}); 14 | }, 15 | 16 | @on('willDestroyElement') 17 | removeBodyClass() { 18 | $('body').removeClass('blog-post use-blog-post-styles blog-post-docked'); 19 | }, 20 | 21 | scrolled() { 22 | $('body').removeClass('blog-post-docked'); 23 | this.unbindScrolling('blog-post-header'); 24 | } 25 | }); -------------------------------------------------------------------------------- /assets/javascripts/discourse/connectors/topic-above-post-stream/blog-post-header-image.hbs: -------------------------------------------------------------------------------- 1 | {{#if model.has_blog_post}} 2 | {{blog-post-header src=model.image_url}} 3 | {{/if}} -------------------------------------------------------------------------------- /assets/javascripts/discourse/initializers/extend-for-discourse-blog-post.js.es6: -------------------------------------------------------------------------------- 1 | import {withPluginApi} from 'discourse/lib/plugin-api'; 2 | import {ajax} from 'discourse/lib/ajax'; 3 | import {popupAjaxError} from 'discourse/lib/ajax-error'; 4 | import TopicStatus from 'discourse/views/topic-status'; 5 | 6 | function markAsBlogPost(post) { 7 | const topic = post.topic; 8 | 9 | post.set('is_blog_post', true); 10 | topic.set('has_blog_post', true); 11 | 12 | ajax('/blog/mark_as_blog_post', { 13 | type: 'POST', 14 | data: {id: post.id} 15 | }).catch(popupAjaxError); 16 | } 17 | 18 | function unmarkAsBlogPost(post) { 19 | const topic = post.topic; 20 | 21 | post.set('is_blog_post', false); 22 | topic.set('has_blog_post', false); 23 | 24 | ajax('/blog/unmark_as_blog_post', { 25 | type: 'POST', 26 | data: {id: post.id} 27 | }).catch(popupAjaxError); 28 | } 29 | 30 | function addBlogImageClass($elem, helper) { 31 | if (helper) { 32 | const post = helper.getModel(); 33 | const isBlogPost = post.get('is_blog_post'); 34 | const imageUrl = post.get('image_url'); 35 | 36 | if (isBlogPost && imageUrl) { 37 | $elem.find('img').first().addClass('blog-post-image'); 38 | $elem.addClass('blog-post-content'); 39 | } else if (isBlogPost) { 40 | $elem.addClass('blog-post-content'); 41 | } 42 | } 43 | } 44 | 45 | function initializeWithApi(api) { 46 | api.includePostAttributes('is_blog_post', 'can_create_blog_post', 'allow_blog_posts_in_category', 'image_url'); 47 | 48 | api.addPostMenuButton('blogPost', attrs => { 49 | 50 | if (attrs.firstPost && attrs.can_create_blog_post) { 51 | if (attrs.is_blog_post) { 52 | 53 | return { 54 | action: 'unmarkAsBlogPost', 55 | icon: 'book', 56 | className: 'blog-post-icon', 57 | title: 'blog_post.convert_to_regular_post', 58 | position: 'second-last-hidden' 59 | } 60 | 61 | } else if (attrs.allow_blog_posts_in_category) { 62 | 63 | return { 64 | action: 'markAsBlogPost', 65 | icon: 'book', 66 | className: 'not-blog-post-icon', 67 | title: 'blog_post.convert_to_blog_post', 68 | position: 'second-last-hidden' 69 | } 70 | } 71 | } 72 | }); 73 | 74 | api.attachWidgetAction('post', 'markAsBlogPost', function () { 75 | const post = this.model; 76 | const current = post.get('topic.postStream.posts'); 77 | const topic = post.get('topic'); 78 | 79 | markAsBlogPost(post); 80 | 81 | current.forEach(p => this.appEvents.trigger('post-stream:refresh', {id: p.id})); 82 | }); 83 | 84 | api.attachWidgetAction('post', 'unmarkAsBlogPost', function () { 85 | const post = this.model; 86 | const current = post.get('topic.postStream.posts'); 87 | 88 | unmarkAsBlogPost(post); 89 | 90 | current.forEach(p => this.appEvents.trigger('post-stream:refresh', {id: p.id})); 91 | }); 92 | 93 | api.decorateCooked(addBlogImageClass); 94 | } 95 | 96 | export default { 97 | name: 'extend-for-discourse-blog-post', 98 | initialize() { 99 | 100 | TopicStatus.reopen({ 101 | statuses: function () { 102 | const results = this._super(); 103 | if (this.topic.has_blog_post) { 104 | results.push({ 105 | openTag: 'a href', 106 | closeTag: 'a', 107 | href: this.topic.get('url'), 108 | extraClasses: 'topic-status-blog-post', 109 | title: I18n.t('blog_post.has_blog_post'), 110 | icon: 'book', 111 | }); 112 | } 113 | return results; 114 | }.property() 115 | }); 116 | 117 | withPluginApi('0.1', initializeWithApi); 118 | } 119 | }; -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/components/blog-post-header.hbs: -------------------------------------------------------------------------------- 1 |