├── src ├── templates │ ├── panels │ │ ├── history.html │ │ ├── notes.html │ │ └── snippets.html │ ├── snippets │ │ ├── example.html.erb │ │ └── example_options.html.erb │ ├── selects │ │ ├── style.html │ │ └── formatblock.html │ ├── modals │ │ ├── htmleditor.html │ │ ├── sanitizer.html │ │ ├── link.html │ │ ├── table.html │ │ └── media.html │ └── palettes │ │ ├── backcolor.html │ │ └── forecolor.html ├── images │ ├── button.png │ ├── clippy.png │ ├── loading-dark.gif │ ├── loading-light.gif │ ├── search-icon.png │ ├── default-snippet.png │ └── toolbar │ │ ├── primary │ │ ├── redo.png │ │ ├── save.png │ │ ├── undo.png │ │ ├── preview.png │ │ ├── _expander.png │ │ ├── _pressed.png │ │ ├── historypanel.png │ │ ├── insertlink.png │ │ ├── insertmedia.png │ │ ├── inserttable.png │ │ ├── notespanel.png │ │ ├── snippetpanel.png │ │ ├── inspectorpanel.png │ │ └── insertcharacter.png │ │ ├── editable │ │ └── buttons.png │ │ └── snippetable │ │ └── buttons.png ├── demo │ ├── out │ │ ├── script.js │ │ ├── style.css │ │ └── index.html │ └── src │ │ ├── style.less │ │ ├── script.coffee │ │ └── index.html ├── scripts │ ├── loaded.coffee │ ├── dialogs │ │ ├── formatblock.coffee │ │ ├── style.coffee │ │ ├── backcolor.coffee │ │ ├── forecolor.coffee │ │ └── objectspanel.coffee │ ├── modals │ │ ├── insertcharacter.coffee │ │ ├── htmleditor.coffee │ │ ├── insertsnippet.coffee │ │ ├── inserttable.coffee │ │ ├── insertmedia.coffee │ │ └── insertlink.coffee │ ├── statusbar.coffee │ ├── history_buffer.coffee │ ├── palette.coffee │ ├── toolbar.button_group.coffee │ ├── native_extensions.coffee │ ├── select.coffee │ ├── tooltip.coffee │ ├── regions │ │ ├── plain.coffee │ │ ├── rich.coffee │ │ ├── snippetable.coffee │ │ └── markupable.coffee │ ├── toolbar.expander.coffee │ ├── dialog.coffee │ ├── snippet_toolbar.coffee │ ├── editors │ │ ├── inline.coffee │ │ └── iframe.coffee │ ├── jquery_extensions.coffee │ ├── snippet.coffee │ ├── toolbar.coffee │ ├── editor.coffee │ ├── panel.coffee │ ├── region.coffee │ ├── toolbar.button.coffee │ ├── modal.coffee │ ├── uploader.coffee │ ├── table_editor.coffee │ └── mercury.coffee ├── styles │ ├── statusbar.less │ ├── tooltip.less │ ├── mercury.less │ ├── uploader.less │ ├── dialog.less │ ├── modal.less │ └── toolbar.less ├── deps │ ├── liquidmetal.js │ └── jquery.additions.js └── dev.js ├── server.coffee ├── .gitignore ├── DEPS.md ├── LICENSE.md ├── package.json └── README.md /src/templates/panels/history.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/panels/notes.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server.coffee: -------------------------------------------------------------------------------- 1 | # Requires 2 | require 'simple-server' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | db/*.sqlite3 3 | log/*.log 4 | tmp/ 5 | .sass-cache 6 | public/system 7 | pkg/ -------------------------------------------------------------------------------- /src/images/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/button.png -------------------------------------------------------------------------------- /src/images/clippy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/clippy.png -------------------------------------------------------------------------------- /src/images/loading-dark.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/loading-dark.gif -------------------------------------------------------------------------------- /src/images/loading-light.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/loading-light.gif -------------------------------------------------------------------------------- /src/images/search-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/search-icon.png -------------------------------------------------------------------------------- /src/images/default-snippet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/default-snippet.png -------------------------------------------------------------------------------- /src/demo/out/script.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | return $('body').bind('mercury-ready', function() { 3 | // blah 4 | }); 5 | }); -------------------------------------------------------------------------------- /src/images/toolbar/primary/redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/primary/redo.png -------------------------------------------------------------------------------- /src/images/toolbar/primary/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/primary/save.png -------------------------------------------------------------------------------- /src/images/toolbar/primary/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/primary/undo.png -------------------------------------------------------------------------------- /src/images/toolbar/primary/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/primary/preview.png -------------------------------------------------------------------------------- /src/images/toolbar/editable/buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/editable/buttons.png -------------------------------------------------------------------------------- /src/images/toolbar/primary/_expander.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/primary/_expander.png -------------------------------------------------------------------------------- /src/images/toolbar/primary/_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/primary/_pressed.png -------------------------------------------------------------------------------- /src/templates/snippets/example.html.erb: -------------------------------------------------------------------------------- 1 | <%= params[:options][:first_name] %> likes<%= params[:options][:favorite_beer] %> -------------------------------------------------------------------------------- /src/images/toolbar/primary/historypanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/primary/historypanel.png -------------------------------------------------------------------------------- /src/images/toolbar/primary/insertlink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/primary/insertlink.png -------------------------------------------------------------------------------- /src/images/toolbar/primary/insertmedia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/primary/insertmedia.png -------------------------------------------------------------------------------- /src/images/toolbar/primary/inserttable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/primary/inserttable.png -------------------------------------------------------------------------------- /src/images/toolbar/primary/notespanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/primary/notespanel.png -------------------------------------------------------------------------------- /src/images/toolbar/primary/snippetpanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/primary/snippetpanel.png -------------------------------------------------------------------------------- /src/images/toolbar/snippetable/buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/snippetable/buttons.png -------------------------------------------------------------------------------- /src/images/toolbar/primary/inspectorpanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/primary/inspectorpanel.png -------------------------------------------------------------------------------- /src/images/toolbar/primary/insertcharacter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevry-archive/mercury/HEAD/src/images/toolbar/primary/insertcharacter.png -------------------------------------------------------------------------------- /src/scripts/loaded.coffee: -------------------------------------------------------------------------------- 1 | @Mercury.loaded = true 2 | $ -> 3 | if window.Mercury.config.editor is 'iframe' 4 | new window.Mercury.IframeEditor() 5 | else 6 | new window.Mercury.InlineEditor() -------------------------------------------------------------------------------- /src/scripts/dialogs/formatblock.coffee: -------------------------------------------------------------------------------- 1 | @Mercury.dialogHandlers.formatblock = -> 2 | @element.find('[data-tag]').click (event) => 3 | tag = jQuery(event.target).data('tag') 4 | Mercury.trigger('action', {action: 'formatblock', value: tag}) 5 | -------------------------------------------------------------------------------- /src/scripts/dialogs/style.coffee: -------------------------------------------------------------------------------- 1 | @Mercury.dialogHandlers.style = -> 2 | @element.find('[data-class]').click (event) => 3 | className = jQuery(event.target).data('class') 4 | Mercury.trigger('action', {action: 'style', value: className}) 5 | -------------------------------------------------------------------------------- /src/scripts/modals/insertcharacter.coffee: -------------------------------------------------------------------------------- 1 | @Mercury.modalHandlers.insertCharacter = -> 2 | @element.find('.character').click -> 3 | Mercury.trigger('action', {action: 'insertHTML', value: "&#{jQuery(@).attr('data-entity')};"}) 4 | Mercury.modal.hide() 5 | -------------------------------------------------------------------------------- /src/templates/selects/style.html: -------------------------------------------------------------------------------- 1 |
2 |
Red text
3 |
Large bold text
4 |
Blue background
5 |
6 | -------------------------------------------------------------------------------- /src/scripts/dialogs/backcolor.coffee: -------------------------------------------------------------------------------- 1 | @Mercury.dialogHandlers.backColor = -> 2 | # needs to be mousedown 3 | @element.find('.picker, .last-picked').mousedown (event) => 4 | color = jQuery(event.target).css('background-color') 5 | @element.find('.last-picked').css({background: color}) 6 | @button.css({backgroundColor: color}) 7 | Mercury.trigger('action', {action: 'backColor', value: color}) 8 | -------------------------------------------------------------------------------- /src/scripts/dialogs/forecolor.coffee: -------------------------------------------------------------------------------- 1 | @Mercury.dialogHandlers.foreColor = -> 2 | # needs to be mousedown 3 | @element.find('.picker, .last-picked').mousedown (event) => 4 | color = jQuery(event.target).css('background-color') 5 | @element.find('.last-picked').css({background: color}) 6 | @button.css({backgroundColor: color}) 7 | Mercury.trigger('action', {action: 'foreColor', value: color}) 8 | -------------------------------------------------------------------------------- /DEPS.md: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | 3 | ## jQuery 1.6.1+ 4 | 5 | ## jQuery UI 1.8+ 6 | 7 | - UI Core 8 | - Core 9 | - Widget 10 | - Mouse 11 | - Position 12 | - Interactions 13 | - Draggable 14 | - Resizable 15 | - Sortable 16 | 17 | Only javascript required, no CSS. 18 | 19 | ## jQuery Additions 20 | 21 | - serializeObject 22 | - Easing 1.3 23 | - JSON 2.1 24 | 25 | ## Liquid Metal 26 | 27 | ## Showdown 28 | 29 | -------------------------------------------------------------------------------- /src/templates/modals/htmleditor.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 |
7 |
    8 |
  1. 9 |
