spree_awesome_blog 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/README.md +13 -0
  2. data/app/controllers/admin/comments_controller.rb +32 -0
  3. data/app/controllers/admin/post_images_controller.rb +55 -0
  4. data/app/controllers/admin/posts_controller.rb +19 -0
  5. data/app/controllers/comments_controller.rb +23 -0
  6. data/app/controllers/posts_controller.rb +32 -0
  7. data/app/helpers/posts_helper.rb +19 -0
  8. data/app/models/comment.rb +31 -0
  9. data/app/models/comment.rb~ +31 -0
  10. data/app/models/post.rb +47 -0
  11. data/app/stylesheets/blog.less +234 -0
  12. data/app/views/admin/comments/_form.html.erb +28 -0
  13. data/app/views/admin/comments/edit.html.erb +8 -0
  14. data/app/views/admin/comments/index.html.erb +45 -0
  15. data/app/views/admin/post_images/_form.html.erb +8 -0
  16. data/app/views/admin/post_images/edit.html.erb +18 -0
  17. data/app/views/admin/post_images/index.html.erb +44 -0
  18. data/app/views/admin/post_images/new.html.erb +22 -0
  19. data/app/views/admin/posts/_form.html.erb +39 -0
  20. data/app/views/admin/posts/edit.html.erb +8 -0
  21. data/app/views/admin/posts/index.html.erb +53 -0
  22. data/app/views/admin/posts/new.html.erb +6 -0
  23. data/app/views/comments/_form.html.erb +30 -0
  24. data/app/views/comments/new.html.erb +3 -0
  25. data/app/views/posts/_sidebar.html.erb +34 -0
  26. data/app/views/posts/_tag_cloud.html.erb +5 -0
  27. data/app/views/posts/_tags.html.erb +3 -0
  28. data/app/views/posts/index.html.erb +32 -0
  29. data/app/views/posts/index.rss.builder +18 -0
  30. data/app/views/posts/show.html.erb +43 -0
  31. data/app/views/shared/_blog_sub_menu.html.erb +9 -0
  32. data/app/views/shared/_blog_tabs.html.erb +25 -0
  33. data/config/locales/en-US.yml +35 -0
  34. data/config/routes.rb +20 -0
  35. data/lib/generators/spree_awesome_blog/install_generator.rb +14 -0
  36. data/lib/generators/templates/db/migrate/20110110042721_create_posts.rb +21 -0
  37. data/lib/generators/templates/db/migrate/20110110042722_create_comments.rb +20 -0
  38. data/lib/generators/templates/db/migrate/20110110053623_acts_as_taggable_on_migration.rb +28 -0
  39. data/lib/generators/templates/public/images/markitup/bold.png +0 -0
  40. data/lib/generators/templates/public/images/markitup/clean.png +0 -0
  41. data/lib/generators/templates/public/images/markitup/h1.png +0 -0
  42. data/lib/generators/templates/public/images/markitup/h2.png +0 -0
  43. data/lib/generators/templates/public/images/markitup/h3.png +0 -0
  44. data/lib/generators/templates/public/images/markitup/h4.png +0 -0
  45. data/lib/generators/templates/public/images/markitup/h5.png +0 -0
  46. data/lib/generators/templates/public/images/markitup/h6.png +0 -0
  47. data/lib/generators/templates/public/images/markitup/image.png +0 -0
  48. data/lib/generators/templates/public/images/markitup/italic.png +0 -0
  49. data/lib/generators/templates/public/images/markitup/link.png +0 -0
  50. data/lib/generators/templates/public/images/markitup/list-bullet.png +0 -0
  51. data/lib/generators/templates/public/images/markitup/list-numeric.png +0 -0
  52. data/lib/generators/templates/public/images/markitup/paragraph.png +0 -0
  53. data/lib/generators/templates/public/images/markitup/picture.png +0 -0
  54. data/lib/generators/templates/public/images/markitup/preview.png +0 -0
  55. data/lib/generators/templates/public/images/markitup/quotes.png +0 -0
  56. data/lib/generators/templates/public/images/markitup/stroke.png +0 -0
  57. data/lib/generators/templates/public/javascripts/admin/blog.js +15 -0
  58. data/lib/generators/templates/public/javascripts/jquery.markitup.showdown.js +1904 -0
  59. data/lib/generators/templates/public/stylesheets/admin/blog.css +163 -0
  60. data/lib/generators/templates/public/stylesheets/blog.css +34 -0
  61. data/lib/spree_awesome_blog.rb +5 -0
  62. data/lib/spree_awesome_blog/engine.rb +29 -0
  63. data/lib/spree_awesome_blog_hooks.rb +10 -0
  64. metadata +179 -0
