├── README.md ├── app └── views │ ├── journals │ └── edit.js.erb │ └── redmine_editor_preview_tab │ ├── _redmine_editor_preview_tab_partial.html.erb │ └── _settings.html.erb ├── assets ├── javascripts │ └── redmine_editor_preview_tab.js └── stylesheets │ ├── redmine_editor_preview_tab.css │ └── redmine_editor_preview_tab_hide.css ├── config └── locales │ ├── de.yml │ ├── en.yml │ ├── fr.yml │ ├── ja.yml │ ├── pt-BR.yml │ └── ru.yml ├── docs ├── screenshot_preview.png ├── screenshot_settings.png └── screenshot_write.png ├── init.rb └── lib └── redmine_editor_preview_tab.rb /README.md: -------------------------------------------------------------------------------- 1 | [![Maintained? No](https://img.shields.io/badge/maintained%3F-no!-red.svg)](https://shields.io/) 2 | # THIS PROJECT IS NO LONGER BEING MAINTAINED 3 | The author no longer uses Redmine and cannot effectively update and maintain this repo. 4 | 5 | # Redmine Editor Preview Tab 6 | 7 | ## Summary 8 | 9 | The Redmine Editor Preview Tab Extension adds a preview tab to the [Redmine](http://www.redmine.org/) text editor, similar to the editor on Github. 10 | 11 | ![Settings](https://raw.githubusercontent.com/tleish/redmine_editor_preview_tab/master/docs/screenshot_write.png) 12 | 13 | ![Settings](https://raw.githubusercontent.com/tleish/redmine_editor_preview_tab/master/docs/screenshot_preview.png) 14 | 15 | ## Installation 16 | ``` 17 | $ cd redmine/plugins 18 | $ git clone https://github.com/tleish/redmine_editor_preview_tab 19 | ``` 20 | 21 | restart Redmine 22 | 23 | ## Settings 24 | By default, both tab and the default Redmine preview functionality is still enabled. You can disable/hide the default Redmine preview in the plugin settings. 25 | 26 | Go to Adminstration > Plugins > Redmine Editor Preview Tab Extension 27 | 28 | ![Settings](https://raw.githubusercontent.com/tleish/redmine_editor_preview_tab/master/docs/screenshot_settings.png) 29 | -------------------------------------------------------------------------------- /app/views/journals/edit.js.erb: -------------------------------------------------------------------------------- 1 | $("#journal-<%= @journal.id %>-notes").hide(); 2 | 3 | if ($("form#journal-<%= @journal.id %>-form").length > 0) { 4 | // journal edit form already loaded 5 | $("#journal-<%= @journal.id %>-form").show(); 6 | } else { 7 | $("#journal-<%= @journal.id %>-notes").after('<%= escape_javascript(render :partial => 'notes_form') %>'); 8 | } 9 | 10 | $("#journal_<%= @journal.id %>_notes").focus(); -------------------------------------------------------------------------------- /app/views/redmine_editor_preview_tab/_redmine_editor_preview_tab_partial.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | if content_for(:header_tags).present? && content_for(:header_tags).include?('/jstoolbar/') 3 | content_for :header_tags do %> 4 | 17 | <%= javascript_include_tag('redmine_editor_preview_tab.js', plugin: 'redmine_editor_preview_tab') %> 18 | <%= stylesheet_link_tag('redmine_editor_preview_tab.css', plugin: 'redmine_editor_preview_tab') %> 19 | <% end 20 | 21 | if Setting.plugin_redmine_editor_preview_tab[:hide_default_preview] 22 | content_for :header_tags do 23 | stylesheet_link_tag('redmine_editor_preview_tab_hide.css', plugin: 'redmine_editor_preview_tab') 24 | end 25 | end 26 | 27 | end 28 | %> -------------------------------------------------------------------------------- /app/views/redmine_editor_preview_tab/_settings.html.erb: -------------------------------------------------------------------------------- 1 | <% settings = {} unless settings.is_a? Hash %> 2 | 3 |
4 | <%= hidden_field_tag 'settings[enabled]', true %> 5 | <%= check_box_tag 'settings[hide_default_preview]', true, !!settings[:hide_default_preview] %> 6 | Disable/Hide Redmine default preview 7 |
-------------------------------------------------------------------------------- /assets/javascripts/redmine_editor_preview_tab.js: -------------------------------------------------------------------------------- 1 | 2 | // Namespace 3 | var RedmineWikiTabPreview = RedmineWikiTabPreview || {}; 4 | 5 | /* *********************************************** 6 | CONFIG BEGIN 7 | ************************************************/ 8 | 9 | /** 10 | * @class Text 11 | * @desc Text labels 12 | */ 13 | 14 | RedmineWikiTabPreview.Text = RedmineWikiTabPreview.Text || { 15 | NO_PREVIEW: 'Nothing to preview', 16 | PREVIEW_DIVS: { 17 | description: 'Description', 18 | notes: 'Notes', 19 | preview: 'Preview', 20 | text: 'Preview', 21 | write: 'Write' 22 | } 23 | }; 24 | 25 | /** 26 | * @class Elements 27 | * @desc Renders Elements 28 | * @methods draw() 29 | */ 30 | RedmineWikiTabPreview.Elements = (function(Text) { 31 | var $textArea; 32 | var $editor; 33 | var $buttonsHtml = '
' + 36 | ''; 37 | 38 | var init = function(editor) { 39 | $editor = editor; 40 | $textArea = $editor.find('textarea'); 41 | return this; 42 | }; 43 | 44 | var draw = function() { 45 | $editor.addClass('write'); 46 | $editor.prepend($buttonsHtml) 47 | .append(drawWikiPreview()); 48 | return this; 49 | }; 50 | 51 | var drawWikiPreview = function() { 52 | // issue[notes] regex 53 | var regex = /\[([^\]]+)\]/; 54 | var name = $textArea.prop('name'); 55 | var type = (name.match(regex)) ? name.match(/\[([^\]]+)\]/)[1] : name; 56 | return $('
', { 57 | 'id': 'wiki-preview-' + type, 58 | 'class': 'wiki-preview wiki', 59 | 'data-type': type 60 | }); 61 | }; 62 | 63 | return { 64 | init: init, 65 | draw: draw 66 | }; 67 | })(RedmineWikiTabPreview.Text); 68 | 69 | /** 70 | * @class View 71 | * @desc Updates preview html 72 | * @methods draw() 73 | */ 74 | RedmineWikiTabPreview.View = (function(Text) { 75 | var $preview; 76 | 77 | var init = function(preview, original_preview) { 78 | $preview = preview; 79 | $original_preview = original_preview; 80 | return this; 81 | }; 82 | 83 | var update = function() { 84 | var html = original_preview_html(); 85 | if (html.length === 0) { 86 | html = Text.NO_PREVIEW; 87 | } 88 | $preview.html(html); 89 | return this; 90 | }; 91 | 92 | var original_preview_html = function(){ 93 | return $original_preview 94 | .find('.preview') 95 | .html() 96 | .replace(/[^>]*>/g, '').trim(); 97 | }; 98 | 99 | return { 100 | init: init, 101 | update: update 102 | }; 103 | })(RedmineWikiTabPreview.Text); 104 | 105 | /** 106 | * @class Ajax 107 | * @desc Runs ajax request to update preview 108 | * @methods draw() 109 | */ 110 | RedmineWikiTabPreview.Ajax = (function(View) { 111 | var $preview; 112 | 113 | var init = function(preview) { 114 | $preview = preview; 115 | return this; 116 | }; 117 | 118 | var send = function() { 119 | eval(onclick()); 120 | return this; 121 | }; 122 | 123 | // private 124 | 125 | var onclick = function() { 126 | return $preview.closest('form').find('a[accesskey=r]').attr('onclick') 127 | .replace(/return false;/, ''); 128 | }; 129 | 130 | // Overide main submitPreview method 131 | var submitPreview = function(url, form, target) { 132 | $.ajax({ 133 | url: url, 134 | type: 'post', 135 | data: submitPreviewData(), 136 | success: submitPreviewSuccess 137 | }); 138 | }; 139 | 140 | var submitPreviewData = function() { 141 | var ENSURE_PREVIEW = ' '; 142 | var params = [$.param(attachments())]; 143 | params.push($.param(textarea()) + ENSURE_PREVIEW); 144 | return params.join('&'); 145 | }; 146 | 147 | var submitPreviewSuccess = function(data) { 148 | var preview = $('
').html(data); 149 | View.init($preview, preview).update(); 150 | }; 151 | 152 | var textarea = function(){ 153 | return $preview.closest('.jstEditor').find('textarea'); 154 | }; 155 | 156 | var attachments = function(){ 157 | return $("input[name^='attachments']"); 158 | }; 159 | 160 | return { 161 | init: init, 162 | send: send 163 | }; 164 | })(RedmineWikiTabPreview.View); 165 | 166 | /** 167 | * @class Tab 168 | * @desc Handles tab behavior 169 | */ 170 | RedmineWikiTabPreview.Tab = (function(Ajax) { 171 | var $tab; 172 | var type; 173 | 174 | var init = function(tab) { 175 | $tab = tab; 176 | type = tab.data('type'); 177 | return this; 178 | }; 179 | 180 | var activate = function() { 181 | activateTab(); 182 | activateView(); 183 | submitAjax(); 184 | }; 185 | 186 | // private 187 | 188 | var activateTab = function() { 189 | $tab.parent('li').addClass('active').siblings().removeClass('active'); 190 | }; 191 | 192 | var activateView = function() { 193 | $tab.closest('.jstEditor').removeClass('write preview').addClass(type); 194 | }; 195 | 196 | var submitAjax = function() { 197 | if (type === 'preview') { 198 | var $preview = $tab.closest('.jstEditor').find('.wiki-preview'); 199 | Ajax.init($preview).send(); 200 | } 201 | }; 202 | 203 | return { 204 | init: init, 205 | activate: activate 206 | }; 207 | })(RedmineWikiTabPreview.Ajax); 208 | 209 | /** 210 | * @class TabEvents 211 | * @desc Binds Tab Events 212 | */ 213 | RedmineWikiTabPreview.TabEvents = (function(Tab) { 214 | var $editor; 215 | 216 | var init = function(editor) { 217 | $editor = editor; 218 | initEvents(); 219 | return this; 220 | }; 221 | 222 | // private 223 | 224 | var initEvents = function() { 225 | $editor.on('click', '.jstEditor-preview-header a', function(e) { 226 | e.preventDefault(); 227 | Tab.init($(this)).activate(); 228 | }); 229 | }; 230 | 231 | return { 232 | init: init 233 | }; 234 | })(RedmineWikiTabPreview.Tab); 235 | 236 | /** 237 | * @class EditorEvents 238 | * @desc When editor is focused, attach the preview to the editor 239 | */ 240 | RedmineWikiTabPreview.EditorEvents = (function(Elements, TabEvents) { 241 | var init = function() { 242 | bindExistingEditors(); 243 | bindNewEditors(); 244 | }; 245 | 246 | // private 247 | 248 | var bindExistingEditors = function() { 249 | $('.jstEditor:not(.write,.preview)').each(initPreview); 250 | }; 251 | 252 | var bindNewEditors = function() { 253 | $('#content').on('focus', '.jstEditor:not(.write,.preview)', initPreview); 254 | }; 255 | 256 | var initPreview = function() { 257 | var $this = $(this); 258 | Elements.init($this).draw(); 259 | TabEvents.init($this); 260 | }; 261 | 262 | return { 263 | init: init 264 | }; 265 | })(RedmineWikiTabPreview.Elements, RedmineWikiTabPreview.TabEvents); 266 | 267 | /** 268 | * @class EnsureAjaxCsrf 269 | * @desc Ensure that CSRF token is include with Ajax calls 270 | */ 271 | RedmineWikiTabPreview.EnsureAjaxCsrf = (function() { 272 | var init = function() { 273 | $.ajaxPrefilter(ensureAjaxCsrfPrefilter); 274 | }; 275 | 276 | // private 277 | 278 | var ensureAjaxCsrfPrefilter = function(options, originalOptions, jqXHR) { 279 | if (!options.crossDomain) { 280 | return setRequestHeader(jqXHR); 281 | } 282 | }; 283 | 284 | var setRequestHeader = function(jqXHR) { 285 | var token = $('meta[name="csrf-token"]').attr('content'); 286 | if (token) { 287 | return jqXHR.setRequestHeader('X-CSRF-Token', token); 288 | } 289 | }; 290 | 291 | return { 292 | init: init 293 | }; 294 | })(); 295 | 296 | /** 297 | * @class Style 298 | * @desc Build and write stylesheet with CSS to the page 299 | * @methods add() 300 | * @methods write() 301 | */ 302 | RedmineWikiTabPreview.Style = (function() { 303 | var style = document.createElement('style'); 304 | style.type = 'text/css'; 305 | 306 | return { 307 | add: function(css) { 308 | style.innerHTML += css; 309 | return this; 310 | }, 311 | 312 | write: function() { 313 | var head = document.getElementsByTagName('head')[0]; 314 | head.appendChild(style); 315 | return this; 316 | } 317 | }; 318 | })(); 319 | 320 | $(function() { 321 | 322 | var $preview_links = $('a[accesskey=r]'); 323 | if($preview_links.length > 0){ 324 | // Set styles 325 | var redmineBackgroundColor = $('#header').css('background-color'); 326 | RedmineWikiTabPreview.Style 327 | .add('.jstEditor-preview-header ul li.active a {border-bottom-color: ' + redmineBackgroundColor + ';} ') 328 | .write(); 329 | 330 | RedmineWikiTabPreview.EnsureAjaxCsrf.init(); 331 | RedmineWikiTabPreview.EditorEvents.init(); 332 | } 333 | 334 | }); 335 | -------------------------------------------------------------------------------- /assets/stylesheets/redmine_editor_preview_tab.css: -------------------------------------------------------------------------------- 1 | .wiki-edit { clear:both; float: left; } 2 | 3 | .wiki-preview { 4 | clear:both; 5 | float: left; 6 | display: none; 7 | word-wrap: break-word; 8 | background: #FFF; 9 | border: 1px solid #ddd; 10 | min-height: 136px; 11 | padding: 5px; 12 | box-shadow: none; 13 | width: 99% 14 | } 15 | .wiki-preview p { 16 | margin: 12px 0; 17 | padding: 0 18 | } 19 | 20 | .jstEditor.preview .wiki-edit { display: none; } 21 | .jstEditor.preview .wiki-preview { display: inline; } 22 | 23 | .jstEditor-preview-header { display: inline-block; width: 100%; } 24 | .jstEditor-preview-header ul { 25 | list-style: none; 26 | padding: 0; 27 | margin: 0; 28 | } 29 | .jstEditor-preview-header ul li { float: left; } 30 | .jstEditor-preview-header ul li a { 31 | display: inline-block; 32 | padding: 10px; 33 | font-size: 12px; 34 | line-height: 10px; 35 | } 36 | .jstEditor-preview-header ul li a, 37 | .jstEditor-preview-header ul li a:visited { 38 | color: grey; 39 | } 40 | 41 | .jstEditor-preview-header ul li a:hover { text-decoration: none; } 42 | .jstEditor-preview-header ul li.active a { 43 | color: black; 44 | border-bottom: 4px solid #628DB6; 45 | } -------------------------------------------------------------------------------- /assets/stylesheets/redmine_editor_preview_tab_hide.css: -------------------------------------------------------------------------------- 1 | fieldset.preview, 2 | a[accesskey=r] { 3 | display:none; 4 | } -------------------------------------------------------------------------------- /config/locales/de.yml: -------------------------------------------------------------------------------- 1 | # German strings go here for Rails i18n 2 | de: 3 | label_write: Schreiben 4 | label_no_preview: Kein Inhalt für Vorschau vorhanden... 5 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # English strings go here for Rails i18n 2 | en: 3 | label_write: Write 4 | label_no_preview: Nothing to preview -------------------------------------------------------------------------------- /config/locales/fr.yml: -------------------------------------------------------------------------------- 1 | # French strings go here for Rails i18n 2 | fr: 3 | label_write: Édition 4 | label_no_preview: Rien à prévisualiser 5 | -------------------------------------------------------------------------------- /config/locales/ja.yml: -------------------------------------------------------------------------------- 1 | # Japanease strings go here for Rails i18n 2 | ja: 3 | label_write: 編集 4 | label_no_preview: プレビューなし -------------------------------------------------------------------------------- /config/locales/pt-BR.yml: -------------------------------------------------------------------------------- 1 | # Brazilian Portuguese strings go here for Rails i18n 2 | pt-BR: 3 | label_write: Escrever 4 | label_no_preview: Nenhum conteúdo para pré-visualizar -------------------------------------------------------------------------------- /config/locales/ru.yml: -------------------------------------------------------------------------------- 1 | # Russian strings go here for Rails i18n 2 | ru: 3 | label_write: Редактирование 4 | label_no_preview: Нет данных для предпросмотра 5 | -------------------------------------------------------------------------------- /docs/screenshot_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tleish/redmine_editor_preview_tab/4bc900ae18b4b91fd408002794bbaac2fb3deee9/docs/screenshot_preview.png -------------------------------------------------------------------------------- /docs/screenshot_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tleish/redmine_editor_preview_tab/4bc900ae18b4b91fd408002794bbaac2fb3deee9/docs/screenshot_settings.png -------------------------------------------------------------------------------- /docs/screenshot_write.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tleish/redmine_editor_preview_tab/4bc900ae18b4b91fd408002794bbaac2fb3deee9/docs/screenshot_write.png -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require_dependency 'redmine_editor_preview_tab' 3 | 4 | Redmine::Plugin.register :redmine_editor_preview_tab do 5 | name 'Redmine Editor Preview Tab Extension' 6 | author 'Thomas Leishman' 7 | description 'The Redmine Editor Preview Tab Extension adds a preview tab to the Redmine text editor, similar to the editor on Github.' 8 | version '0.1.5' 9 | url 'https://github.com/tleish/redmine_editor_preview_tab' 10 | author_url 'https://github.com/tleish' 11 | settings :default => { hide_default_preview: false, enabled: true } , :partial => 'redmine_editor_preview_tab/settings' 12 | end 13 | -------------------------------------------------------------------------------- /lib/redmine_editor_preview_tab.rb: -------------------------------------------------------------------------------- 1 | class RedmineEditorPreviewTabHookListener < Redmine::Hook::ViewListener 2 | render_on :view_layouts_base_html_head, :partial => "redmine_editor_preview_tab/redmine_editor_preview_tab_partial" 3 | end --------------------------------------------------------------------------------