10 |
11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /src/scripts/modals/htmleditor.coffee: -------------------------------------------------------------------------------- 1 | @Mercury.modalHandlers.htmlEditor = -> 2 | # fill the text area with the content 3 | @element.find('textarea').val(Mercury.region.content(null, true, false)) 4 | 5 | # replace the contents on form submit 6 | @element.find('form').submit (event) => 7 | event.preventDefault() 8 | value = @element.find('textarea').val().replace(/\n/g, '') 9 | Mercury.trigger('action', {action: 'replaceHTML', value: value}) 10 | @hide() 11 | -------------------------------------------------------------------------------- /src/templates/panels/snippets.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 12 |
13 | -------------------------------------------------------------------------------- /src/templates/modals/sanitizer.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
It looks like you've pasted directly from Microsoft Office!

Sadly though, your browser wasn't able to properly handle what you pasted. But that's ok, because I've sanitized it for you!
4 | 5 | 6 | 7 | You can just copy and paste the sanitized content that appears above. 8 | 9 |
10 | -------------------------------------------------------------------------------- /src/scripts/modals/insertsnippet.coffee: -------------------------------------------------------------------------------- 1 | @Mercury.modalHandlers.insertSnippet = -> 2 | @element.find('form').submit (event) => 3 | event.preventDefault() 4 | serializedForm = @element.find('form').serializeObject() 5 | if Mercury.snippet 6 | snippet = Mercury.snippet 7 | snippet.setOptions(serializedForm) 8 | Mercury.snippet = null 9 | else 10 | snippet = Mercury.Snippet.create(@options.snippetName, serializedForm) 11 | Mercury.trigger('action', {action: 'insertSnippet', value: snippet}) 12 | @hide() 13 | -------------------------------------------------------------------------------- /src/templates/selects/formatblock.html: -------------------------------------------------------------------------------- 1 |
2 |

Heading 1 <h1>

3 |

Heading 2 <h2>

4 |

Heading 3 <h3>

5 |

Heading 4 <h4>