@@ -0,0 +1,9 @@
1
+ <% content_for :sub_menu do %>
2
+ <ul id="sub_nav">
3
+ <%= hook :admin_blog_sub_tabs do %>
4
+ <%= tab :posts, :match_path => '/posts' %>
5
+ <%= tab :comments, :match_path => '/comments' %>
6
+ <% end %>
7
+ </ul>
8
+ <% end %>
9
+
@@ -0,0 +1,25 @@
1
+ <h1><%= raw(t("editing_post") + " &ldquo;#{@post.title}&rdquo;") %></h1>
2
+
3
+ <% content_for :sidebar do %>
4
+
5
+ <h3><%= @post.title %></h3>
6
+ <br class="clear" />
7
+
8
+ <ul class="sidebar post-menu">
9
+ <%= hook :admin_post_tabs, {:current => current} do %>
10
+
11
+ <li<%= raw(' class="active"') if current == "Post" %>>
12
+ <%= link_to t("post"), edit_admin_post_path(@post) %>
13
+ </li>
14
+ <li<%= raw(' class="active"') if current == "Images" %>>
15
+ <%= link_to "#{t("images")} (#{@post.images.count})", admin_post_images_path(@post) %>
16
+ </li>
17
+ <li<%= raw(' class="active"') if current == "Comments" %>>
18
+ <%= link_to "#{t("comments")} (#{@post.comments.count})", admin_post_comments_path(@post) %>
19
+ </li>
20
+ <% end %>
21
+ </ul>
22
+ <br class="clear" />
23
+
24
+ <% end %>
25
+
@@ -0,0 +1,35 @@
1
+ ---
2
+ en:
3
+ blog: Blog
4
+ posts: Posts
5
+ title: Title
6
+ new_post: New Post
7
+ unpublished: Unpublished
8
+ published: Published
9
+ publish: Publish
10
+ body: Body
11
+ comments: Comments
12
+ comment: Comment
13
+ post: Post
14
+ approved: Approved
15
+ not approved: Not Approved
16
+ editing_post: Editing post
17
+ comments_for: Comments for
18
+ posts_tagged: Posts tagged %s
19
+ posts_year: Posts for year %s
20
+ posts_month: Posts for %s
21
+ posts_day: Posts for %s
22
+ read_more: Read more
23
+ tags: Tags
24
+ comma_seperated: Comma seperated
25
+ your_name: Your name
26
+ your_email: Your email
27
+ your_website: Your website
28
+ post_comment: Post Comment
29
+ commenting_on: Commenting on "%s"
30
+ not_shared: Your email is never shared with anyone
31
+ created_successfully_pending_approval: Comment was successfully created. It will displayed once it is approved
32
+ comment_on_post: Comment on this post
33
+ blog_archive: Blog Archive
34
+ url: URL
35
+ editing_comment: Edit Comment
data/config/routes.rb ADDED
@@ -0,0 +1,20 @@
1
+ Rails.application.routes.draw do
2
+ scope "/#{(Spree::Config[:blog_title] || 'blog').to_url}" do
3
+ match '/tagged/:tag', :to => 'posts#index', :as => 'posts_by_tag'
4
+ match '/:year(/:month(/:day))', :to => 'posts#index', :as => 'posts_by_date',
5
+ :year => /\d{4}/,
6
+ :month => /\d{1,2}/,
7
+ :day => /\d{1,2}/
8
+ resources :posts, :path => '/' do
9
+ resources :comments
10
+ end
11
+ end
12
+
13
+ namespace :admin do
14
+ resources :posts do
15
+ resources :comments
16
+ resources :images, :controller => 'post_images'
17
+ end
18
+ resources :comments
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ module SpreeAwesomeBlog
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path("../../templates", __FILE__)
5
+
6
+ desc "Configures your Rails application for use with spree_awesome_blog"
7
+ def copy_files
8
+ directory "db"
9
+ directory "public"
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ class CreatePosts < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :posts do |t|
4
+ t.string :title
5
+ t.string :permalink
6
+ t.text :summary
7
+ t.text :body
8
+ t.text :tags
9
+ t.boolean :publish
10
+ t.datetime :published_on
11
+ t.string :meta_keywords
12
+ t.string :meta_description
13
+
14
+ t.timestamps
15
+ end
16
+ end
17
+
18
+ def self.down
19
+ drop_table :posts
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ class CreateComments < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :comments do |t|
4
+ t.integer :post_id
5
+ t.integer :user_id
6
+ t.string :name
7
+ t.string :email
8
+ t.string :url
9
+ t.text :message
10
+ t.boolean :approved, :default => false
11
+ t.datetime :approved_on
12
+
13
+ t.timestamps
14
+ end
15
+ end
16
+
17
+ def self.down
18
+ drop_table :comments
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ class ActsAsTaggableOnMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :tags do |t|
4
+ t.string :name
5
+ end
6
+
7
+ create_table :taggings do |t|
8
+ t.references :tag
9
+
10
+ # You should make sure that the column created is
11
+ # long enough to store the required class names.
12
+ t.references :taggable, :polymorphic => true
13
+ t.references :tagger, :polymorphic => true
14
+
15
+ t.string :context
16
+
17
+ t.datetime :created_at
18
+ end
19
+
20
+ add_index :taggings, :tag_id
21
+ add_index :taggings, [:taggable_id, :taggable_type, :context]
22
+ end
23
+
24
+ def self.down
25
+ drop_table :taggings
26
+ drop_table :tags
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ $(function() {
2
+ var pictures = [];
3
+ var allowPictures = false;
4
+
5
+ if ($('#images').size() > 0) {
6
+ allowPictures = true;
7
+ //load images
8
+ $('#images img').each(function() {
9
+ pictures.push({
10
+ url: $(this).attr('src'),
11
+ title: $(this).attr('alt')});
12
+ });
13
+ }
14
+ $("textarea.editor").markdownEditor({allowPictures: allowPictures, pictures: pictures});
15
+ });
@@ -0,0 +1,1904 @@
1
+ //
2
+ // showdown.js -- A javascript port of Markdown.
3
+ //
4
+ // Copyright (c) 2007 John Fraser.
5
+ //
6
+ // Original Markdown Copyright (c) 2004-2005 John Gruber
7
+ // <http://daringfireball.net/projects/markdown/>
8
+ //
9
+ // Redistributable under a BSD-style open source license.
10
+ // See license.txt for more information.
11
+ //
12
+ // The full source distribution is at:
13
+ //
14
+ // A A L
15
+ // T C A
16
+ // T K B
17
+ //
18
+ // <http://www.attacklab.net/>
19
+ //
20
+
21
+ //
22
+ // Wherever possible, Showdown is a straight, line-by-line port
23
+ // of the Perl version of Markdown.
24
+ //
25
+ // This is not a normal parser design; it's basically just a
26
+ // series of string substitutions. It's hard to read and
27
+ // maintain this way, but keeping Showdown close to the original
28
+ // design makes it easier to port new features.
29
+ //
30
+ // More importantly, Showdown behaves like markdown.pl in most
31
+ // edge cases. So web applications can do client-side preview
32
+ // in Javascript, and then build identical HTML on the server.
33
+ //
34
+ // This port needs the new RegExp functionality of ECMA 262,
35
+ // 3rd Edition (i.e. Javascript 1.5). Most modern web browsers
36
+ // should do fine. Even with the new regular expression features,
37
+ // We do a lot of work to emulate Perl's regex functionality.
38
+ // The tricky changes in this file mostly have the "attacklab:"
39
+ // label. Major or self-explanatory changes don't.
40
+ //
41
+ // Smart diff tools like Araxis Merge will be able to match up
42
+ // this file with markdown.pl in a useful way. A little tweaking
43
+ // helps: in a copy of markdown.pl, replace "#" with "//" and
44
+ // replace "$text" with "text". Be sure to ignore whitespace
45
+ // and line endings.
46
+ //
47
+
48
+
49
+ //
50
+ // Showdown usage:
51
+ //
52
+ // var text = "Markdown *rocks*.";
53
+ //
54
+ // var converter = new Showdown.converter();
55
+ // var html = converter.makeHtml(text);
56
+ //
57
+ // alert(html);
58
+ //
59
+ // Note: move the sample code to the bottom of this
60
+ // file before uncommenting it.
61
+ //
62
+
63
+
64
+ //
65
+ // Showdown namespace
66
+ //
67
+ var Showdown = {};
68
+
69
+ //
70
+ // converter
71
+ //
72
+ // Wraps all "globals" so that the only thing
73
+ // exposed is makeHtml().
74
+ //
75
+ Showdown.converter = function() {
76
+
77
+ //
78
+ // Globals:
79
+ //
80
+
81
+ // Global hashes, used by various utility routines
82
+ var g_urls;
83
+ var g_titles;
84
+ var g_html_blocks;
85
+
86
+ // Used to track when we're inside an ordered or unordered list
87
+ // (see _ProcessListItems() for details):
88
+ var g_list_level = 0;
89
+
90
+
91
+ this.makeHtml = function(text) {
92
+ //
93
+ // Main function. The order in which other subs are called here is
94
+ // essential. Link and image substitutions need to happen before
95
+ // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
96
+ // and <img> tags get encoded.
97
+ //
98
+
99
+ // Clear the global hashes. If we don't clear these, you get conflicts
100
+ // from other articles when generating a page which contains more than
101
+ // one article (e.g. an index page that shows the N most recent
102
+ // articles):
103
+ g_urls = new Array();
104
+ g_titles = new Array();
105
+ g_html_blocks = new Array();
106
+
107
+ // attacklab: Replace ~ with ~T
108
+ // This lets us use tilde as an escape char to avoid md5 hashes
109
+ // The choice of character is arbitray; anything that isn't
110
+ // magic in Markdown will work.
111
+ text = text.replace(/~/g,"~T");
112
+
113
+ // attacklab: Replace $ with ~D
114
+ // RegExp interprets $ as a special character
115
+ // when it's in a replacement string
116
+ text = text.replace(/\$/g,"~D");
117
+
118
+ // Standardize line endings
119
+ text = text.replace(/\r\n/g,"\n"); // DOS to Unix
120
+ text = text.replace(/\r/g,"\n"); // Mac to Unix
121
+
122
+ // Make sure text begins and ends with a couple of newlines:
123
+ text = "\n\n" + text + "\n\n";
124
+
125
+ // Convert all tabs to spaces.
126
+ text = _Detab(text);
127
+
128
+ // Strip any lines consisting only of spaces and tabs.
129
+ // This makes subsequent regexen easier to write, because we can
130
+ // match consecutive blank lines with /\n+/ instead of something
131
+ // contorted like /[ \t]*\n+/ .
132
+ text = text.replace(/^[ \t]+$/mg,"");
133
+
134
+ // Turn block-level HTML blocks into hash entries
135
+ text = _HashHTMLBlocks(text);
136
+
137
+ // Strip link definitions, store in hashes.
138
+ text = _StripLinkDefinitions(text);
139
+
140
+ text = _RunBlockGamut(text);
141
+
142
+ text = _UnescapeSpecialChars(text);
143
+
144
+ // attacklab: Restore dollar signs
145
+ text = text.replace(/~D/g,"$$");
146
+
147
+ // attacklab: Restore tildes
148
+ text = text.replace(/~T/g,"~");
149
+
150
+ return text;
151
+ }
152
+
153
+
154
+ var _StripLinkDefinitions = function(text) {
155
+ //
156
+ // Strips link definitions from text, stores the URLs and titles in
157
+ // hash references.
158
+ //
159
+
160
+ // Link defs are in the form: ^[id]: url "optional title"
161
+
162
+ /*
163
+ var text = text.replace(/
164
+ ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1
165
+ [ \t]*
166
+ \n? // maybe *one* newline
167
+ [ \t]*
168
+ <?(\S+?)>? // url = $2
169
+ [ \t]*
170
+ \n? // maybe one newline
171
+ [ \t]*
172
+ (?:
173
+ (\n*) // any lines skipped = $3 attacklab: lookbehind removed
174
+ ["(]
175
+ (.+?) // title = $4
176
+ [")]
177
+ [ \t]*
178
+ )? // title is optional
179
+ (?:\n+|$)
180
+ /gm,
181
+ function(){...});
182
+ */
183
+ var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,
184
+ function (wholeMatch,m1,m2,m3,m4) {
185
+ m1 = m1.toLowerCase();
186
+ g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive
187
+ if (m3) {
188
+ // Oops, found blank lines, so it's not a title.
189
+ // Put back the parenthetical statement we stole.
190
+ return m3+m4;
191
+ } else if (m4) {
192
+ g_titles[m1] = m4.replace(/"/g,"&quot;");
193
+ }
194
+
195
+ // Completely remove the definition from the text
196
+ return "";
197
+ }
198
+ );
199
+
200
+ return text;
201
+ }
202
+
203
+
204
+ var _HashHTMLBlocks = function(text) {
205
+ // attacklab: Double up blank lines to reduce lookaround
206
+ text = text.replace(/\n/g,"\n\n");
207
+
208
+ // Hashify HTML blocks:
209
+ // We only want to do this for block-level HTML tags, such as headers,
210
+ // lists, and tables. That's because we still want to wrap <p>s around
211
+ // "paragraphs" that are wrapped in non-block-level tags, such as anchors,
212
+ // phrase emphasis, and spans. The list of tags we're looking for is
213
+ // hard-coded:
214
+ var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
215
+ var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
216
+
217
+ // First, look for nested blocks, e.g.:
218
+ // <div>
219
+ // <div>
220
+ // tags for inner block must be indented.
221
+ // </div>
222
+ // </div>
223
+ //
224
+ // The outermost tags must start at the left margin for this to match, and
225
+ // the inner nested divs must be indented.
226
+ // We need to do this before the next, more liberal match, because the next
227
+ // match will start at the first `<div>` and stop at the first `</div>`.
228
+
229
+ // attacklab: This regex can be expensive when it fails.
230
+ /*
231
+ var text = text.replace(/
232
+ ( // save in $1
233
+ ^ // start of line (with /m)
234
+ <($block_tags_a) // start tag = $2
235
+ \b // word break
236
+ // attacklab: hack around khtml/pcre bug...
237
+ [^\r]*?\n // any number of lines, minimally matching
238
+ </\2> // the matching end tag
239
+ [ \t]* // trailing spaces/tabs
240
+ (?=\n+) // followed by a newline
241
+ ) // attacklab: there are sentinel newlines at end of document
242
+ /gm,function(){...}};
243
+ */
244
+ text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement);
245
+
246
+ //
247
+ // Now match more liberally, simply from `\n<tag>` to `</tag>\n`
248
+ //
249
+
250
+ /*
251
+ var text = text.replace(/
252
+ ( // save in $1
253
+ ^ // start of line (with /m)
254
+ <($block_tags_b) // start tag = $2
255
+ \b // word break
256
+ // attacklab: hack around khtml/pcre bug...
257
+ [^\r]*? // any number of lines, minimally matching
258
+ .*</\2> // the matching end tag
259
+ [ \t]* // trailing spaces/tabs
260
+ (?=\n+) // followed by a newline
261
+ ) // attacklab: there are sentinel newlines at end of document
262
+ /gm,function(){...}};
263
+ */
264
+ text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement);
265
+
266
+ // Special case just for <hr />. It was easier to make a special case than
267
+ // to make the other regex more complicated.
268
+
269
+ /*
270
+ text = text.replace(/
271
+ ( // save in $1
272
+ \n\n // Starting after a blank line
273
+ [ ]{0,3}
274
+ (<(hr) // start tag = $2
275
+ \b // word break
276
+ ([^<>])*? //
277
+ \/?>) // the matching end tag
278
+ [ \t]*
279
+ (?=\n{2,}) // followed by a blank line
280
+ )
281
+ /g,hashElement);
282
+ */
283
+ text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement);
284
+
285
+ // Special case for standalone HTML comments:
286
+
287
+ /*
288
+ text = text.replace(/
289
+ ( // save in $1
290
+ \n\n // Starting after a blank line
291
+ [ ]{0,3} // attacklab: g_tab_width - 1
292
+ <!
293
+ (--[^\r]*?--\s*)+
294
+ >
295
+ [ \t]*
296
+ (?=\n{2,}) // followed by a blank line
297
+ )
298
+ /g,hashElement);
299
+ */
300
+ text = text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,hashElement);
301
+
302
+ // PHP and ASP-style processor instructions (<?...?> and <%...%>)
303
+
304
+ /*
305
+ text = text.replace(/
306
+ (?:
307
+ \n\n // Starting after a blank line
308
+ )
309
+ ( // save in $1
310
+ [ ]{0,3} // attacklab: g_tab_width - 1
311
+ (?:
312
+ <([?%]) // $2
313
+ [^\r]*?
314
+ \2>
315
+ )
316
+ [ \t]*
317
+ (?=\n{2,}) // followed by a blank line
318
+ )
319
+ /g,hashElement);
320
+ */
321
+ text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement);
322
+
323
+ // attacklab: Undo double lines (see comment at top of this function)
324
+ text = text.replace(/\n\n/g,"\n");
325
+ return text;
326
+ }
327
+
328
+ var hashElement = function(wholeMatch,m1) {
329
+ var blockText = m1;
330
+
331
+ // Undo double lines
332
+ blockText = blockText.replace(/\n\n/g,"\n");
333
+ blockText = blockText.replace(/^\n/,"");
334
+
335
+ // strip trailing blank lines
336
+ blockText = blockText.replace(/\n+$/g,"");
337
+
338
+ // Replace the element text with a marker ("~KxK" where x is its key)
339
+ blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n";
340
+
341
+ return blockText;
342
+ };
343
+
344
+ var _RunBlockGamut = function(text) {
345
+ //
346
+ // These are all the transformations that form block-level
347
+ // tags like paragraphs, headers, and list items.
348
+ //
349
+ text = _DoHeaders(text);
350
+
351
+ // Do Horizontal Rules:
352
+ var key = hashBlock("<hr />");
353
+ text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);
354
+ text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);
355
+ text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);
356
+
357
+ text = _DoLists(text);
358
+ text = _DoCodeBlocks(text);
359
+ text = _DoBlockQuotes(text);
360
+
361
+ // We already ran _HashHTMLBlocks() before, in Markdown(), but that
362
+ // was to escape raw HTML in the original Markdown source. This time,
363
+ // we're escaping the markup we've just created, so that we don't wrap
364
+ // <p> tags around block-level tags.
365
+ text = _HashHTMLBlocks(text);
366
+ text = _FormParagraphs(text);
367
+
368
+ return text;
369
+ }
370
+
371
+
372
+ var _RunSpanGamut = function(text) {
373
+ //
374
+ // These are all the transformations that occur *within* block-level
375
+ // tags like paragraphs, headers, and list items.
376
+ //
377
+
378
+ text = _DoCodeSpans(text);
379
+ text = _EscapeSpecialCharsWithinTagAttributes(text);
380
+ text = _EncodeBackslashEscapes(text);
381
+
382
+ // Process anchor and image tags. Images must come first,
383
+ // because ![foo][f] looks like an anchor.
384
+ text = _DoImages(text);
385
+ text = _DoAnchors(text);
386
+
387
+ // Make links out of things like `<http://example.com/>`
388
+ // Must come after _DoAnchors(), because you can use < and >
389
+ // delimiters in inline links like [this](<url>).
390
+ text = _DoAutoLinks(text);
391
+ text = _EncodeAmpsAndAngles(text);
392
+ text = _DoItalicsAndBold(text);
393
+
394
+ // Do hard breaks:
395
+ text = text.replace(/ +\n/g," <br />\n");
396
+
397
+ return text;
398
+ }
399
+
400
+ var _EscapeSpecialCharsWithinTagAttributes = function(text) {
401
+ //
402
+ // Within tags -- meaning between < and > -- encode [\ ` * _] so they
403
+ // don't conflict with their use in Markdown for code, italics and strong.
404
+ //
405
+
406
+ // Build a regex to find HTML tags and comments. See Friedl's
407
+ // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
408
+ var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
409
+
410
+ text = text.replace(regex, function(wholeMatch) {
411
+ var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");
412
+ tag = escapeCharacters(tag,"\\`*_");
413
+ return tag;
414
+ });
415
+
416
+ return text;
417
+ }
418
+
419
+ var _DoAnchors = function(text) {
420
+ //
421
+ // Turn Markdown link shortcuts into XHTML <a> tags.
422
+ //
423
+ //
424
+ // First, handle reference-style links: [link text] [id]
425
+ //
426
+
427
+ /*
428
+ text = text.replace(/
429
+ ( // wrap whole match in $1
430
+ \[
431
+ (
432
+ (?:
433
+ \[[^\]]*\] // allow brackets nested one level
434
+ |
435
+ [^\[] // or anything else
436
+ )*
437
+ )
438
+ \]
439
+
440
+ [ ]? // one optional space
441
+ (?:\n[ ]*)? // one optional newline followed by spaces
442
+
443
+ \[
444
+ (.*?) // id = $3
445
+ \]
446
+ )()()()() // pad remaining backreferences
447
+ /g,_DoAnchors_callback);
448
+ */
449
+ text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);
450
+
451
+ //
452
+ // Next, inline-style links: [link text](url "optional title")
453
+ //
454
+
455
+ /*
456
+ text = text.replace(/
457
+ ( // wrap whole match in $1
458
+ \[
459
+ (
460
+ (?:
461
+ \[[^\]]*\] // allow brackets nested one level
462
+ |
463
+ [^\[\]] // or anything else
464
+ )
465
+ )
466
+ \]
467
+ \( // literal paren
468
+ [ \t]*
469
+ () // no id, so leave $3 empty
470
+ <?(.*?)>? // href = $4
471
+ [ \t]*
472
+ ( // $5
473
+ (['"]) // quote char = $6
474
+ (.*?) // Title = $7
475
+ \6 // matching quote
476
+ [ \t]* // ignore any spaces/tabs between closing quote and )
477
+ )? // title is optional
478
+ \)
479
+ )
480
+ /g,writeAnchorTag);
481
+ */
482
+ text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);
483
+
484
+ //
485
+ // Last, handle reference-style shortcuts: [link text]
486
+ // These must come last in case you've also got [link test][1]
487
+ // or [link test](/foo)
488
+ //
489
+
490
+ /*
491
+ text = text.replace(/
492
+ ( // wrap whole match in $1
493
+ \[
494
+ ([^\[\]]+) // link text = $2; can't contain '[' or ']'
495
+ \]
496
+ )()()()()() // pad rest of backreferences
497
+ /g, writeAnchorTag);
498
+ */
499
+ text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
500
+
501
+ return text;
502
+ }
503
+
504
+ var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
505
+ if (m7 == undefined) m7 = "";
506
+ var whole_match = m1;
507
+ var link_text = m2;
508
+ var link_id = m3.toLowerCase();
509
+ var url = m4;
510
+ var title = m7;
511
+
512
+ if (url == "") {
513
+ if (link_id == "") {
514
+ // lower-case and turn embedded newlines into spaces
515
+ link_id = link_text.toLowerCase().replace(/ ?\n/g," ");
516
+ }
517
+ url = "#"+link_id;
518
+
519
+ if (g_urls[link_id] != undefined) {
520
+ url = g_urls[link_id];
521
+ if (g_titles[link_id] != undefined) {
522
+ title = g_titles[link_id];
523
+ }
524
+ }
525
+ else {
526
+ if (whole_match.search(/\(\s*\)$/m)>-1) {
527
+ // Special case for explicit empty url
528
+ url = "";
529
+ } else {
530
+ return whole_match;
531
+ }
532
+ }
533
+ }
534
+
535
+ url = escapeCharacters(url,"*_");
536
+ var result = "<a href=\"" + url + "\"";
537
+
538
+ if (title != "") {
539
+ title = title.replace(/"/g,"&quot;");
540
+ title = escapeCharacters(title,"*_");
541
+ result += " title=\"" + title + "\"";
542
+ }
543
+
544
+ result += ">" + link_text + "</a>";
545
+
546
+ return result;
547
+ }
548
+
549
+
550
+ var _DoImages = function(text) {
551
+ //
552
+ // Turn Markdown image shortcuts into <img> tags.
553
+ //
554
+
555
+ //
556
+ // First, handle reference-style labeled images: ![alt text][id]
557
+ //
558
+
559
+ /*
560
+ text = text.replace(/
561
+ ( // wrap whole match in $1
562
+ !\[
563
+ (.*?) // alt text = $2
564
+ \]
565
+
566
+ [ ]? // one optional space
567
+ (?:\n[ ]*)? // one optional newline followed by spaces
568
+
569
+ \[
570
+ (.*?) // id = $3
571
+ \]
572
+ )()()()() // pad rest of backreferences
573
+ /g,writeImageTag);
574
+ */
575
+ text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);
576
+
577
+ //
578
+ // Next, handle inline images: ![alt text](url "optional title")
579
+ // Don't forget: encode * and _
580
+
581
+ /*
582
+ text = text.replace(/
583
+ ( // wrap whole match in $1
584
+ !\[
585
+ (.*?) // alt text = $2
586
+ \]
587
+ \s? // One optional whitespace character
588
+ \( // literal paren
589
+ [ \t]*
590
+ () // no id, so leave $3 empty
591
+ <?(\S+?)>? // src url = $4
592
+ [ \t]*
593
+ ( // $5
594
+ (['"]) // quote char = $6
595
+ (.*?) // title = $7
596
+ \6 // matching quote
597
+ [ \t]*
598
+ )? // title is optional
599
+ \)
600
+ )
601
+ /g,writeImageTag);
602
+ */
603
+ text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);
604
+
605
+ return text;
606
+ }
607
+
608
+ var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
609
+ var whole_match = m1;
610
+ var alt_text = m2;
611
+ var link_id = m3.toLowerCase();
612
+ var url = m4;
613
+ var title = m7;
614
+
615
+ if (!title) title = "";
616
+
617
+ if (url == "") {
618
+ if (link_id == "") {
619
+ // lower-case and turn embedded newlines into spaces
620
+ link_id = alt_text.toLowerCase().replace(/ ?\n/g," ");
621
+ }
622
+ url = "#"+link_id;
623
+
624
+ if (g_urls[link_id] != undefined) {
625
+ url = g_urls[link_id];
626
+ if (g_titles[link_id] != undefined) {
627
+ title = g_titles[link_id];
628
+ }
629
+ }
630
+ else {
631
+ return whole_match;
632
+ }
633
+ }
634
+
635
+ alt_text = alt_text.replace(/"/g,"&quot;");
636
+ url = escapeCharacters(url,"*_");
637
+ var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";
638
+
639
+ // attacklab: Markdown.pl adds empty title attributes to images.
640
+ // Replicate this bug.
641
+
642
+ //if (title != "") {
643
+ title = title.replace(/"/g,"&quot;");
644
+ title = escapeCharacters(title,"*_");
645
+ result += " title=\"" + title + "\"";
646
+ //}
647
+
648
+ result += " />";
649
+
650
+ return result;
651
+ }
652
+
653
+
654
+ var _DoHeaders = function(text) {
655
+
656
+ // Setext-style headers:
657
+ // Header 1
658
+ // ========
659
+ //
660
+ // Header 2
661
+ // --------
662
+ //
663
+ text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
664
+ function(wholeMatch,m1){return hashBlock("<h1>" + _RunSpanGamut(m1) + "</h1>");});
665
+
666
+ text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
667
+ function(matchFound,m1){return hashBlock("<h2>" + _RunSpanGamut(m1) + "</h2>");});
668
+
669
+ // atx-style headers:
670
+ // # Header 1
671
+ // ## Header 2
672
+ // ## Header 2 with closing hashes ##
673
+ // ...
674
+ // ###### Header 6
675
+ //
676
+
677
+ /*
678
+ text = text.replace(/
679
+ ^(\#{1,6}) // $1 = string of #'s
680
+ [ \t]*
681
+ (.+?) // $2 = Header text
682
+ [ \t]*
683
+ \#* // optional closing #'s (not counted)
684
+ \n+
685
+ /gm, function() {...});
686
+ */
687
+
688
+ text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
689
+ function(wholeMatch,m1,m2) {
690
+ var h_level = m1.length;
691
+ return hashBlock("<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">");
692
+ });
693
+
694
+ return text;
695
+ }
696
+
697
+ // This declaration keeps Dojo compressor from outputting garbage:
698
+ var _ProcessListItems;
699
+
700
+ var _DoLists = function(text) {
701
+ //
702
+ // Form HTML ordered (numbered) and unordered (bulleted) lists.
703
+ //
704
+
705
+ // attacklab: add sentinel to hack around khtml/safari bug:
706
+ // http://bugs.webkit.org/show_bug.cgi?id=11231
707
+ text += "~0";
708
+
709
+ // Re-usable pattern to match any entirel ul or ol list:
710
+
711
+ /*
712
+ var whole_list = /
713
+ ( // $1 = whole list
714
+ ( // $2
715
+ [ ]{0,3} // attacklab: g_tab_width - 1
716
+ ([*+-]|\d+[.]) // $3 = first list item marker
717
+ [ \t]+
718
+ )
719
+ [^\r]+?
720
+ ( // $4
721
+ ~0 // sentinel for workaround; should be $
722
+ |
723
+ \n{2,}
724
+ (?=\S)
725
+ (?! // Negative lookahead for another list item marker
726
+ [ \t]*
727
+ (?:[*+-]|\d+[.])[ \t]+
728
+ )
729
+ )
730
+ )/g
731
+ */
732
+ var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
733
+
734
+ if (g_list_level) {
735
+ text = text.replace(whole_list,function(wholeMatch,m1,m2) {
736
+ var list = m1;
737
+ var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol";
738
+
739
+ // Turn double returns into triple returns, so that we can make a
740
+ // paragraph for the last item in a list, if necessary:
741
+ list = list.replace(/\n{2,}/g,"\n\n\n");;
742
+ var result = _ProcessListItems(list);
743
+
744
+ // Trim any trailing whitespace, to put the closing `</$list_type>`
745
+ // up on the preceding line, to get it past the current stupid
746
+ // HTML block parser. This is a hack to work around the terrible
747
+ // hack that is the HTML block parser.
748
+ result = result.replace(/\s+$/,"");
749
+ result = "<"+list_type+">" + result + "</"+list_type+">\n";
750
+ return result;
751
+ });
752
+ } else {
753
+ whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
754
+ text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) {
755
+ var runup = m1;
756
+ var list = m2;
757
+
758
+ var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol";
759
+ // Turn double returns into triple returns, so that we can make a
760
+ // paragraph for the last item in a list, if necessary:
761
+ var list = list.replace(/\n{2,}/g,"\n\n\n");;
762
+ var result = _ProcessListItems(list);
763
+ result = runup + "<"+list_type+">\n" + result + "</"+list_type+">\n";
764
+ return result;
765
+ });
766
+ }
767
+
768
+ // attacklab: strip sentinel
769
+ text = text.replace(/~0/,"");
770
+
771
+ return text;
772
+ }
773
+
774
+ _ProcessListItems = function(list_str) {
775
+ //
776
+ // Process the contents of a single ordered or unordered list, splitting it
777
+ // into individual list items.
778
+ //
779
+ // The $g_list_level global keeps track of when we're inside a list.
780
+ // Each time we enter a list, we increment it; when we leave a list,
781
+ // we decrement. If it's zero, we're not in a list anymore.
782
+ //
783
+ // We do this because when we're not inside a list, we want to treat
784
+ // something like this:
785
+ //
786
+ // I recommend upgrading to version
787
+ // 8. Oops, now this line is treated
788
+ // as a sub-list.
789
+ //
790
+ // As a single paragraph, despite the fact that the second line starts
791
+ // with a digit-period-space sequence.
792
+ //
793
+ // Whereas when we're inside a list (or sub-list), that line will be
794
+ // treated as the start of a sub-list. What a kludge, huh? This is
795
+ // an aspect of Markdown's syntax that's hard to parse perfectly
796
+ // without resorting to mind-reading. Perhaps the solution is to
797
+ // change the syntax rules such that sub-lists must start with a
798
+ // starting cardinal number; e.g. "1." or "a.".
799
+
800
+ g_list_level++;
801
+
802
+ // trim trailing blank lines:
803
+ list_str = list_str.replace(/\n{2,}$/,"\n");
804
+
805
+ // attacklab: add sentinel to emulate \z
806
+ list_str += "~0";
807
+
808
+ /*
809
+ list_str = list_str.replace(/
810
+ (\n)? // leading line = $1
811
+ (^[ \t]*) // leading whitespace = $2
812
+ ([*+-]|\d+[.]) [ \t]+ // list marker = $3
813
+ ([^\r]+? // list item text = $4
814
+ (\n{1,2}))
815
+ (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))
816
+ /gm, function(){...});
817
+ */
818
+ list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
819
+ function(wholeMatch,m1,m2,m3,m4){
820
+ var item = m4;
821
+ var leading_line = m1;
822
+ var leading_space = m2;
823
+
824
+ if (leading_line || (item.search(/\n{2,}/)>-1)) {
825
+ item = _RunBlockGamut(_Outdent(item));
826
+ }
827
+ else {
828
+ // Recursion for sub-lists:
829
+ item = _DoLists(_Outdent(item));
830
+ item = item.replace(/\n$/,""); // chomp(item)
831
+ item = _RunSpanGamut(item);
832
+ }
833
+
834
+ return "<li>" + item + "</li>\n";
835
+ }
836
+ );
837
+
838
+ // attacklab: strip sentinel
839
+ list_str = list_str.replace(/~0/g,"");
840
+
841
+ g_list_level--;
842
+ return list_str;
843
+ }
844
+
845
+
846
+ var _DoCodeBlocks = function(text) {
847
+ //
848
+ // Process Markdown `<pre><code>` blocks.
849
+ //
850
+
851
+ /*
852
+ text = text.replace(text,
853
+ /(?:\n\n|^)
854
+ ( // $1 = the code block -- one or more lines, starting with a space/tab
855
+ (?:
856
+ (?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
857
+ .*\n+
858
+ )+
859
+ )
860
+ (\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width
861
+ /g,function(){...});
862
+ */
863
+
864
+ // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
865
+ text += "~0";
866
+
867
+ text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
868
+ function(wholeMatch,m1,m2) {
869
+ var codeblock = m1;
870
+ var nextChar = m2;
871
+
872
+ codeblock = _EncodeCode( _Outdent(codeblock));
873
+ codeblock = _Detab(codeblock);
874
+ codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
875
+ codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
876
+
877
+ codeblock = "<pre><code>" + codeblock + "\n</code></pre>";
878
+
879
+ return hashBlock(codeblock) + nextChar;
880
+ }
881
+ );
882
+
883
+ // attacklab: strip sentinel
884
+ text = text.replace(/~0/,"");
885
+
886
+ return text;
887
+ }
888
+
889
+ var hashBlock = function(text) {
890
+ text = text.replace(/(^\n+|\n+$)/g,"");
891
+ return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n";
892
+ }
893
+
894
+
895
+ var _DoCodeSpans = function(text) {
896
+ //
897
+ // * Backtick quotes are used for <code></code> spans.
898
+ //
899
+ // * You can use multiple backticks as the delimiters if you want to
900
+ // include literal backticks in the code span. So, this input:
901
+ //
902
+ // Just type ``foo `bar` baz`` at the prompt.
903
+ //
904
+ // Will translate to:
905
+ //
906
+ // <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
907
+ //
908
+ // There's no arbitrary limit to the number of backticks you
909
+ // can use as delimters. If you need three consecutive backticks
910
+ // in your code, use four for delimiters, etc.
911
+ //
912
+ // * You can use spaces to get literal backticks at the edges:
913
+ //
914
+ // ... type `` `bar` `` ...
915
+ //
916
+ // Turns to:
917
+ //
918
+ // ... type <code>`bar`</code> ...
919
+ //
920
+
921
+ /*
922
+ text = text.replace(/
923
+ (^|[^\\]) // Character before opening ` can't be a backslash
924
+ (`+) // $2 = Opening run of `
925
+ ( // $3 = The code block
926
+ [^\r]*?
927
+ [^`] // attacklab: work around lack of lookbehind
928
+ )
929
+ \2 // Matching closer
930
+ (?!`)
931
+ /gm, function(){...});
932
+ */
933
+
934
+ text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
935
+ function(wholeMatch,m1,m2,m3,m4) {
936
+ var c = m3;
937
+ c = c.replace(/^([ \t]*)/g,""); // leading whitespace
938
+ c = c.replace(/[ \t]*$/g,""); // trailing whitespace
939
+ c = _EncodeCode(c);
940
+ return m1+"<code>"+c+"</code>";
941
+ });
942
+
943
+ return text;
944
+ }
945
+
946
+
947
+ var _EncodeCode = function(text) {
948
+ //
949
+ // Encode/escape certain characters inside Markdown code runs.
950
+ // The point is that in code, these characters are literals,
951
+ // and lose their special Markdown meanings.
952
+ //
953
+ // Encode all ampersands; HTML entities are not
954
+ // entities within a Markdown code span.
955
+ text = text.replace(/&/g,"&amp;");
956
+
957
+ // Do the angle bracket song and dance:
958
+ text = text.replace(/</g,"&lt;");
959
+ text = text.replace(/>/g,"&gt;");
960
+
961
+ // Now, escape characters that are magic in Markdown:
962
+ text = escapeCharacters(text,"\*_{}[]\\",false);
963
+
964
+ // jj the line above breaks this:
965
+ //---
966
+
967
+ //* Item
968
+
969
+ // 1. Subitem
970
+
971
+ // special char: *
972
+ //---
973
+
974
+ return text;
975
+ }
976
+
977
+
978
+ var _DoItalicsAndBold = function(text) {
979
+
980
+ // <strong> must go first:
981
+ text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,
982
+ "<strong>$2</strong>");
983
+
984
+ text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,
985
+ "<em>$2</em>");
986
+
987
+ return text;
988
+ }
989
+
990
+
991
+ var _DoBlockQuotes = function(text) {
992
+
993
+ /*
994
+ text = text.replace(/
995
+ ( // Wrap whole match in $1
996
+ (
997
+ ^[ \t]*>[ \t]? // '>' at the start of a line
998
+ .+\n // rest of the first line
999
+ (.+\n)* // subsequent consecutive lines
1000
+ \n* // blanks
1001
+ )+
1002
+ )
1003
+ /gm, function(){...});
1004
+ */
1005
+
1006
+ text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
1007
+ function(wholeMatch,m1) {
1008
+ var bq = m1;
1009
+
1010
+ // attacklab: hack around Konqueror 3.5.4 bug:
1011
+ // "----------bug".replace(/^-/g,"") == "bug"
1012
+
1013
+ bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting
1014
+
1015
+ // attacklab: clean up hack
1016
+ bq = bq.replace(/~0/g,"");
1017
+
1018
+ bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines
1019
+ bq = _RunBlockGamut(bq); // recurse
1020
+
1021
+ bq = bq.replace(/(^|\n)/g,"$1 ");
1022
+ // These leading spaces screw with <pre> content, so we need to fix that:
1023
+ bq = bq.replace(
1024
+ /(\s*<pre>[^\r]+?<\/pre>)/gm,
1025
+ function(wholeMatch,m1) {
1026
+ var pre = m1;
1027
+ // attacklab: hack around Konqueror 3.5.4 bug:
1028
+ pre = pre.replace(/^ /mg,"~0");
1029
+ pre = pre.replace(/~0/g,"");
1030
+ return pre;
1031
+ });
1032
+
1033
+ return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");
1034
+ });
1035
+ return text;
1036
+ }
1037
+
1038
+
1039
+ var _FormParagraphs = function(text) {
1040
+ //
1041
+ // Params:
1042
+ // $text - string to process with html <p> tags
1043
+ //
1044
+
1045
+ // Strip leading and trailing lines:
1046
+ text = text.replace(/^\n+/g,"");
1047
+ text = text.replace(/\n+$/g,"");
1048
+
1049
+ var grafs = text.split(/\n{2,}/g);
1050
+ var grafsOut = new Array();
1051
+
1052
+ //
1053
+ // Wrap <p> tags.
1054
+ //
1055
+ var end = grafs.length;
1056
+ for (var i=0; i<end; i++) {
1057
+ var str = grafs[i];
1058
+
1059
+ // if this is an HTML marker, copy it
1060
+ if (str.search(/~K(\d+)K/g) >= 0) {
1061
+ grafsOut.push(str);
1062
+ }
1063
+ else if (str.search(/\S/) >= 0) {
1064
+ str = _RunSpanGamut(str);
1065
+ str = str.replace(/^([ \t]*)/g,"<p>");
1066
+ str += "</p>"
1067
+ grafsOut.push(str);
1068
+ }
1069
+
1070
+ }
1071
+
1072
+ //
1073
+ // Unhashify HTML blocks
1074
+ //
1075
+ end = grafsOut.length;
1076
+ for (var i=0; i<end; i++) {
1077
+ // if this is a marker for an html block...
1078
+ while (grafsOut[i].search(/~K(\d+)K/) >= 0) {
1079
+ var blockText = g_html_blocks[RegExp.$1];
1080
+ blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs
1081
+ grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText);
1082
+ }
1083
+ }
1084
+
1085
+ return grafsOut.join("\n\n");
1086
+ }
1087
+
1088
+
1089
+ var _EncodeAmpsAndAngles = function(text) {
1090
+ // Smart processing for ampersands and angle brackets that need to be encoded.
1091
+
1092
+ // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
1093
+ // http://bumppo.net/projects/amputator/
1094
+ text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&amp;");
1095
+
1096
+ // Encode naked <'s
1097
+ text = text.replace(/<(?![a-z\/?\$!])/gi,"&lt;");
1098
+
1099
+ return text;
1100
+ }
1101
+
1102
+
1103
+ var _EncodeBackslashEscapes = function(text) {
1104
+ //
1105
+ // Parameter: String.
1106
+ // Returns: The string, with after processing the following backslash
1107
+ // escape sequences.
1108
+ //
1109
+
1110
+ // attacklab: The polite way to do this is with the new
1111
+ // escapeCharacters() function:
1112
+ //
1113
+ // text = escapeCharacters(text,"\\",true);
1114
+ // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
1115
+ //
1116
+ // ...but we're sidestepping its use of the (slow) RegExp constructor
1117
+ // as an optimization for Firefox. This function gets called a LOT.
1118
+
1119
+ text = text.replace(/\\(\\)/g,escapeCharacters_callback);
1120
+ text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback);
1121
+ return text;
1122
+ }
1123
+
1124
+
1125
+ var _DoAutoLinks = function(text) {
1126
+
1127
+ text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");
1128
+
1129
+ // Email addresses: <address@domain.foo>
1130
+
1131
+ /*
1132
+ text = text.replace(/
1133
+ <
1134
+ (?:mailto:)?
1135
+ (
1136
+ [-.\w]+
1137
+ \@
1138
+ [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
1139
+ )
1140
+ >
1141
+ /gi, _DoAutoLinks_callback());
1142
+ */
1143
+ text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
1144
+ function(wholeMatch,m1) {
1145
+ return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
1146
+ }
1147
+ );
1148
+
1149
+ return text;
1150
+ }
1151
+
1152
+
1153
+ var _EncodeEmailAddress = function(addr) {
1154
+ //
1155
+ // Input: an email address, e.g. "foo@example.com"
1156
+ //
1157
+ // Output: the email address as a mailto link, with each character
1158
+ // of the address encoded as either a decimal or hex entity, in
1159
+ // the hopes of foiling most address harvesting spam bots. E.g.:
1160
+ //
1161
+ // <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
1162
+ // x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
1163
+ // &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
1164
+ //
1165
+ // Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
1166
+ // mailing list: <http://tinyurl.com/yu7ue>
1167
+ //
1168
+
1169
+ // attacklab: why can't javascript speak hex?
1170
+ function char2hex(ch) {
1171
+ var hexDigits = '0123456789ABCDEF';
1172
+ var dec = ch.charCodeAt(0);
1173
+ return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15));
1174
+ }
1175
+
1176
+ var encode = [
1177
+ function(ch){return "&#"+ch.charCodeAt(0)+";";},
1178
+ function(ch){return "&#x"+char2hex(ch)+";";},
1179
+ function(ch){return ch;}
1180
+ ];
1181
+
1182
+ addr = "mailto:" + addr;
1183
+
1184
+ addr = addr.replace(/./g, function(ch) {
1185
+ if (ch == "@") {
1186
+ // this *must* be encoded. I insist.
1187
+ ch = encode[Math.floor(Math.random()*2)](ch);
1188
+ } else if (ch !=":") {
1189
+ // leave ':' alone (to spot mailto: later)
1190
+ var r = Math.random();
1191
+ // roughly 10% raw, 45% hex, 45% dec
1192
+ ch = (
1193
+ r > .9 ? encode[2](ch) :
1194
+ r > .45 ? encode[1](ch) :
1195
+ encode[0](ch)
1196
+ );
1197
+ }
1198
+ return ch;
1199
+ });
1200
+
1201
+ addr = "<a href=\"" + addr + "\">" + addr + "</a>";
1202
+ addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part
1203
+
1204
+ return addr;
1205
+ }
1206
+
1207
+
1208
+ var _UnescapeSpecialChars = function(text) {
1209
+ //
1210
+ // Swap back in all the special characters we've hidden.
1211
+ //
1212
+ text = text.replace(/~E(\d+)E/g,
1213
+ function(wholeMatch,m1) {
1214
+ var charCodeToReplace = parseInt(m1);
1215
+ return String.fromCharCode(charCodeToReplace);
1216
+ }
1217
+ );
1218
+ return text;
1219
+ }
1220
+
1221
+
1222
+ var _Outdent = function(text) {
1223
+ //
1224
+ // Remove one level of line-leading tabs or spaces
1225
+ //
1226
+
1227
+ // attacklab: hack around Konqueror 3.5.4 bug:
1228
+ // "----------bug".replace(/^-/g,"") == "bug"
1229
+
1230
+ text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width
1231
+
1232
+ // attacklab: clean up hack
1233
+ text = text.replace(/~0/g,"")
1234
+
1235
+ return text;
1236
+ }
1237
+
1238
+ var _Detab = function(text) {
1239
+ // attacklab: Detab's completely rewritten for speed.
1240
+ // In perl we could fix it by anchoring the regexp with \G.
1241
+ // In javascript we're less fortunate.
1242
+
1243
+ // expand first n-1 tabs
1244
+ text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width
1245
+
1246
+ // replace the nth with two sentinels
1247
+ text = text.replace(/\t/g,"~A~B");
1248
+
1249
+ // use the sentinel to anchor our regex so it doesn't explode
1250
+ text = text.replace(/~B(.+?)~A/g,
1251
+ function(wholeMatch,m1,m2) {
1252
+ var leadingText = m1;
1253
+ var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width
1254
+
1255
+ // there *must* be a better way to do this:
1256
+ for (var i=0; i<numSpaces; i++) leadingText+=" ";
1257
+
1258
+ return leadingText;
1259
+ }
1260
+ );
1261
+
1262
+ // clean up sentinels
1263
+ text = text.replace(/~A/g," "); // attacklab: g_tab_width
1264
+ text = text.replace(/~B/g,"");
1265
+
1266
+ return text;
1267
+ }
1268
+
1269
+
1270
+ //
1271
+ // attacklab: Utility functions
1272
+ //
1273
+
1274
+
1275
+ var escapeCharacters = function(text, charsToEscape, afterBackslash) {
1276
+ // First we have to escape the escape characters so that
1277
+ // we can build a character class out of them
1278
+ var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g,"\\$1") + "])";
1279
+
1280
+ if (afterBackslash) {
1281
+ regexString = "\\\\" + regexString;
1282
+ }
1283
+
1284
+ var regex = new RegExp(regexString,"g");
1285
+ text = text.replace(regex,escapeCharacters_callback);
1286
+
1287
+ return text;
1288
+ }
1289
+
1290
+
1291
+ var escapeCharacters_callback = function(wholeMatch,m1) {
1292
+ var charCodeToEscape = m1.charCodeAt(0);
1293
+ return "~E"+charCodeToEscape+"E";
1294
+ }
1295
+
1296
+ }; // end of Showdown.converter
1297
+
1298
+
1299
+ // ----------------------------------------------------------------------------
1300
+ // markItUp! Universal MarkUp Engine, JQuery plugin
1301
+ // v 1.1.x
1302
+ // Dual licensed under the MIT and GPL licenses.
1303
+ // ----------------------------------------------------------------------------
1304
+ // Copyright (C) 2007-2010 Jay Salvat
1305
+ // http://markitup.jaysalvat.com/
1306
+ // ----------------------------------------------------------------------------
1307
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1308
+ // of this software and associated documentation files (the "Software"), to deal
1309
+ // in the Software without restriction, including without limitation the rights
1310
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1311
+ // copies of the Software, and to permit persons to whom the Software is
1312
+ // furnished to do so, subject to the following conditions:
1313
+ //
1314
+ // The above copyright notice and this permission notice shall be included in
1315
+ // all copies or substantial portions of the Software.
1316
+ //
1317
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1318
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1319
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1320
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1321
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1322
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1323
+ // THE SOFTWARE.
1324
+ // ----------------------------------------------------------------------------
1325
+ (function($) {
1326
+ $.fn.markItUp = function(settings, extraSettings) {
1327
+ var converter = new Showdown.converter();
1328
+ var options, ctrlKey, shiftKey, altKey;
1329
+ ctrlKey = shiftKey = altKey = false;
1330
+
1331
+ options = { id: '',
1332
+ nameSpace: '',
1333
+ root: '',
1334
+ previewInWindow: '', // 'width=800, height=600, resizable=yes, scrollbars=yes'
1335
+ previewAutoRefresh: true,
1336
+ previewPosition: 'after',
1337
+ previewTemplatePath: '~/templates/preview.html',
1338
+ previewParserPath: '',
1339
+ previewParserVar: 'data',
1340
+ resizeHandle: true,
1341
+ beforeInsert: '',
1342
+ afterInsert: '',
1343
+ onEnter: {},
1344
+ onShiftEnter: {},
1345
+ onCtrlEnter: {},
1346
+ onTab: {},
1347
+ markupSet: [ { /* set */ } ]
1348
+ };
1349
+ $.extend(options, settings, extraSettings);
1350
+
1351
+ // compute markItUp! path
1352
+ if (!options.root) {
1353
+ $('script').each(function(a, tag) {
1354
+ miuScript = $(tag).get(0).src.match(/(.*)jquery\.markitup(\.pack)?\.js$/);
1355
+ if (miuScript !== null) {
1356
+ options.root = miuScript[1];
1357
+ }
1358
+ });
1359
+ }
1360
+
1361
+ return this.each(function() {
1362
+ var $$, textarea, levels, scrollPosition, caretPosition, caretOffset,
1363
+ clicked, hash, header, footer, previewWindow, template, previewDiv, abort;
1364
+ $$ = $(this);
1365
+ textarea = this;
1366
+ levels = [];
1367
+ abort = false;
1368
+ scrollPosition = caretPosition = 0;
1369
+ caretOffset = -1;
1370
+
1371
+ options.previewParserPath = localize(options.previewParserPath);
1372
+ options.previewTemplatePath = localize(options.previewTemplatePath);
1373
+
1374
+ // apply the computed path to ~/
1375
+ function localize(data, inText) {
1376
+ if (inText) {
1377
+ return data.replace(/("|')~\//g, "$1"+options.root);
1378
+ }
1379
+ return data.replace(/^~\//, options.root);
1380
+ }
1381
+
1382
+ // init and build editor
1383
+ function init() {
1384
+ id = ''; nameSpace = '';
1385
+ if (options.id) {
1386
+ id = 'id="'+options.id+'"';
1387
+ } else if ($$.attr("id")) {
1388
+ id = 'id="markItUp'+($$.attr("id").substr(0, 1).toUpperCase())+($$.attr("id").substr(1))+'"';
1389
+
1390
+ }
1391
+ if (options.nameSpace) {
1392
+ nameSpace = 'class="'+options.nameSpace+'"';
1393
+ }
1394
+ $$.wrap('<div '+nameSpace+'></div>');
1395
+ $$.wrap('<div '+id+' class="markItUp"></div>');
1396
+ $$.wrap('<div class="markItUpContainer"></div>');
1397
+ $$.addClass("markItUpEditor");
1398
+
1399
+ // add the header before the textarea
1400
+ header = $('<div class="markItUpHeader"></div>').insertBefore($$);
1401
+ $(dropMenus(options.markupSet)).appendTo(header);
1402
+
1403
+ // add the footer after the textarea
1404
+ footer = $('<div class="markItUpFooter"></div>').insertAfter($$);
1405
+
1406
+ // add the resize handle after textarea
1407
+ if (options.resizeHandle === true && $.browser.safari !== true) {
1408
+ resizeHandle = $('<div class="markItUpResizeHandle"></div>')
1409
+ .insertAfter($$)
1410
+ .bind("mousedown", function(e) {
1411
+ var h = $$.height(), y = e.clientY, mouseMove, mouseUp;
1412
+ mouseMove = function(e) {
1413
+ $$.css("height", Math.max(20, e.clientY+h-y)+"px");
1414
+ return false;
1415
+ };
1416
+ mouseUp = function(e) {
1417
+ $("html").unbind("mousemove", mouseMove).unbind("mouseup", mouseUp);
1418
+ return false;
1419
+ };
1420
+ $("html").bind("mousemove", mouseMove).bind("mouseup", mouseUp);
1421
+ });
1422
+ footer.append(resizeHandle);
1423
+ }
1424
+
1425
+ // listen key events
1426
+ $$.keydown(keyPressed).keyup(keyPressed);
1427
+
1428
+ // bind an event to catch external calls
1429
+ $$.bind("insertion", function(e, settings) {
1430
+ if (settings.target !== false) {
1431
+ get();
1432
+ }
1433
+ if (textarea === $.markItUp.focused) {
1434
+ markup(settings);
1435
+ }
1436
+ });
1437
+
1438
+ if (options.previewAutoRefresh) {
1439
+ preview();
1440
+ }
1441
+
1442
+ // remember the last focus
1443
+ $$.focus(function() {
1444
+ $.markItUp.focused = this;
1445
+ });
1446
+ }
1447
+
1448
+ // recursively build header with dropMenus from markupset
1449
+ function dropMenus(markupSet) {
1450
+ var ul = $('<ul></ul>'), i = 0;
1451
+ $('li:hover > ul', ul).css('display', 'block');
1452
+ $.each(markupSet, function() {
1453
+ var button = this, t = '', title, li, j;
1454
+ title = (button.key) ? (button.name||'')+' [Ctrl+'+button.key+']' : (button.name||'');
1455
+ key = (button.key) ? 'accesskey="'+button.key+'"' : '';
1456
+ if (button.separator) {
1457
+ li = $('<li class="markItUpSeparator">'+(button.separator||'')+'</li>').appendTo(ul);
1458
+ } else {
1459
+ i++;
1460
+ for (j = levels.length -1; j >= 0; j--) {
1461
+ t += levels[j]+"-";
1462
+ }
1463
+ li = $('<li class="markItUpButton markItUpButton'+t+(i)+' '+(button.className||'')+'"><a href="" '+key+' title="'+title+'">'+(button.name||'')+'</a></li>')
1464
+ .bind("contextmenu", function() { // prevent contextmenu on mac and allow ctrl+click
1465
+ return false;
1466
+ }).click(function() {
1467
+ return false;
1468
+ }).bind("focusin", function(){
1469
+ $$.focus();
1470
+ }).mousedown(function() {
1471
+ if (button.call) {
1472
+ eval(button.call)();
1473
+ }
1474
+ setTimeout(function() { markup(button) },1);
1475
+ return false;
1476
+ }).hover(function() {
1477
+ $('> ul', this).show();
1478
+ $(document).one('click', function() { // close dropmenu if click outside
1479
+ $('ul ul', header).hide();
1480
+ }
1481
+ );
1482
+ }, function() {
1483
+ $('> ul', this).hide();
1484
+ }
1485
+ ).appendTo(ul);
1486
+ if (button.dropMenu) {
1487
+ levels.push(i);
1488
+ $(li).addClass('markItUpDropMenu').append(dropMenus(button.dropMenu));
1489
+ }
1490
+ }
1491
+ });
1492
+ levels.pop();
1493
+ return ul;
1494
+ }
1495
+
1496
+ // markItUp! markups
1497
+ function magicMarkups(string) {
1498
+ if (string) {
1499
+ string = string.toString();
1500
+ string = string.replace(/\(\!\(([\s\S]*?)\)\!\)/g,
1501
+ function(x, a) {
1502
+ var b = a.split('|!|');
1503
+ if (altKey === true) {
1504
+ return (b[1] !== undefined) ? b[1] : b[0];
1505
+ } else {
1506
+ return (b[1] === undefined) ? "" : b[0];
1507
+ }
1508
+ }
1509
+ );
1510
+ // [![prompt]!], [![prompt:!:value]!]
1511
+ string = string.replace(/\[\!\[([\s\S]*?)\]\!\]/g,
1512
+ function(x, a) {
1513
+ var b = a.split(':!:');
1514
+ if (abort === true) {
1515
+ return false;
1516
+ }
1517
+ value = prompt(b[0], (b[1]) ? b[1] : '');
1518
+ if (value === null) {
1519
+ abort = true;
1520
+ }
1521
+ return value;
1522
+ }
1523
+ );
1524
+ return string;
1525
+ }
1526
+ return "";
1527
+ }
1528
+
1529
+ // prepare action
1530
+ function prepare(action) {
1531
+ if ($.isFunction(action)) {
1532
+ action = action(hash);
1533
+ }
1534
+ return magicMarkups(action);
1535
+ }
1536
+
1537
+ // build block to insert
1538
+ function build(string) {
1539
+ openWith = prepare(clicked.openWith);
1540
+ placeHolder = prepare(clicked.placeHolder);
1541
+ replaceWith = prepare(clicked.replaceWith);
1542
+ closeWith = prepare(clicked.closeWith);
1543
+ if (replaceWith !== "") {
1544
+ block = openWith + replaceWith + closeWith;
1545
+ } else if (selection === '' && placeHolder !== '') {
1546
+ block = openWith + placeHolder + closeWith;
1547
+ } else {
1548
+ block = openWith + (string||selection) + closeWith;
1549
+ }
1550
+ return { block:block,
1551
+ openWith:openWith,
1552
+ replaceWith:replaceWith,
1553
+ placeHolder:placeHolder,
1554
+ closeWith:closeWith
1555
+ };
1556
+ }
1557
+
1558
+ // define markup to insert
1559
+ function markup(button) {
1560
+ var len, j, n, i;
1561
+ hash = clicked = button;
1562
+ get();
1563
+
1564
+ $.extend(hash, { line:"",
1565
+ root:options.root,
1566
+ textarea:textarea,
1567
+ selection:(selection||''),
1568
+ caretPosition:caretPosition,
1569
+ ctrlKey:ctrlKey,
1570
+ shiftKey:shiftKey,
1571
+ altKey:altKey
1572
+ }
1573
+ );
1574
+ // callbacks before insertion
1575
+ prepare(options.beforeInsert);
1576
+ prepare(clicked.beforeInsert);
1577
+ if (ctrlKey === true && shiftKey === true) {
1578
+ prepare(clicked.beforeMultiInsert);
1579
+ }
1580
+ $.extend(hash, { line:1 });
1581
+
1582
+ if (ctrlKey === true && shiftKey === true) {
1583
+ lines = selection.split(/\r?\n/);
1584
+ for (j = 0, n = lines.length, i = 0; i < n; i++) {
1585
+ if ($.trim(lines[i]) !== '') {
1586
+ $.extend(hash, { line:++j, selection:lines[i] } );
1587
+ lines[i] = build(lines[i]).block;
1588
+ } else {
1589
+ lines[i] = "";
1590
+ }
1591
+ }
1592
+ string = { block:lines.join('\n')};
1593
+ start = caretPosition;
1594
+ len = string.block.length + (($.browser.opera) ? n-1 : 0);
1595
+ } else if (ctrlKey === true) {
1596
+ string = build(selection);
1597
+ start = caretPosition + string.openWith.length;
1598
+ len = string.block.length - string.openWith.length - string.closeWith.length;
1599
+ len -= fixIeBug(string.block);
1600
+ } else if (shiftKey === true) {
1601
+ string = build(selection);
1602
+ start = caretPosition;
1603
+ len = string.block.length;
1604
+ len -= fixIeBug(string.block);
1605
+ } else {
1606
+ string = build(selection);
1607
+ start = caretPosition + string.block.length ;
1608
+ len = 0;
1609
+ start -= fixIeBug(string.block);
1610
+ }
1611
+ if ((selection === '' && string.replaceWith === '')) {
1612
+ caretOffset += fixOperaBug(string.block);
1613
+
1614
+ start = caretPosition + string.openWith.length;
1615
+ len = string.block.length - string.openWith.length - string.closeWith.length;
1616
+
1617
+ caretOffset = $$.val().substring(caretPosition, $$.val().length).length;
1618
+ caretOffset -= fixOperaBug($$.val().substring(0, caretPosition));
1619
+ }
1620
+ $.extend(hash, { caretPosition:caretPosition, scrollPosition:scrollPosition } );
1621
+
1622
+ if (string.block !== selection && abort === false) {
1623
+ insert(string.block);
1624
+ set(start, len);
1625
+ } else {
1626
+ caretOffset = -1;
1627
+ }
1628
+ get();
1629
+
1630
+ $.extend(hash, { line:'', selection:selection });
1631
+
1632
+ // callbacks after insertion
1633
+ if (ctrlKey === true && shiftKey === true) {
1634
+ prepare(clicked.afterMultiInsert);
1635
+ }
1636
+ prepare(clicked.afterInsert);
1637
+ prepare(options.afterInsert);
1638
+
1639
+ // refresh preview if opened
1640
+ if (previewDiv && options.previewAutoRefresh) {
1641
+ refreshPreview();
1642
+ }
1643
+
1644
+ // reinit keyevent
1645
+ shiftKey = altKey = ctrlKey = abort = false;
1646
+ }
1647
+
1648
+ // Substract linefeed in Opera
1649
+ function fixOperaBug(string) {
1650
+ if ($.browser.opera) {
1651
+ return string.length - string.replace(/\n*/g, '').length;
1652
+ }
1653
+ return 0;
1654
+ }
1655
+ // Substract linefeed in IE
1656
+ function fixIeBug(string) {
1657
+ if ($.browser.msie) {
1658
+ return string.length - string.replace(/\r/g, '').length;
1659
+ }
1660
+ return 0;
1661
+ }
1662
+
1663
+ // add markup
1664
+ function insert(block) {
1665
+ if (document.selection) {
1666
+ var newSelection = document.selection.createRange();
1667
+ newSelection.text = block;
1668
+ } else {
1669
+ textarea.value = textarea.value.substring(0, caretPosition) + block + textarea.value.substring(caretPosition + selection.length, textarea.value.length);
1670
+ }
1671
+ }
1672
+
1673
+ // set a selection
1674
+ function set(start, len) {
1675
+ if (textarea.createTextRange){
1676
+ // quick fix to make it work on Opera 9.5
1677
+ if ($.browser.opera && $.browser.version >= 9.5 && len == 0) {
1678
+ return false;
1679
+ }
1680
+ range = textarea.createTextRange();
1681
+ range.collapse(true);
1682
+ range.moveStart('character', start);
1683
+ range.moveEnd('character', len);
1684
+ range.select();
1685
+ } else if (textarea.setSelectionRange ){
1686
+ textarea.setSelectionRange(start, start + len);
1687
+ }
1688
+ textarea.scrollTop = scrollPosition;
1689
+ textarea.focus();
1690
+ }
1691
+
1692
+ // get the selection
1693
+ function get() {
1694
+ textarea.focus();
1695
+
1696
+ scrollPosition = textarea.scrollTop;
1697
+ if (document.selection) {
1698
+ selection = document.selection;
1699
+ if ($.browser.msie) { // ie
1700
+ var range = selection.createRange();
1701
+ var stored_range = range.duplicate();
1702
+ stored_range.moveToElementText(textarea);
1703
+ stored_range.setEndPoint('EndToEnd', range);
1704
+ var s = stored_range.text.length - range.text.length;
1705
+
1706
+ caretPosition = s - (textarea.value.substr(0, s).length - textarea.value.substr(0, s).replace(/\r/g, '').length);
1707
+ selection = range.text;
1708
+ } else { // opera
1709
+ caretPosition = textarea.selectionStart;
1710
+ }
1711
+ } else { // gecko & webkit
1712
+ caretPosition = textarea.selectionStart;
1713
+ selection = textarea.value.substring(caretPosition, textarea.selectionEnd);
1714
+ }
1715
+ return selection;
1716
+ }
1717
+
1718
+ // open preview window
1719
+ function preview() {
1720
+ if (!previewDiv) {
1721
+ previewDiv = $('<div class="markItUpPreview"></div>');
1722
+
1723
+ if (options.previewPosition == 'after') {
1724
+ previewDiv.insertAfter(footer);
1725
+ } else {
1726
+ previewDiv.insertBefore(header);
1727
+ }
1728
+ } else if (altKey === true) {
1729
+ if (previewDiv) {
1730
+ previewDiv.remove();
1731
+ }
1732
+ previewDiv = false;
1733
+ }
1734
+ refreshPreview();
1735
+ }
1736
+
1737
+ // refresh Preview window
1738
+ function refreshPreview() {
1739
+ renderPreview();
1740
+ }
1741
+
1742
+ function renderPreview() {
1743
+ previewDiv.html(converter.makeHtml($$.val()));
1744
+
1745
+ return false;
1746
+ }
1747
+
1748
+ // set keys pressed
1749
+ function keyPressed(e) {
1750
+ shiftKey = e.shiftKey;
1751
+ altKey = e.altKey;
1752
+ ctrlKey = (!(e.altKey && e.ctrlKey)) ? e.ctrlKey : false;
1753
+
1754
+ if (e.type === 'keydown') {
1755
+ if (ctrlKey === true) {
1756
+ li = $("a[accesskey="+String.fromCharCode(e.keyCode)+"]", header).parent('li');
1757
+ if (li.length !== 0) {
1758
+ ctrlKey = false;
1759
+ setTimeout(function() {
1760
+ li.triggerHandler('mousedown');
1761
+ },1);
1762
+ return false;
1763
+ }
1764
+ }
1765
+ if (e.keyCode === 13 || e.keyCode === 10) { // Enter key
1766
+ if (ctrlKey === true) { // Enter + Ctrl
1767
+ ctrlKey = false;
1768
+ markup(options.onCtrlEnter);
1769
+ return options.onCtrlEnter.keepDefault;
1770
+ } else if (shiftKey === true) { // Enter + Shift
1771
+ shiftKey = false;
1772
+ markup(options.onShiftEnter);
1773
+ return options.onShiftEnter.keepDefault;
1774
+ } else { // only Enter
1775
+ markup(options.onEnter);
1776
+ return options.onEnter.keepDefault;
1777
+ }
1778
+ }
1779
+ if (e.keyCode === 9) { // Tab key
1780
+ if (shiftKey == true || ctrlKey == true || altKey == true) {
1781
+ return false;
1782
+ }
1783
+ if (caretOffset !== -1) {
1784
+ get();
1785
+ caretOffset = $$.val().length - caretOffset;
1786
+ set(caretOffset, 0);
1787
+ caretOffset = -1;
1788
+ return false;
1789
+ } else {
1790
+ markup(options.onTab);
1791
+ return options.onTab.keepDefault;
1792
+ }
1793
+ }
1794
+ }
1795
+ refreshPreview();
1796
+ }
1797
+
1798
+ init();
1799
+ });
1800
+ };
1801
+
1802
+ $.fn.markItUpRemove = function() {
1803
+ return this.each(function() {
1804
+ var $$ = $(this).unbind().removeClass('markItUpEditor');
1805
+ $$.parent('div').parent('div.markItUp').parent('div').replaceWith($$);
1806
+ }
1807
+ );
1808
+ };
1809
+
1810
+ $.markItUp = function(settings) {
1811
+ var options = { target:false };
1812
+ $.extend(options, settings);
1813
+ if (options.target) {
1814
+ return $(options.target).each(function() {
1815
+ $(this).focus();
1816
+ $(this).trigger('insertion', [options]);
1817
+ });
1818
+ } else {
1819
+ $('textarea').trigger('insertion', [options]);
1820
+ }
1821
+ };
1822
+
1823
+
1824
+ $.fn.markdownEditor = function(settings) {
1825
+ var options = {
1826
+ headings: [1,2,3,4,5,6],
1827
+ allowPictures: true,
1828
+ pictures: []
1829
+ };
1830
+ $.extend(options, settings);
1831
+
1832
+ var markups = []
1833
+
1834
+ if (options.headings.indexOf(1) > -1) {
1835
+ markups.push({name:'Heading 1', key:"1", className: 'heading1', placeHolder:'Your title here...', closeWith:function(markItUp) { return miu.markdownTitle(markItUp, '=') } });
1836
+ }
1837
+ if (options.headings.indexOf(2) > -1) {
1838
+ markups.push({name:'Heading 2', key:"2", className: 'heading2', placeHolder:'Your title here...', closeWith:function(markItUp) { return miu.markdownTitle(markItUp, '-') } });
1839
+ }
1840
+ if (options.headings.indexOf(3) > -1) {
1841
+ markups.push({name:'Heading 3', key:"3", className: 'heading3', openWith:'### ', placeHolder:'Your title here...' });
1842
+ }
1843
+ if (options.headings.indexOf(4) > -1) {
1844
+ markups.push({name:'Heading 4', key:"4", className: 'heading4', openWith:'#### ', placeHolder:'Your title here...' });
1845
+ }
1846
+ if (options.headings.indexOf(5) > -1) {
1847
+ markups.push({name:'Heading 5', key:"5", className: 'heading5', openWith:'##### ', placeHolder:'Your title here...' });
1848
+ }
1849
+ if (options.headings.indexOf(6) > -1) {
1850
+ markups.push({name:'Heading 6', key:"6", className: 'heading6', openWith:'###### ', placeHolder:'Your title here...' });
1851
+ }
1852
+ if (options.headings.length > 0 ) {
1853
+ markups.push({separator:'---------------' });
1854
+ }
1855
+ markups.push({name:'Bold', key:"B", className: 'bold', openWith:'**', closeWith:'**'});
1856
+ markups.push({name:'Italic', key:"I", className: 'italic', openWith:'_', closeWith:'_'});
1857
+ markups.push({name:'Paragraph', key:"6", className: 'paragraph', openWith:'\n\n', placeHolder:'Your paragraph here...' });
1858
+ markups.push({name:'Quotes', className: 'quotes', openWith:'> '});
1859
+ markups.push({separator:'---------------' });
1860
+ markups.push({name:'Bulleted List', className: 'bullet-list', openWith:'- ' });
1861
+ markups.push({name:'Numeric List', className: 'number-list', openWith:function(markItUp) {
1862
+ return markItUp.line+'. ';
1863
+ }});
1864
+ markups.push({separator:'---------------' });
1865
+
1866
+ if (options.allowPictures) {
1867
+ var pictureTab = {name: 'Picture', key: "P", className: 'picture', dropMenu: []};
1868
+
1869
+ $.each(options.pictures, function(_, picture) {
1870
+ console.log(picture.title);
1871
+ pictureTab.dropMenu.push({name: picture.title, replaceWith:'![' + picture.title + '](' + picture.url + ' "' + picture.title + '")'});
1872
+ });
1873
+
1874
+ pictureTab.dropMenu.push({name: 'External Image URL', replaceWith:'![[![Alternative text]!]]([![Url:!:http://]!] "[![Title]!]")'});
1875
+
1876
+ markups.push(pictureTab);
1877
+ }
1878
+
1879
+ markups.push({name:'Link', key:"L", className: 'link', openWith:'[', closeWith:']([![Url:!:http://]!])', placeHolder:'Your text to link here...' });
1880
+ markups.push({separator:'---------------'});
1881
+ markups.push({name:'Preview', call:'preview', className:"preview"});
1882
+
1883
+ markdownSettings = {
1884
+ nameSpace: 'markdown', // Useful to prevent multi-instances CSS conflict
1885
+ onShiftEnter: {keepDefault:false, openWith:'\n\n'},
1886
+ markupSet: markups
1887
+ }
1888
+ // mIu nameSpace to avoid conflict.
1889
+ miu = {
1890
+ markdownTitle: function(markItUp, char) {
1891
+ heading = '';
1892
+ n = $.trim(markItUp.selection||markItUp.placeHolder).length;
1893
+ for(i = 0; i < n; i++) {
1894
+ heading += char;
1895
+ }
1896
+ return '\n'+heading+'\n';
1897
+ }
1898
+ }
1899
+
1900
+ $(this).markItUp(markdownSettings);
1901
+
1902
+ };
1903
+
1904
+ })(jQuery);