6 |
Heading 5 <h5>
7 |
Heading 6 <h6>
8 |
Paragraph <p>
9 |
Blockquote <blockquote>
10 |
Formatted <pre>
11 |
12 | -------------------------------------------------------------------------------- /src/scripts/dialogs/objectspanel.coffee: -------------------------------------------------------------------------------- 1 | @Mercury.dialogHandlers.snippetPanel = -> 2 | # make the filter work 3 | @element.find('input.filter').keyup => 4 | value = @element.find('input.filter').val() 5 | for snippet in @element.find('li[data-filter]') 6 | if LiquidMetal.score(jQuery(snippet).data('filter'), value) == 0 then jQuery(snippet).hide() else jQuery(snippet).show() 7 | 8 | # when an element is dragged, set it so we have a global object 9 | @element.find('img[data-snippet]').bind 'dragstart', (event) -> 10 | Mercury.snippet = jQuery(@).data('snippet') 11 | -------------------------------------------------------------------------------- /src/styles/statusbar.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Statusbar 3 | *----------------------------------------------------------------------------*/ 4 | .mercury-statusbar { 5 | position: fixed; 6 | -webkit-user-select: none; 7 | -moz-user-select: none; 8 | user-select: none; 9 | box-sizing: border-box; 10 | -moz-box-sizing: border-box; 11 | -webkit-box-sizing: border-box; 12 | z-index: 10020; 13 | bottom: 0; 14 | width: 100%; 15 | height: 23px; 16 | cursor: default; 17 | padding: 0 10px; 18 | background: #E2E1E2; 19 | border-top: 1px solid #727272; 20 | font-family: Helvetica, Tahoma, Arial, sans-serif; 21 | font-size: 8.5pt; 22 | line-height: 23px; 23 | color: #222; 24 | } 25 | -------------------------------------------------------------------------------- /src/styles/tooltip.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Tooltip 3 | *----------------------------------------------------------------------------*/ 4 | .mercury-tooltip { 5 | position: absolute; 6 | top: 100px; 7 | left: 100px; 8 | max-width: 400px; 9 | overflow: hidden; 10 | background: rgba(0, 0, 0, .70); 11 | padding: 2px 5px; 12 | border-radius: 2px; 13 | -moz-border-radius: 2px; 14 | box-shadow: 0 0 5px rgba(0, 0, 0, .3); 15 | -moz-box-shadow: 0 0 5px rgba(0, 0, 0, .3); 16 | font: normal normal normal 11px/normal Helvetica, Tahoma, Arial, sans-serif; 17 | color: #EEE; 18 | word-wrap: break-word; 19 | a { 20 | color: #EEE; 21 | text-decoration: none; 22 | &:hover { 23 | color: #FFF; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/scripts/statusbar.coffee: -------------------------------------------------------------------------------- 1 | class @Mercury.Statusbar 2 | 3 | constructor: (@options = {}) -> 4 | @build() 5 | @bindEvents() 6 | 7 | 8 | build: -> 9 | @element = jQuery('
', {class: 'mercury-statusbar'}).appendTo(jQuery(@options.appendTo).get(0) ? 'body') 10 | 11 | 12 | bindEvents: -> 13 | Mercury.bind 'region:update', (event, options) => 14 | @setPath(options.region.path()) if options.region && jQuery.type(options.region.path) == 'function' 15 | 16 | 17 | height: -> 18 | return @element.outerHeight() 19 | 20 | 21 | top: -> 22 | return @element.offset().top 23 | 24 | 25 | setPath: (elements) -> 26 | path = [] 27 | path.push("#{element.tagName.toLowerCase()}") for element in elements 28 | 29 | @element.html("Path: #{path.reverse().join(' » ')}") 30 | -------------------------------------------------------------------------------- /src/scripts/history_buffer.coffee: -------------------------------------------------------------------------------- 1 | class @Mercury.HistoryBuffer 2 | 3 | constructor: (@maxLength = 200) -> 4 | @index = 0 5 | @stack = [] 6 | @markerRegExp = /<\/em>/g 7 | 8 | 9 | push: (item) -> 10 | if jQuery.type(item) == 'string' 11 | return if @stack[@index] && @stack[@index].replace(@markerRegExp, '') == item.replace(@markerRegExp, '') 12 | else if jQuery.type(item) == 'object' && item.html 13 | return if @stack[@index] && @stack[@index].html == item.html 14 | 15 | @stack = @stack[0...@index + 1] 16 | @stack.push(item) 17 | @stack.shift() if @stack.length > @maxLength 18 | @index = @stack.length - 1 19 | 20 | 21 | undo: -> 22 | return null if @index < 1 23 | @index -= 1 24 | return @stack[@index] 25 | 26 | 27 | redo: -> 28 | return null if @index >= @stack.length - 1 29 | @index += 1 30 | return @stack[@index] 31 | -------------------------------------------------------------------------------- /src/templates/snippets/example_options.html.erb: -------------------------------------------------------------------------------- 1 | <% @options = params[:options] || {} %> 2 | <%= semantic_form_for 'options', { :html => { :style => 'width:600px' } } do |f| %> 3 | 4 |
5 |
6 | <%= f.inputs do %> 7 | <%= f.input :first_name, :required => false, :label => 'First Name', 8 | :input_html => {:value => @options[:first_name] || 'First Name'} %> 9 | <% end %> 10 | <%= f.inputs 'Options' do %> 11 | <%= f.input :favorite_beer, :required => false, :label => 'Favorite Beer', 12 | :input_html => {:value => @options[:favorite_beer] || 'PBR'} %> 13 | <% end %> 14 |
15 |
16 | 17 |
18 | <%= f.buttons do %> 19 | <%= f.commit_button 'Insert Snippet' %> 20 | <% end %> 21 |
22 | 23 | <% end %> 24 | -------------------------------------------------------------------------------- /src/scripts/palette.coffee: -------------------------------------------------------------------------------- 1 | class @Mercury.Palette extends Mercury.Dialog 2 | 3 | constructor: (@url, @name, @options = {}) -> 4 | super 5 | 6 | 7 | build: -> 8 | @element = jQuery('
', {class: "mercury-palette mercury-#{@name}-palette loading", style: 'display:none'}) 9 | @element.appendTo(jQuery(@options.appendTo).get(0) ? 'body') 10 | 11 | 12 | bindEvents: -> 13 | Mercury.bind 'hide:dialogs', (event, dialog) => 14 | @hide() unless dialog == @ 15 | super 16 | 17 | 18 | position: (keepVisible) -> 19 | @element.css({top: 0, left: 0, display: 'block', visibility: 'hidden'}) 20 | position = @button.offset() 21 | width = @element.width() 22 | 23 | position.left = position.left - width + @button.width() if position.left + width > jQuery(window).width() 24 | 25 | @element.css { 26 | top: position.top + @button.height() 27 | left: position.left 28 | display: if keepVisible then 'block' else 'none' 29 | visibility: 'visible' 30 | } 31 | -------------------------------------------------------------------------------- /src/demo/out/style.css: -------------------------------------------------------------------------------- 1 | body, td { 2 | font: normal normal normal 100%/normal 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | } 4 | h1 { 5 | color: #09F; 6 | margin: 20px 0 0 0; 7 | font-weight: 200; 8 | } 9 | h2 { 10 | font-weight: 200; 11 | margin: 15px 0 0 0; 12 | border-bottom: 1px solid #CCC; 13 | } 14 | #content { 15 | min-width: 500px; 16 | width: 70%; 17 | margin: 20px auto; 18 | } 19 | #controls { 20 | position: absolute; 21 | top: 20px; 22 | left: 20px; 23 | padding: 10px; 24 | margin: 0; 25 | border: 1px solid #999; 26 | background: rgba(255, 255, 255, 0.8); 27 | border-radius: 3px; 28 | -moz-border-radius: 3px; 29 | -webkit-border-radius: 3px; 30 | list-style-type: none; 31 | } 32 | #controls a { 33 | color: #333; 34 | text-transform: lowercase; 35 | text-decoration: none; 36 | } 37 | .mercury-snippet { 38 | background: rgba(0, 0, 0, 0.5); 39 | color: #FFF; 40 | width: 300px; 41 | height: 100px; 42 | margin: 1px 0; 43 | border-radius: 3px; 44 | -moz-border-radius: 3px; 45 | -webkit-border-radius: 3px; 46 | } 47 | #snippetable1 { 48 | float: left; 49 | width: 100%; 50 | margin-bottom: 20px; 51 | } 52 | #snippetable1 .mercury-snippet { 53 | float: left; 54 | width: 33%; 55 | border: 1px solid #000; 56 | } -------------------------------------------------------------------------------- /src/demo/src/style.less: -------------------------------------------------------------------------------- 1 | body, td { 2 | font: normal normal normal 100%/normal 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | } 4 | h1 { 5 | color: #09F; 6 | margin: 20px 0 0 0; 7 | font-weight: 200; 8 | } 9 | h2 { 10 | font-weight: 200; 11 | margin: 15px 0 0 0; 12 | border-bottom: 1px solid #CCC; 13 | } 14 | #content { 15 | min-width: 500px; 16 | width: 70%; 17 | margin: 20px auto; 18 | } 19 | #controls { 20 | position: absolute; 21 | top: 20px; 22 | left: 20px; 23 | padding: 10px; 24 | margin: 0; 25 | border: 1px solid #999; 26 | background: rgba(255, 255, 255, 0.8); 27 | border-radius: 3px; 28 | -moz-border-radius: 3px; 29 | -webkit-border-radius: 3px; 30 | list-style-type: none; 31 | } 32 | #controls a { 33 | color: #333; 34 | text-transform: lowercase; 35 | text-decoration: none; 36 | } 37 | .mercury-snippet { 38 | background: rgba(0, 0, 0, 0.5); 39 | color: #FFF; 40 | width: 300px; 41 | height: 100px; 42 | margin: 1px 0; 43 | border-radius: 3px; 44 | -moz-border-radius: 3px; 45 | -webkit-border-radius: 3px; 46 | } 47 | #snippetable1 { 48 | float: left; 49 | width: 100%; 50 | margin-bottom: 20px; 51 | } 52 | #snippetable1 .mercury-snippet { 53 | float: left; 54 | width: 33%; 55 | border: 1px solid #000; 56 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Mercury Editor is a Coffeescript and jQuery based WYSIWYG editor. 2 | 3 | Documentation and other useful information can be found at 4 | https://github.com/jejacks0n/mercury 5 | 6 | Copyright (c) 2011 Jeremy Jackson 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mercury", 3 | "version": "0.2.0", 4 | "description": "A fully featured HTML5 WYSIWYG editor written in CoffeeScript", 5 | "homepage": "https://github.com/balupton/mercury", 6 | "keywords": [ 7 | "contenteditable", 8 | "wysiwyg", 9 | "wyriwyg", 10 | "wywiwyg" 11 | ], 12 | "author": { 13 | "name": "Jeremy Jackson", 14 | "email": "jeremy@factorylabs.com", 15 | "web": "https://github.com/jejacks0n" 16 | }, 17 | "maintainers": [ 18 | { 19 | "name": "Jeremy Jackson", 20 | "email": "jeremy@factorylabs.com", 21 | "web": "https://github.com/jejacks0n" 22 | }, 23 | { 24 | "name": "Benjamin Lupton", 25 | "email": "b@lupton.cc", 26 | "web": "https://github.com/balupton" 27 | } 28 | ], 29 | "contributors": [ 30 | { 31 | "name": "Benjamin Lupton", 32 | "email": "b@lupton.cc", 33 | "web": "https://github.com/balupton" 34 | } 35 | ], 36 | "bugs": { 37 | "web": "https://github.com/balupton/mercury/issues" 38 | }, 39 | "licenses": [ 40 | { 41 | "type": "MIT", 42 | "url": "http://creativecommons.org/licenses/MIT/" 43 | } 44 | ], 45 | "repository" : { 46 | "type" : "git", 47 | "url" : "http://github.com/balupton/mercury.git" 48 | }, 49 | "dependencies": { 50 | "coffee-script": "=1.1.1", 51 | "buildr": "=0.6.4", 52 | "simple-server": "=0.4.0" 53 | }, 54 | "engines" : { 55 | "node": ">=0.4.0" 56 | }, 57 | "directories": { 58 | "lib": "src" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/scripts/toolbar.button_group.coffee: -------------------------------------------------------------------------------- 1 | class @Mercury.Toolbar.ButtonGroup 2 | 3 | constructor: (@name, @options = {}) -> 4 | @build() 5 | @bindEvents() 6 | @regions = @options._regions 7 | return @element 8 | 9 | 10 | build: -> 11 | @element = jQuery('
', {class: "mercury-button-group mercury-#{@name}-group"}) 12 | if @options._context || @options._regions 13 | @element.addClass('disabled') 14 | 15 | 16 | bindEvents: -> 17 | Mercury.bind 'region:update', (event, options) => 18 | context = Mercury.Toolbar.ButtonGroup.contexts[@name] 19 | if context 20 | if options.region && jQuery.type(options.region.currentElement) == 'function' 21 | element = options.region.currentElement() 22 | if element.length && context.call(@, element, options.region.element) 23 | @element.removeClass('disabled') 24 | else 25 | @element.addClass('disabled') 26 | 27 | Mercury.bind 'region:focused', (event, options) => 28 | if @regions && options.region && options.region.type 29 | if @regions.indexOf(options.region.type) > -1 30 | @element.removeClass('disabled') unless @options._context 31 | else 32 | @element.addClass('disabled') 33 | 34 | Mercury.bind 'region:blurred', (event, options) => 35 | @element.addClass('disabled') if @options.regions 36 | 37 | 38 | 39 | # ButtonGroup contexts 40 | @Mercury.Toolbar.ButtonGroup.contexts = 41 | 42 | table: (node, region) -> !!node.closest('table', region).length 43 | -------------------------------------------------------------------------------- /src/scripts/native_extensions.coffee: -------------------------------------------------------------------------------- 1 | String::titleize = -> 2 | @[0].toUpperCase() + @slice(1) 3 | 4 | 5 | String::toHex = -> 6 | # todo: we should handle alpha as well 7 | return @ if @[0] == '#' 8 | @replace /rgba?\((\d+)[\s|\,]?\s(\d+)[\s|\,]?\s(\d+)\)/gi, (a, r, g, b) -> 9 | "##{parseInt(r).toHex()}#{parseInt(g).toHex()}#{parseInt(b).toHex()}" 10 | 11 | 12 | String::singleDiff = (that) -> 13 | diff = '' 14 | for char, index in that 15 | break if char == 'each' 16 | if char != @[index] 17 | re = new RegExp(@substr(index).regExpEscape().replace(/^\s+|^( )+/g, '') + '$', 'm') 18 | diff = that.substr(index).replace(re, '') 19 | break 20 | return diff 21 | 22 | 23 | String::regExpEscape = -> 24 | specials = ['/','.','*','+','?','|','(',')','[',']','{','}','\\'] 25 | escaped = new RegExp('(\\' + specials.join('|\\') + ')', 'g') 26 | return @replace(escaped, '\\$1') 27 | 28 | 29 | String::sanitizeHTML = -> 30 | element = jQuery('
').html(@.toString()) 31 | element.find('style').remove() 32 | content = element.text() 33 | content = content.replace(/\n+/g, '
').replace(/.*/g, '').replace(/^()+|(\s*)+$/g, '') 34 | return content 35 | 36 | 37 | Number::toHex = -> 38 | result = @toString(16).toUpperCase() 39 | return if result[1] then result else "0#{result}" 40 | 41 | 42 | Number::toBytes = -> 43 | bytes = parseInt(@) 44 | i = 0 45 | while 1023 < bytes 46 | bytes /= 1024 47 | i += 1 48 | return if i then "#{bytes.toFixed(2)}#{['', ' kb', ' Mb', ' Gb', ' Tb', ' Pb', ' Eb'][i]}" else "#{bytes} bytes" 49 | -------------------------------------------------------------------------------- /src/scripts/select.coffee: -------------------------------------------------------------------------------- 1 | class @Mercury.Select extends Mercury.Dialog 2 | 3 | constructor: (@url, @name, @options = {}) -> 4 | super 5 | 6 | 7 | build: -> 8 | @element = jQuery('
', {class: "mercury-select mercury-#{@name}-select loading", style: 'display:none'}) 9 | @element.appendTo(jQuery(@options.appendTo).get(0) ? 'body') 10 | 11 | 12 | bindEvents: -> 13 | Mercury.bind 'hide:dialogs', (event, dialog) => @hide() unless dialog == @ 14 | 15 | @element.mousedown (event) => event.preventDefault() 16 | 17 | super 18 | 19 | 20 | position: (keepVisible) -> 21 | @element.css({top: 0, left: 0, display: 'block', visibility: 'hidden'}) 22 | position = @button.offset() 23 | elementWidth = @element.width() 24 | elementHeight = @element.height() 25 | documentHeight = jQuery(document).height() 26 | 27 | # Inline 28 | if Mercury.displayRect.height is Mercury.displayRect.fullHeight 29 | top = position.top + @button.height() - jQuery(document).scrollTop() 30 | # Iframe 31 | else 32 | top = position.top + (@button.height() / 2) - (elementHeight / 2) 33 | top = position.top - 100 if top < position.top - 100 34 | top = 20 if top < 20 35 | 36 | height = if @loaded then 'auto' else elementHeight 37 | height = documentHeight - top - 20 if top + elementHeight >= documentHeight - 20 38 | 39 | left = position.left 40 | left = left - elementWidth + @button.width() if left + elementWidth > jQuery(window).width() 41 | 42 | @element.css { 43 | top: top 44 | left: left 45 | height: height 46 | display: if keepVisible then 'block' else 'none' 47 | visibility: 'visible' 48 | } 49 | -------------------------------------------------------------------------------- /src/scripts/tooltip.coffee: -------------------------------------------------------------------------------- 1 | @Mercury.tooltip = (forElement, content, options = {}) -> 2 | Mercury.tooltip.show(forElement, content, options) 3 | return Mercury.tooltip 4 | 5 | jQuery.extend Mercury.tooltip, { 6 | 7 | show: (@forElement, @content, @options = {}) -> 8 | @document = jQuery(@forElement.get(0).ownerDocument) 9 | @initialize() 10 | if @visible then @update() else @appear() 11 | 12 | 13 | initialize: -> 14 | return if @initialized 15 | @build() 16 | @bindEvents() 17 | @initialized = true 18 | 19 | 20 | build: -> 21 | @element = jQuery('
', {class: 'mercury-tooltip'}) 22 | @element.appendTo(jQuery(@options.appendTo).get(0) ? 'body') 23 | 24 | 25 | bindEvents: -> 26 | Mercury.bind 'resize', => @position() if @visible 27 | @document.scroll => @position() if @visible 28 | @element.mousedown (event) -> 29 | event.preventDefault() 30 | event.stopPropagation() 31 | 32 | 33 | appear: -> 34 | @update() 35 | 36 | @element.show() 37 | @element.animate {opacity: 1}, 200, 'easeInOutSine', => 38 | @visible = true 39 | 40 | 41 | update: -> 42 | @element.html(@content) 43 | @position() 44 | 45 | 46 | position: -> 47 | offset = @forElement.offset() 48 | width = @element.width() 49 | 50 | top = offset.top + @forElement.outerHeight() 51 | left = offset.left - jQuery(@document).scrollLeft() 52 | 53 | # Iframe 54 | unless Mercury.displayRect.height is Mercury.displayRect.fullHeight 55 | top += Mercury.displayRect.top - jQuery(@document).scrollTop() 56 | 57 | left = left - (left + width + 25) - Mercury.displayRect.width if (left + width + 25) > Mercury.displayRect.width 58 | left = if left <= 0 then 0 else left 59 | 60 | @element.css { 61 | top: top 62 | left: left 63 | } 64 | 65 | 66 | hide: -> 67 | return unless @initialized 68 | @element.hide() 69 | @visible = false 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/scripts/regions/plain.coffee: -------------------------------------------------------------------------------- 1 | class @Mercury.Regions.Plain extends @Mercury.Regions.Basic 2 | type = 'plain' 3 | 4 | constructor: (@element, @window, @options = {}) -> 5 | super 6 | @type = 'plain' 7 | 8 | execCommand: (action, options = {}) -> 9 | console.log action 10 | unless action in ['redo','undo','insertHTML'] 11 | return false 12 | 13 | # use a custom handler if there's one, otherwise use execCommand 14 | if handler = Mercury.config.behaviors[action] || @actions[action] 15 | handler.call(@, @selection(), options) 16 | else 17 | sibling = @element.get(0).previousSibling if action == 'indent' 18 | options.value = jQuery('
').html(options.value).html() if action == 'insertHTML' && options.value && options.value.get 19 | try 20 | @document.execCommand(action, false, options.value) 21 | catch error 22 | # mozilla: indenting when there's no br tag handles strangely 23 | # todo: mozilla: trying to justify the first line of any contentEditable fails 24 | @element.prev().remove() if action == 'indent' && @element.prev() != sibling 25 | 26 | handlePaste: (prePasteContent) -> 27 | prePasteContent = prePasteContent.replace(/^\/, '') 28 | 29 | # remove any regions that might have been pasted 30 | @element.find('.mercury-region').remove() 31 | 32 | # handle pasting from ms office etc 33 | content = @content() 34 | 35 | # strip styles 36 | pasted = prePasteContent.singleDiff(@content()) 37 | 38 | container = jQuery('
').appendTo(@document.createDocumentFragment()).html(pasted) 39 | container.find('[style]').attr({style: null}) 40 | 41 | @document.execCommand('undo', false, null) 42 | @execCommand('insertHTML', {value: container.text()}) 43 | 44 | # Custom actions (eg. things that execCommand doesn't do, or doesn't do well) 45 | actions: { 46 | undo: -> @content(@history.undo()) 47 | redo: -> @content(@history.redo()) 48 | } 49 | -------------------------------------------------------------------------------- /src/scripts/toolbar.expander.coffee: -------------------------------------------------------------------------------- 1 | class @Mercury.Toolbar.Expander extends Mercury.Palette 2 | 3 | constructor: (@name, @options) -> 4 | @container = @options.for 5 | @containerWidth = @container.outerWidth() 6 | super(null, @name, @options) 7 | return @element 8 | 9 | 10 | build: -> 11 | @container.css({whiteSpace: 'normal'}) 12 | @trigger = jQuery('
', {class: 'mercury-toolbar-expander'}).appendTo(jQuery(@options.appendTo).get(0) ? 'body') 13 | @element = jQuery('
', {class: "mercury-palette mercury-expander mercury-#{@name}-expander", style: 'display:none'}) 14 | @windowResize() 15 | 16 | 17 | bindEvents: -> 18 | Mercury.bind 'hide:dialogs', (event, dialog) => @hide() unless dialog == @ 19 | Mercury.bind 'resize', => @windowResize() 20 | 21 | super 22 | 23 | @trigger.click (event) => 24 | event.stopPropagation() 25 | hiddenButtons = [] 26 | for button in @container.find('.mercury-button') 27 | button = jQuery(button) 28 | hiddenButtons.push(button.data('expander')) if button.position().top > 5 29 | 30 | @loadContent(hiddenButtons.join('')) 31 | @toggle() 32 | 33 | @element.click (event) => 34 | buttonName = jQuery(event.target).closest('[data-button]').data('button') 35 | button = @container.find(".mercury-#{buttonName}-button") 36 | button.click() 37 | 38 | 39 | windowResize: -> 40 | if @containerWidth > jQuery(window).width() then @trigger.show() else @trigger.hide() 41 | @hide() 42 | 43 | 44 | position: (keepVisible) -> 45 | @element.css({top: 0, left: 0, display: 'block', visibility: 'hidden'}) 46 | position = @trigger.offset() 47 | width = @element.width() 48 | 49 | position.left = position.left - width + @trigger.width() if position.left + width > jQuery(window).width() 50 | 51 | @element.css { 52 | top: position.top + @trigger.height() 53 | left: position.left 54 | display: if keepVisible then 'block' else 'none' 55 | visibility: 'visible' 56 | } 57 | -------------------------------------------------------------------------------- /src/scripts/dialog.coffee: -------------------------------------------------------------------------------- 1 | class @Mercury.Dialog 2 | 3 | constructor: (@url, @name, @options = {}) -> 4 | @button = @options.for 5 | 6 | @build() 7 | @bindEvents() 8 | @preload() 9 | 10 | 11 | build: -> 12 | @element = jQuery('
', {class: "mercury-dialog mercury-#{@name}-dialog loading", style: 'display:none'}) 13 | @element.appendTo(jQuery(@options.appendTo).get(0) ? 'body') 14 | 15 | 16 | bindEvents: -> 17 | @element.mousedown (event) -> event.stopPropagation() 18 | 19 | 20 | preload: -> 21 | @load() if @options.preload 22 | 23 | 24 | toggle: (element) -> 25 | if @visible then @hide() else @show() 26 | 27 | 28 | resize: -> 29 | @show() 30 | 31 | 32 | show: -> 33 | Mercury.trigger('hide:dialogs', @) 34 | @visible = true 35 | if @loaded 36 | @element.css({width: 'auto', height: 'auto'}) 37 | @position(@visible) 38 | @appear() 39 | else 40 | @position() 41 | @appear() 42 | 43 | 44 | position: (keepVisible) -> 45 | 46 | 47 | appear: -> 48 | @element.css({display: 'block', opacity: 0}) 49 | @element.animate {opacity: 0.95}, 200, 'easeInOutSine', => 50 | @load(=> @resize()) unless @loaded 51 | 52 | 53 | hide: -> 54 | @element.hide() 55 | @visible = false 56 | 57 | 58 | load: (callback) -> 59 | return unless @url 60 | if Mercury.preloadedViews[@url] 61 | @loadContent(Mercury.preloadedViews[@url]) 62 | Mercury.dialogHandlers[@name].call(@) if Mercury.dialogHandlers[@name] 63 | callback() if callback 64 | else 65 | jQuery.ajax @url, { 66 | success: (data) => 67 | @loadContent(data) 68 | Mercury.dialogHandlers[@name].call(@) if Mercury.dialogHandlers[@name] 69 | callback() if callback 70 | error: => 71 | @hide() 72 | @button.removeClass('pressed') if @button 73 | alert("Mercury was unable to load #{@url} for the #{@name} dialog.") 74 | } 75 | 76 | 77 | loadContent: (data) -> 78 | @loaded = true 79 | @element.removeClass('loading') 80 | @element.html(data) 81 | -------------------------------------------------------------------------------- /src/scripts/snippet_toolbar.coffee: -------------------------------------------------------------------------------- 1 | class @Mercury.SnippetToolbar extends Mercury.Toolbar 2 | 3 | constructor: (@document, @options = {}) -> 4 | super(@options) 5 | 6 | 7 | build: -> 8 | @element = jQuery('
', {class: 'mercury-toolbar mercury-snippet-toolbar', style: 'display:none'}) 9 | @element.appendTo(jQuery(@options.appendTo).get(0) ? 'body') 10 | 11 | for own buttonName, options of Mercury.config.toolbars.snippetable 12 | button = @buildButton(buttonName, options) 13 | button.appendTo(@element) if button 14 | 15 | 16 | bindEvents: -> 17 | Mercury.bind 'show:toolbar', (event, options) => 18 | return unless options.snippet 19 | options.snippet.mouseout => @hide() 20 | @show(options.snippet) 21 | 22 | Mercury.bind 'hide:toolbar', (event, options) => 23 | return unless options.type && options.type == 'snippet' 24 | @hide(options.immediately) 25 | 26 | jQuery(@document).scroll => @position() if @visible 27 | 28 | @element.mousemove => clearTimeout(@hideTimeout) 29 | @element.mouseout => @hide() 30 | 31 | 32 | show: (@snippet) -> 33 | Mercury.tooltip.hide() 34 | @position() 35 | @appear() 36 | 37 | 38 | position: -> 39 | offset = @snippet.offset() 40 | 41 | top = offset.top + Mercury.displayRect.top - jQuery(@document).scrollTop() - @height() + 10 42 | left = offset.left - jQuery(@document).scrollLeft() 43 | 44 | @element.css { 45 | top: top 46 | left: left 47 | } 48 | 49 | 50 | appear: -> 51 | clearTimeout(@hideTimeout) 52 | return if @visible 53 | @visible = true 54 | @element.css({display: 'block', opacity: 0}) 55 | @element.stop().animate({opacity: 1}, 200, 'easeInOutSine') 56 | 57 | 58 | hide: (immediately = false) -> 59 | clearTimeout(@hideTimeout) 60 | if immediately 61 | @element.hide() 62 | @visible = false 63 | else 64 | @hideTimeout = setTimeout((=> 65 | @element.stop().animate {opacity: 0}, 300, 'easeInOutSine', => 66 | @element.hide() 67 | @visible = false 68 | ), 500) 69 | 70 | -------------------------------------------------------------------------------- /src/scripts/modals/inserttable.coffee: -------------------------------------------------------------------------------- 1 | @Mercury.modalHandlers.insertTable = -> 2 | table = @element.find('#table_display table') 3 | 4 | # make td's selectable 5 | table.click (event) => 6 | cell = jQuery(event.target) 7 | table = cell.closest('table') 8 | table.find('.selected').removeClass('selected') 9 | cell.addClass('selected') 10 | Mercury.tableEditor(table, cell, ' ') 11 | 12 | # select the first td 13 | firstCell = table.find('td, th').first() 14 | firstCell.addClass('selected') 15 | Mercury.tableEditor(table, firstCell, ' ') 16 | 17 | # make the buttons work 18 | @element.find('input.action').click (event) => 19 | action = jQuery(event.target).attr('name') 20 | switch action 21 | when 'insertRowBefore' then Mercury.tableEditor.addRow('before') 22 | when 'insertRowAfter' then Mercury.tableEditor.addRow('after') 23 | when 'deleteRow' then Mercury.tableEditor.removeRow() 24 | when 'insertColumnBefore' then Mercury.tableEditor.addColumn('before') 25 | when 'insertColumnAfter' then Mercury.tableEditor.addColumn('after') 26 | when 'deleteColumn' then Mercury.tableEditor.removeColumn() 27 | when 'increaseColspan' then Mercury.tableEditor.increaseColspan() 28 | when 'decreaseColspan' then Mercury.tableEditor.decreaseColspan() 29 | when 'increaseRowspan' then Mercury.tableEditor.increaseRowspan() 30 | when 'decreaseRowspan' then Mercury.tableEditor.decreaseRowspan() 31 | 32 | # set the alignment 33 | @element.find('#table_alignment').change => 34 | table.attr({align: @element.find('#table_alignment').val()}) 35 | 36 | # set the border 37 | @element.find('#table_border').keyup => 38 | table.attr({border: parseInt(@element.find('#table_border').val())}) 39 | 40 | # set the cellspacing 41 | @element.find('#table_spacing').keyup => 42 | table.attr({cellspacing: parseInt(@element.find('#table_spacing').val())}) 43 | 44 | # build the table on form submission 45 | @element.find('form').submit (event) => 46 | event.preventDefault() 47 | table.find('.selected').removeClass('selected') 48 | table.find('td, th').html(' ') 49 | 50 | html = jQuery('
').html(table).html() 51 | value = html.replace(/^\s+|\n/gm, '').replace(/(<\/.*?>|||)/g, '$1\n') 52 | 53 | Mercury.trigger('action', {action: 'insertHTML', value: value}) 54 | @hide() 55 | 56 | -------------------------------------------------------------------------------- /src/scripts/editors/inline.coffee: -------------------------------------------------------------------------------- 1 | class @Mercury.InlineEditor extends @Mercury.Editor 2 | 3 | # options 4 | # saveStyle: 'form', or 'json' (defaults to json) 5 | # ignoredLinks: an array containing classes for links to ignore (eg. lightbox or accordian controls) 6 | constructor: (@saveUrl = null, @options = {}) -> 7 | super 8 | 9 | throw "Mercury.PageEditor is unsupported in this client. Supported browsers are chrome 10+, firefix 4+, and safari 5+." unless Mercury.supported 10 | throw "Mercury.PageEditor can only be instantiated once." if window.mercuryInstance 11 | 12 | window.mercuryInstance = @ 13 | @regions = [] 14 | @initializeInterface() 15 | Mercury.csrfToken = token if token = $('meta[name="csrf-token"]').attr('content') 16 | 17 | $ -> 18 | $('body').createPromiseEvent('mercury-ready').trigger('mercury-ready') 19 | 20 | initializeInterface: -> 21 | super 22 | 23 | @initializeEditor() 24 | 25 | initializeEditor: -> 26 | super 27 | 28 | @document = $(document) 29 | @toolbar = new Mercury.Toolbar(@options) 30 | @statusbar = new Mercury.Statusbar(@options) 31 | 32 | $('body').addClass('mercury-inline').addClass('mercury-hidden') 33 | 34 | $(" 138 | 139 |

image next to content wrapped in an anchor

140 | Clippylink for #bookmark1 141 | 142 |

snippet that's pre-loaded

143 |
snippet1
144 | 145 |

bookmark links

146 | Bookmark1
Bookmark2 147 | 148 |

149 | Additional content not wrapped in anything 150 | 151 |

youtube and vimeo iframes with content surrounding it

152 | 123123321 153 | 123 154 |
155 | 156 |

Markupable region (with markdown)

157 |
158 | ## notes ## 159 | **whitespace** in markupable regions is _important_. 160 | 161 | ## ordered list ## 162 | 1. list item 1 163 | 2. list item 2 164 | 165 | ## unordered list ## 166 | - list item 1 167 | - list item 2 168 | 169 | ## horizontal rule ## 170 | - - - 171 | 172 | ## anchor ## 173 | [this is a link](foo 'optional title') 174 | 175 | ## image ## 176 | ![add alt text](../../images/clippy.png) 177 | 178 | ## snippet that's pre-loaded ## 179 | [--snippet_2--] 180 | 181 | ## code ## 182 | this html will not be escaped 183 | and whitespace will be retained 184 | 185 | ## blockquote ## 186 | > this is indented one level 187 | 188 | ## paragraph with bold, italics, etc. ## 189 | Turkey corned beef ham, chuck ham hock **venison _drumstick_ ** tongue ribeye. Beef tongue pancetta, short ribs meatball headcheese jowl bacon swine t-bone shoulder venison. Hamburger short loin turkey flank short ribs tail. Chicken tri-tip sausage, bresaola sirloin turkey hamburger headcheese shank rump. Pork ground round t-bone, ham hock sirloin biltong chicken fatback pork belly pastrami pancetta jowl tail. Cow ribeye pork chop, flank sirloin tail short loin pork loin chicken boudin pastrami. Boudin chuck shankle, spare ribs pancetta fatback bresaola. 190 |
191 | 192 |

Snippetable region

193 |
194 |
snippet3
195 |
snippet4
196 |
snippet5
197 |
snippet6
198 |
snippet7
199 |
snippet8
200 |
snippet9
201 |
snippet10
202 |
203 | 204 |

Editable region (single snippet and no content)

205 |
206 |
snippet11
207 |
208 | 209 |

Editable region (fixed width/height and overflow)

210 |
211 | wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww 212 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 213 |
214 | 215 |

Snippetable region (iframe snippet that contains flash)

216 |
217 |
snippet12
218 |
snippet13
219 | 220 |
221 |
222 | 223 | 224 | -------------------------------------------------------------------------------- /src/demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Mercury Demo 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 | 36 | 37 |

Editable region

38 |

Editable region

39 |
40 | If the first line of content isn't wrapped in an element you can't set justification (mozilla only bug). 41 | 42 |

text with a meta tag (that will be removed)

43 | 123editable content 44 | 45 |

anchor with a div inside

46 | l
ink with a div in
it
47 | 48 |

image wrapped in an anchor

49 | clippy123 50 | 51 |

table with header and definition cells

52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
R1C1R1C2
R2C1R2C2R2C3R2C4R2C5
R3C1R3C2R3C3
R4C1R4C2R4C3R4C4R4C5
R5C1R5C2R5C3R5C4
R6C1R6C2R6C3R6C4
R7C1R7C2R7C3
R8C1R7C2
100 | 101 |

table without tbody or border

102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |
R1C1R1C2
R2C1R2C2R2C3
R3C1R3C2
117 | 118 |

fixed width table (column where all colspans > 1, row where all rowspans > 1)

119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 |
R1C1R1C2
R3C1R3C2
R4C1R4C3
136 | 137 |

paragraph with an overline wrapped in an underline

138 |

123 under overline line 123

139 | 140 |

paragraph with an underline wrapped in an overline

141 |

123 over underline line 123

142 | 143 |

style tag that styles images with a border

144 | 145 | 146 |

image next to content wrapped in an anchor

147 | Clippylink for #bookmark1 148 | 149 |

snippet that's pre-loaded

150 |
snippet1
151 | 152 |

bookmark links

153 | Bookmark1
Bookmark2 154 | 155 |

156 | Additional content not wrapped in anything 157 | 158 |

youtube and vimeo iframes with content surrounding it

159 | 123123321 160 | 123 161 |
162 | 163 |

Markupable region (with markdown)

164 |
165 | ## notes ## 166 | **whitespace** in markupable regions is _important_. 167 | 168 | ## ordered list ## 169 | 1. list item 1 170 | 2. list item 2 171 | 172 | ## unordered list ## 173 | - list item 1 174 | - list item 2 175 | 176 | ## horizontal rule ## 177 | - - - 178 | 179 | ## anchor ## 180 | [this is a link](foo 'optional title') 181 | 182 | ## image ## 183 | ![add alt text](../../images/clippy.png) 184 | 185 | ## snippet that's pre-loaded ## 186 | [--snippet_2--] 187 | 188 | ## code ## 189 | this html will not be escaped 190 | and whitespace will be retained 191 | 192 | ## blockquote ## 193 | > this is indented one level 194 | 195 | ## paragraph with bold, italics, etc. ## 196 | Turkey corned beef ham, chuck ham hock **venison _drumstick_ ** tongue ribeye. Beef tongue pancetta, short ribs meatball headcheese jowl bacon swine t-bone shoulder venison. Hamburger short loin turkey flank short ribs tail. Chicken tri-tip sausage, bresaola sirloin turkey hamburger headcheese shank rump. Pork ground round t-bone, ham hock sirloin biltong chicken fatback pork belly pastrami pancetta jowl tail. Cow ribeye pork chop, flank sirloin tail short loin pork loin chicken boudin pastrami. Boudin chuck shankle, spare ribs pancetta fatback bresaola. 197 |
198 | 199 |

Snippetable region

200 |
201 |
snippet3
202 |
snippet4
203 |
snippet5
204 |
snippet6
205 |
snippet7
206 |
snippet8
207 |
snippet9
208 |
snippet10
209 |
210 | 211 |

Editable region (single snippet and no content)

212 |
213 |
snippet11
214 |
215 | 216 |

Editable region (fixed width/height and overflow)

217 |
218 | wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww 219 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 220 |
221 | 222 |

Snippetable region (iframe snippet that contains flash)

223 |
224 |
snippet12
225 |
snippet13
226 | 227 |
228 |
229 | 230 | 231 | -------------------------------------------------------------------------------- /src/scripts/table_editor.coffee: -------------------------------------------------------------------------------- 1 | @Mercury.tableEditor = (table, cell, cellContent) -> 2 | Mercury.tableEditor.load(table, cell, cellContent) 3 | return Mercury.tableEditor 4 | 5 | jQuery.extend Mercury.tableEditor, { 6 | 7 | load: (@table, @cell, @cellContent = '') -> 8 | @row = @cell.parent('tr') 9 | @columnCount = @getColumnCount() 10 | @rowCount = @getRowCount() 11 | 12 | 13 | addColumn: (position = 'after') -> 14 | sig = @cellSignatureFor(@cell) 15 | 16 | for row, i in @table.find('tr') 17 | rowSpan = 1 18 | matchOptions = if position == 'after' then {right: sig.right} else {left: sig.left} 19 | if matching = @findCellByOptionsFor(row, matchOptions) 20 | newCell = jQuery("<#{matching.cell.get(0).tagName}>").html(@cellContent) 21 | @setRowspanFor(newCell, matching.height) 22 | if position == 'before' then matching.cell.before(newCell) else matching.cell.after(newCell) 23 | i += matching.height - 1 24 | else if intersecting = @findCellByIntersectionFor(row, sig) 25 | @setColspanFor(intersecting.cell, intersecting.width + 1) 26 | 27 | 28 | removeColumn: -> 29 | sig = @cellSignatureFor(@cell) 30 | return if sig.width > 1 31 | 32 | removing = [] 33 | adjusting = [] 34 | for row, i in @table.find('tr') 35 | if matching = @findCellByOptionsFor(row, {left: sig.left, width: sig.width}) 36 | removing.push(matching.cell) 37 | i += matching.height - 1 38 | else if intersecting = @findCellByIntersectionFor(row, sig) 39 | adjusting.push(intersecting.cell) 40 | 41 | jQuery(cell).remove() for cell in removing 42 | @setColspanFor(cell, @colspanFor(cell) - 1) for cell in adjusting 43 | 44 | 45 | addRow: (position = 'after') -> 46 | newRow = jQuery('') 47 | 48 | if (rowspan = @rowspanFor(@cell)) > 1 && position == 'after' 49 | @row = jQuery(@row.nextAll('tr')[rowspan - 2]) 50 | 51 | cellCount = 0 52 | for cell in @row.find('th, td') 53 | colspan = @colspanFor(cell) 54 | newCell = jQuery("<#{cell.tagName}>").html(@cellContent) 55 | @setColspanFor(newCell, colspan) 56 | cellCount += colspan 57 | if (rowspan = @rowspanFor(cell)) > 1 && position == 'after' 58 | @setRowspanFor(cell, rowspan + 1) 59 | continue 60 | 61 | newRow.append(newCell) 62 | 63 | if cellCount < @columnCount 64 | rowCount = 0 65 | for previousRow in @row.prevAll('tr') 66 | rowCount += 1 67 | for cell in jQuery(previousRow).find('td[rowspan], th[rowspan]') 68 | rowspan = @rowspanFor(cell) 69 | if rowspan - 1 >= rowCount && position == 'before' 70 | @setRowspanFor(cell, rowspan + 1) 71 | else if rowspan - 1 >= rowCount && position == 'after' 72 | if rowspan - 1 == rowCount 73 | newCell = jQuery("<#{cell.tagName}>").html(@cellContent) 74 | @setColspanFor(newCell, @colspanFor(cell)) 75 | newRow.append(newCell) 76 | else 77 | @setRowspanFor(cell, rowspan + 1) 78 | 79 | if position == 'before' then @row.before(newRow) else @row.after(newRow) 80 | 81 | 82 | removeRow: -> 83 | # check to see that all cells have the same rowspan, and figure out the minimum rowspan 84 | rowspansMatch = true 85 | prevRowspan = 0 86 | minRowspan = 0 87 | for cell in @row.find('td, th') 88 | rowspan = @rowspanFor(cell) 89 | rowspansMatch = false if prevRowspan && rowspan != prevRowspan 90 | minRowspan = rowspan if rowspan < minRowspan || !minRowspan 91 | prevRowspan = rowspan 92 | 93 | return if !rowspansMatch && @rowspanFor(@cell) > minRowspan 94 | 95 | # remove any emtpy rows below 96 | if minRowspan > 1 97 | jQuery(@row.nextAll('tr')[i]).remove() for i in [0..minRowspan - 2] 98 | 99 | # find and move down any cells that have a larger rowspan 100 | for cell in @row.find('td[rowspan], th[rowspan]') 101 | sig = @cellSignatureFor(cell) 102 | continue if sig.height == minRowspan 103 | if match = @findCellByOptionsFor(@row.nextAll('tr')[minRowspan - 1], {left: sig.left, forceAdjacent: true}) 104 | @setRowspanFor(cell, @rowspanFor(cell) - @rowspanFor(@cell)) 105 | if match.direction == 'before' then match.cell.before(jQuery(cell).clone()) else match.cell.after(jQuery(cell).clone()) 106 | 107 | if @columnsFor(@row.find('td, th')) < @columnCount 108 | # move up rows looking for cells with rowspans that might intersect 109 | rowsAbove = 0 110 | for aboveRow in @row.prevAll('tr') 111 | rowsAbove += 1 112 | for cell in jQuery(aboveRow).find('td[rowspan], th[rowspan]') 113 | # if the cell intersects with the row we're trying to calculate on, and it's index is less than where we've 114 | # gotten so far, add it 115 | rowspan = @rowspanFor(cell) 116 | @setRowspanFor(cell, rowspan - @rowspanFor(@cell)) if rowspan > rowsAbove 117 | 118 | @row.remove() 119 | 120 | 121 | increaseColspan: -> 122 | cell = @cell.next('td, th') 123 | return unless cell.length 124 | return if @rowspanFor(cell) != @rowspanFor(@cell) 125 | return if @cellIndexFor(cell) > @cellIndexFor(@cell) + @colspanFor(@cell) 126 | @setColspanFor(@cell, @colspanFor(@cell) + @colspanFor(cell)) 127 | cell.remove() 128 | 129 | 130 | decreaseColspan: -> 131 | return if @colspanFor(@cell) == 1 132 | @setColspanFor(@cell, @colspanFor(@cell) - 1) 133 | newCell = jQuery("<#{@cell.get(0).tagName}>").html(@cellContent) 134 | @setRowspanFor(newCell, @rowspanFor(@cell)) 135 | @cell.after(newCell) 136 | 137 | 138 | increaseRowspan: -> 139 | sig = @cellSignatureFor(@cell) 140 | nextRow = @row.nextAll('tr')[sig.height - 1] 141 | if nextRow && match = @findCellByOptionsFor(nextRow, {left: sig.left, width: sig.width}) 142 | @setRowspanFor(@cell, sig.height + match.height) 143 | match.cell.remove() 144 | 145 | decreaseRowspan: -> 146 | sig = @cellSignatureFor(@cell) 147 | return if sig.height == 1 148 | nextRow = @row.nextAll('tr')[sig.height - 2] 149 | if match = @findCellByOptionsFor(nextRow, {left: sig.left, forceAdjacent: true}) 150 | newCell = jQuery("<#{@cell.get(0).tagName}>").html(@cellContent) 151 | @setColspanFor(newCell, @colspanFor(@cell)) 152 | @setRowspanFor(@cell, sig.height - 1) 153 | if match.direction == 'before' then match.cell.before(newCell) else match.cell.after(newCell) 154 | 155 | # Counts the columns of the first row (alpha row) in the table. We can safely rely on the first row always being 156 | # comprised of a full set of cells or cells with colspans. 157 | getColumnCount: -> 158 | return @columnsFor(@table.find('thead tr:first-child, tbody tr:first-child, tfoot tr:first-child').first().find('td, th')) 159 | 160 | 161 | # Counts the rows of the table. 162 | getRowCount: -> 163 | return @table.find('tr').length 164 | 165 | 166 | # Gets the index for a given cell, taking into account that rows above it can have cells that have rowspans. 167 | cellIndexFor: (cell) -> 168 | cell = jQuery(cell) 169 | 170 | # get the row for the cell and calculate all the columns in it 171 | row = cell.parent('tr') 172 | columns = @columnsFor(row.find('td, th')) 173 | index = @columnsFor(cell.prevAll('td, th')) 174 | 175 | # if the columns is less than expected, we need to look above for rowspans 176 | if columns < @columnCount 177 | # move up rows looking for cells with rowspans that might intersect 178 | rowsAbove = 0 179 | for aboveRow in row.prevAll('tr') 180 | rowsAbove += 1 181 | for aboveCell in jQuery(aboveRow).find('td[rowspan], th[rowspan]') 182 | # if the cell intersects with the row we're trying to calculate on, and it's index is less than where we've 183 | # gotten so far, add it 184 | if @rowspanFor(aboveCell) > rowsAbove && @cellIndexFor(aboveCell) <= index 185 | index += @colspanFor(aboveCell) 186 | 187 | return index 188 | 189 | # Creates a signature for a given cell, which is made up if it's size, and itself. 190 | cellSignatureFor: (cell) -> 191 | sig = {cell: jQuery(cell)} 192 | sig.left = @cellIndexFor(cell) 193 | sig.width = @colspanFor(cell) 194 | sig.height = @rowspanFor(cell) 195 | sig.right = sig.left + sig.width 196 | return sig 197 | 198 | # Find a cell based on options. Options can be: 199 | # right 200 | # or 201 | # left, [width], [forceAdjacent] 202 | # eg. findCellByOptionsFor(@row, {left: 1, width: 2, forceAdjacent: true}) 203 | findCellByOptionsFor: (row, options) -> 204 | for cell in jQuery(row).find('td, th') 205 | sig = @cellSignatureFor(cell) 206 | if typeof(options.right) != 'undefined' 207 | return sig if sig.right == options.right 208 | if typeof(options.left) != 'undefined' 209 | 210 | if options.width 211 | return sig if sig.left == options.left && sig.width == options.width 212 | else if !options.forceAdjacent 213 | return sig if sig.left == options.left 214 | else if options.forceAdjacent 215 | if sig.left > options.left 216 | prev = jQuery(cell).prev('td, th') 217 | if prev.length 218 | sig = @cellSignatureFor(prev) 219 | sig.direction = 'after' 220 | else 221 | sig.direction = 'before' 222 | return sig 223 | 224 | if options.forceAdjacent 225 | sig.direction = 'after' 226 | return sig 227 | 228 | return null 229 | 230 | # Finds a cell that intersects with the current signature 231 | findCellByIntersectionFor: (row, signature) -> 232 | for cell in jQuery(row).find('td, th') 233 | sig = @cellSignatureFor(cell) 234 | return sig if sig.right - signature.left >= 0 && sig.right > signature.left 235 | return null 236 | 237 | 238 | # Counts all the columns in a given array of columns, taking colspans into 239 | # account. 240 | columnsFor: (cells) -> 241 | count = 0 242 | count += @colspanFor(cell) for cell in cells 243 | return count 244 | 245 | 246 | # Tries to get the colspan of a cell, falling back to 1 if there's none 247 | # specified. 248 | colspanFor: (cell) -> 249 | return parseInt(jQuery(cell).attr('colspan')) || 1 250 | 251 | 252 | # Tries to get the rowspan of a cell, falling back to 1 if there's none 253 | # specified. 254 | rowspanFor: (cell) -> 255 | return parseInt(jQuery(cell).attr('rowspan')) || 1 256 | 257 | 258 | # Sets the colspan of a cell, removing it if it's 1. 259 | setColspanFor: (cell, value) -> 260 | jQuery(cell).attr('colspan', if value > 1 then value else null) 261 | 262 | 263 | # Sets the rowspan of a cell, removing it if it's 1 264 | setRowspanFor: (cell, value) -> 265 | jQuery(cell).attr('rowspan', if value > 1 then value else null) 266 | 267 | } 268 | -------------------------------------------------------------------------------- /src/scripts/mercury.coffee: -------------------------------------------------------------------------------- 1 | ###! 2 | Mercury Editor is a Coffeescript and jQuery based WYSIWYG editor. Documentation and other useful information can be found at https://github.com/jejacks0n/mercury 3 | 4 | Supported browsers: 5 | - Firefox 4+ 6 | - Chrome 10+ 7 | - Safari 5+ 8 | 9 | Copyright (c) 2011 Jeremy Jackson 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | ### 17 | #= require jquery-1.6 18 | #= require jquery-ui-1.8.13.custom 19 | #= require jquery.additions 20 | #= require liquidmetal 21 | #= require showdown 22 | #= require_self 23 | #= require ./native_extensions 24 | #= require ./page_editor 25 | #= require ./history_buffer 26 | #= require ./table_editor 27 | #= require ./dialog 28 | #= require ./palette 29 | #= require ./select 30 | #= require ./panel 31 | #= require ./modal 32 | #= require ./statusbar 33 | #= require ./toolbar 34 | #= require ./toolbar.button 35 | #= require ./toolbar.button_group 36 | #= require ./toolbar.expander 37 | #= require ./tooltip 38 | #= require ./snippet 39 | #= require ./snippet_toolbar 40 | #= require ./region 41 | #= require ./uploader 42 | #= require_tree ./regions 43 | #= require_tree ./dialogs 44 | #= require_tree ./modals 45 | 46 | 47 | # iFrame / no-iFrame handle 48 | if window.top.Mercury? and window.top.Mercury.loaded? 49 | window.mercuryWindow = window.top 50 | return 51 | else 52 | window.mercuryWindow = window 53 | 54 | # Mercury! Do a little dance! Boom cha cha boom cha cha 55 | Mercury = window.Mercury = jQuery.extend( 56 | 57 | # Deep Extend 58 | true 59 | 60 | # Default 61 | { 62 | # Options 63 | silent: false 64 | debug: true 65 | 66 | # Configuration 67 | config: 68 | editor: 'inline' 69 | 70 | cleanStylesOnPaste: true 71 | snippets: 72 | optionsUrl: "/mercury/snippets/:name/options" 73 | previewUrl: "/mercury/snippets/:name/preview" 74 | 75 | uploading: 76 | enabled: 'local' 77 | allowedMimeTypes: [ "image/jpeg", "image/gif", "image/png" ] 78 | maxFileSize: 1235242880 79 | inputName: "image[image]" 80 | url: "/images" 81 | handler: false 82 | 83 | toolbars: 84 | primary: 85 | save: [ "Save", "Save this page" ] 86 | preview: [ "Preview", "Preview this page", 87 | toggle: true 88 | mode: true 89 | ] 90 | sep1: " " 91 | undoredo: 92 | undo: [ "Undo", "Undo your last action" ] 93 | redo: [ "Redo", "Redo your last action" ] 94 | sep: " " 95 | 96 | insertLink: [ "Link", "Insert Link", 97 | modal: "/mercury/modals/link" 98 | regions: [ "rich", "markupable" ] 99 | ] 100 | insertMedia: [ "Media", "Insert Media (images and videos)", 101 | modal: "/mercury/modals/media" 102 | regions: [ "rich", "markupable" ] 103 | ] 104 | insertTable: [ "Table", "Insert Table", 105 | modal: "/mercury/modals/table" 106 | regions: [ "rich", "markupable" ] 107 | ] 108 | insertCharacter: [ "Character", "Special Characters", 109 | modal: "/mercury/modals/character" 110 | regions: [ "rich", "markupable" ] 111 | ] 112 | snippetPanel: [ "Snippet", "Snippet Panel", panel: "/mercury/panels/snippets" ] 113 | sep2: " " 114 | historyPanel: [ "History", "Page Version History", panel: "/mercury/panels/history" ] 115 | sep3: " " 116 | notesPanel: [ "Notes", "Page Notes", panel: "/mercury/panels/notes" ] 117 | 118 | simple: 119 | _regions: [ "basic", "rich", "markupable" ] 120 | predefined: 121 | style: [ "Style", null, 122 | select: "/mercury/selects/style" 123 | preload: true 124 | ] 125 | sep1: " " 126 | formatblock: [ "Block Format", null, 127 | select: "/mercury/selects/formatblock" 128 | preload: true 129 | ] 130 | sep2: "-" 131 | 132 | colors: 133 | backColor: [ "Background Color", null, 134 | palette: "/mercury/palettes/backcolor" 135 | context: true 136 | preload: true 137 | regions: [ "basic", "rich" ] 138 | ] 139 | sep1: " " 140 | foreColor: [ "Text Color", null, 141 | palette: "/mercury/palettes/forecolor" 142 | context: true 143 | preload: true 144 | regions: [ "basic", "rich" ] 145 | ] 146 | sep2: "-" 147 | 148 | decoration: 149 | bold: [ "Bold", null, context: true ] 150 | italic: [ "Italicize", null, context: true ] 151 | overline: [ "Overline", null, 152 | context: true 153 | regions: [ "basic", "rich" ] 154 | ] 155 | strikethrough: [ "Strikethrough", null, 156 | context: true 157 | regions: [ "basic", "rich" ] 158 | ] 159 | underline: [ "Underline", null, 160 | context: true 161 | regions: [ "basic", "rich" ] 162 | ] 163 | sep: "-" 164 | 165 | script: 166 | subscript: [ "Subscript", null, context: true ] 167 | superscript: [ "Superscript", null, context: true ] 168 | 169 | editable: 170 | _regions: [ "rich", "markupable" ] 171 | justify: 172 | justifyLeft: [ "Align Left", null, 173 | context: true 174 | regions: [ "rich" ] 175 | ] 176 | justifyCenter: [ "Center", null, 177 | context: true 178 | regions: [ "rich" ] 179 | ] 180 | justifyRight: [ "Align Right", null, 181 | context: true 182 | regions: [ "rich" ] 183 | ] 184 | justifyFull: [ "Justify Full", null, 185 | context: true 186 | regions: [ "rich" ] 187 | ] 188 | sep: "-" 189 | 190 | list: 191 | insertUnorderedList: [ "Unordered List", null, context: true ] 192 | insertOrderedList: [ "Numbered List", null, context: true ] 193 | sep: "-" 194 | 195 | indent: 196 | outdent: [ "Decrease Indentation", null ] 197 | indent: [ "Increase Indentation", null ] 198 | sep: "-" 199 | 200 | table: 201 | _context: true 202 | insertRowBefore: [ "Insert Table Row", "Insert a table row before the cursor", regions: [ "rich" ] ] 203 | insertRowAfter: [ "Insert Table Row", "Insert a table row after the cursor", regions: [ "rich" ] ] 204 | deleteRow: [ "Delete Table Row", "Delete this table row", regions: [ "rich" ] ] 205 | insertColumnBefore: [ "Insert Table Column", "Insert a table column before the cursor", regions: [ "rich" ] ] 206 | insertColumnAfter: [ "Insert Table Column", "Insert a table column after the cursor", regions: [ "rich" ] ] 207 | deleteColumn: [ "Delete Table Column", "Delete this table column", regions: [ "rich" ] ] 208 | sep1: " " 209 | increaseColspan: [ "Increase Cell Columns", "Increase the cells colspan" ] 210 | decreaseColspan: [ "Decrease Cell Columns", "Decrease the cells colspan and add a new cell" ] 211 | increaseRowspan: [ "Increase Cell Rows", "Increase the cells rowspan" ] 212 | decreaseRowspan: [ "Decrease Cell Rows", "Decrease the cells rowspan and add a new cell" ] 213 | sep2: "-" 214 | 215 | rules: 216 | horizontalRule: [ "Horizontal Rule", "Insert a horizontal rule" ] 217 | sep1: "-" 218 | 219 | formatting: 220 | removeFormatting: [ "Remove Formatting", "Remove formatting for the selection", regions: [ "rich" ] ] 221 | sep2: " " 222 | 223 | editors: htmlEditor: [ "Edit HTML", "Edit the HTML content", regions: [ "rich" ] ] 224 | 225 | snippetable: 226 | _custom: true 227 | actions: 228 | editSnippet: [ "Edit Snippet Settings", null ] 229 | sep1: " " 230 | removeSnippet: [ "Remove Snippet", null ] 231 | 232 | behaviors: 233 | horizontalRule: (selection) -> 234 | selection.replace "
" 235 | 236 | htmlEditor: -> 237 | Mercury.modal "/mercury/modals/htmleditor", 238 | title: "HTML Editor" 239 | fullHeight: true 240 | handler: "htmlEditor" 241 | 242 | injectedStyles: "" + ".mercury-region, .mercury-textarea { min-height: 10px; outline: 1px dotted #09F }" + ".mercury-textarea { box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; resize: vertical; }" + ".mercury-region:focus, .mercury-region.focus, .mercury-textarea.focus { outline: none; -webkit-box-shadow: 0 0 10px #09F, 0 0 1px #045; box-shadow: 0 0 10px #09F, 0 0 1px #045 }" + ".mercury-region:after { content: \".\"; display: block; visibility: hidden; clear: both; height: 0; overflow: hidden; }" + ".mercury-region table, .mercury-region td, .mercury-region th { border: 1px dotted red; }" 243 | 244 | # Mercury object namespaces 245 | Regions: {} 246 | modalHandlers: {} 247 | dialogHandlers: {} 248 | preloadedViews: {} 249 | } 250 | 251 | # User Configuration 252 | window.Mercury||{} 253 | 254 | # Custom Configuration 255 | { 256 | version: '0.2.0' 257 | 258 | # No IE support yet because it doesn't follow the W3C standards for HTML5 contentEditable (aka designMode). 259 | supported: document.getElementById && document.designMode && !jQuery.browser.konqueror && !jQuery.browser.msie 260 | 261 | # Custom event and logging methods 262 | bind: (eventName, callback) -> 263 | jQuery(document).bind("mercury:#{eventName}", callback) 264 | 265 | trigger: (eventName, options) -> 266 | Mercury.log(eventName, options) 267 | jQuery(document).trigger("mercury:#{eventName}", options) 268 | 269 | log: -> 270 | if Mercury.debug && console 271 | return if arguments[0] == 'hide:toolbar' || arguments[0] == 'show:toolbar' 272 | try console.debug(arguments) catch e 273 | 274 | } 275 | ) -------------------------------------------------------------------------------- /src/scripts/regions/markupable.coffee: -------------------------------------------------------------------------------- 1 | # todo: 2 | # context for the toolbar buttons and groups needs to change so we can do the following: 3 | # how to handle context for buttons? if the cursor is within a bold area (**bo|ld**), or selecting it -- it would be 4 | # nice if we could activate the bold button for instance. 5 | 6 | class @Mercury.Regions.Markupable extends Mercury.Region 7 | type = 'markupable' 8 | 9 | constructor: (@element, @window, @options = {}) -> 10 | super 11 | @type = 'markupable' 12 | @converter = new Showdown.converter() 13 | 14 | 15 | build: -> 16 | width = '100%' 17 | height = @element.height() 18 | 19 | value = @element.html().replace(/^\s+|\s+$/g, '').replace('>', '>') 20 | @textarea = jQuery('