typeout 1.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README.rdoc +133 -0
- data/lib/typeout.js +276 -0
- data/lib/typeout.rb +170 -0
- metadata +86 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010 Connor McKay
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
= Typeout
|
2
|
+
|
3
|
+
Typeout is a plain text formatting language designed around two goals:
|
4
|
+
|
5
|
+
* Syntax simple enough for computer illiterate users
|
6
|
+
* Easily typed into an HTML textbox, meaning no tabs or extra spaces.
|
7
|
+
|
8
|
+
It is also a set of libraries in different programming languages for converting
|
9
|
+
Typeout formatted text into HTML. The primary implementation is in Ruby, which
|
10
|
+
has been more or less directly translated into Javascript.
|
11
|
+
|
12
|
+
Typeout is inspired by:
|
13
|
+
|
14
|
+
* Markdown http://daringfireball.net/projects/markdown
|
15
|
+
* Textile http://textism.com/tools/textile
|
16
|
+
* Creole http://www.wikicreole.org
|
17
|
+
|
18
|
+
*Author*:: Connor McKay (mailto:connor@verticalforest.com)
|
19
|
+
*Version*:: 1.4.1 (2010-7-8)
|
20
|
+
*Copyright*:: Copyright (c) 2007-2010 Connor McKay. All rights reserved.
|
21
|
+
*License*:: MIT License (http://opensource.org/licenses/mit-license.php)
|
22
|
+
*Website*:: http://github.com/greneholt/typeout
|
23
|
+
|
24
|
+
== Requires
|
25
|
+
|
26
|
+
Ruby version:
|
27
|
+
|
28
|
+
* Sanitize >= 1.2.1
|
29
|
+
|
30
|
+
Javascript version:
|
31
|
+
|
32
|
+
* MooTools Core >= 1.2.4
|
33
|
+
|
34
|
+
== Installation
|
35
|
+
|
36
|
+
Gem:
|
37
|
+
|
38
|
+
gem install typeout
|
39
|
+
|
40
|
+
Rails plugin:
|
41
|
+
|
42
|
+
./script/plugin install git://github.com/greneholt/typeout.git
|
43
|
+
|
44
|
+
== Usage
|
45
|
+
|
46
|
+
Ruby:
|
47
|
+
|
48
|
+
Typeout.convert(text)
|
49
|
+
|
50
|
+
Javascript:
|
51
|
+
|
52
|
+
var text = new TypeOut(text);
|
53
|
+
text.toHTML();
|
54
|
+
|
55
|
+
== Syntax
|
56
|
+
|
57
|
+
---
|
58
|
+
Code block
|
59
|
+
---
|
60
|
+
|
61
|
+
= Level 1 Header
|
62
|
+
== Level 2 Header, etc.
|
63
|
+
|
64
|
+
* Unordered
|
65
|
+
* List
|
66
|
+
|
67
|
+
# Ordered
|
68
|
+
# List
|
69
|
+
|
70
|
+
* Multilevel
|
71
|
+
* Unordered
|
72
|
+
** List
|
73
|
+
** With
|
74
|
+
*** Multiple
|
75
|
+
* Asterisks
|
76
|
+
|
77
|
+
* Mixed
|
78
|
+
* Unordered
|
79
|
+
*# And
|
80
|
+
*# Ordered
|
81
|
+
*# Nested
|
82
|
+
* List
|
83
|
+
|
84
|
+
~~~
|
85
|
+
Four score and seven years ago, blockquotes use at least three tildes at
|
86
|
+
beginning and end.
|
87
|
+
~~~
|
88
|
+
|
89
|
+
Average paragraph containing *bold text*, _italic text_, and a
|
90
|
+
[link to google](http://google.com) and some `inline code`.
|
91
|
+
|
92
|
+
Another paragraph with a [link](http://google.com):button with a class of "button".
|
93
|
+
|
94
|
+
Images
|
95
|
+
|
96
|
+
!(http://example.com/an_image.png)
|
97
|
+
|
98
|
+
!(http://example.com/logo.png)[An image with optional alt text]
|
99
|
+
|
100
|
+
|
101
|
+
Name: Jane Alice
|
102
|
+
Age: 25
|
103
|
+
Favorite food: Pizza
|
104
|
+
|
105
|
+
Text at the beginning of a line followed by a colon will be automatically bolded.
|
106
|
+
|
107
|
+
Newlines in the middle of a paragraph are preserved in Typeout, meaning
|
108
|
+
that a paragraph cannot be split across multiple lines.
|
109
|
+
|
110
|
+
Plain html can be preserved intact by surrounding it in [html], [/html]
|
111
|
+
pairs. It will be sanitized using the Sanitize library in the Ruby version.
|
112
|
+
|
113
|
+
== License
|
114
|
+
|
115
|
+
Copyright (c) 2010 Connor McKay
|
116
|
+
|
117
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
118
|
+
of this software and associated documentation files (the "Software"), to deal
|
119
|
+
in the Software without restriction, including without limitation the rights
|
120
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
121
|
+
copies of the Software, and to permit persons to whom the Software is
|
122
|
+
furnished to do so, subject to the following conditions:
|
123
|
+
|
124
|
+
The above copyright notice and this permission notice shall be included in
|
125
|
+
all copies or substantial portions of the Software.
|
126
|
+
|
127
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
128
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
129
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
130
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
131
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
132
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
133
|
+
THE SOFTWARE.
|
data/lib/typeout.js
ADDED
@@ -0,0 +1,276 @@
|
|
1
|
+
var Typeout = new Class({
|
2
|
+
initialize: function(content) {
|
3
|
+
var self = this;
|
4
|
+
|
5
|
+
self.content = content;
|
6
|
+
},
|
7
|
+
|
8
|
+
toHTML: function() {
|
9
|
+
var self = this;
|
10
|
+
|
11
|
+
var text = self.content;
|
12
|
+
|
13
|
+
text = self.cleanWhitespace(text);
|
14
|
+
text = '\n\n' + text + '\n\n'; // a few guaranteed newlines
|
15
|
+
|
16
|
+
self.archive = [];
|
17
|
+
|
18
|
+
text = self.archiveHtml(text);
|
19
|
+
|
20
|
+
text = self.htmlEscape(text);
|
21
|
+
|
22
|
+
text = self.archiveCode(text);
|
23
|
+
|
24
|
+
text = self.makeBlockquotes(text);
|
25
|
+
text = self.makeHeadings(text);
|
26
|
+
text = self.makeLists(text);
|
27
|
+
text = self.makeParagraphs(text);
|
28
|
+
text = self.makeInlines(text);
|
29
|
+
|
30
|
+
text = self.removeExcessNewlines(text);
|
31
|
+
|
32
|
+
text = self.retrieveArchive(text);
|
33
|
+
|
34
|
+
return text;
|
35
|
+
},
|
36
|
+
|
37
|
+
htmlEscape: function(text) {
|
38
|
+
return text.
|
39
|
+
replace(/&/g, '&').
|
40
|
+
replace(/</g, '<').
|
41
|
+
replace(/>/g, '>').
|
42
|
+
replace(/"/g, '"')
|
43
|
+
},
|
44
|
+
|
45
|
+
regexpEscape: function(text) {
|
46
|
+
return text.replace(/([\{\}\(\)\[\]\*\+\?\$\^\.\#\\])/g, '\\$1')
|
47
|
+
},
|
48
|
+
|
49
|
+
cleanWhitespace: function(text) {
|
50
|
+
return text.
|
51
|
+
replace(/\r\n/g, '\n').
|
52
|
+
replace(/\r/g, '\n').
|
53
|
+
replace(/^ +$/gm, '');
|
54
|
+
},
|
55
|
+
|
56
|
+
removeExcessNewlines: function(text) {
|
57
|
+
return text.replace(/\n{3,}/g, '\n\n');
|
58
|
+
},
|
59
|
+
|
60
|
+
archiveHtml: function(text) {
|
61
|
+
var self = this;
|
62
|
+
|
63
|
+
return text.replace(/\[html\]([\s\S]*?)\[\/html\]/gmi, function(wholeMatch, m1) {
|
64
|
+
return self.addToArchive(m1);
|
65
|
+
});
|
66
|
+
},
|
67
|
+
|
68
|
+
archiveCode: function(text) {
|
69
|
+
var self = this;
|
70
|
+
|
71
|
+
text = text.replace(/^-{3,}\n([\s\S]+?)\n-{3,}$/gm, function(wholeMatch, m1) {
|
72
|
+
return self.addToArchive('<pre><code>' + m1 +'</code></pre>');
|
73
|
+
});
|
74
|
+
|
75
|
+
return text.replace(/`(?!\s)((?:\s*\S)+?)`/g, function(wholeMatch, m1) {
|
76
|
+
return self.addToArchive('<code>' + m1 + '</code>');
|
77
|
+
});
|
78
|
+
},
|
79
|
+
|
80
|
+
addToArchive: function(text) {
|
81
|
+
var self = this;
|
82
|
+
|
83
|
+
var index = self.archive.push(text) - 1;
|
84
|
+
return '!a!r!c!h!i!v!e!' + index + '!a!r!c!h!i!v!e!'; // nobody's going to type that!
|
85
|
+
},
|
86
|
+
|
87
|
+
retrieveArchive: function(text) {
|
88
|
+
var self = this;
|
89
|
+
|
90
|
+
self.archive.each(function(content, index) {
|
91
|
+
text = text.replace(new RegExp('!a!r!c!h!i!v!e!' + index + '!a!r!c!h!i!v!e!'), content); // the block is so gsub won't substitute \&
|
92
|
+
});
|
93
|
+
return text;
|
94
|
+
},
|
95
|
+
|
96
|
+
makeBlockquotes: function(text) {
|
97
|
+
return text.replace(/^~{3,}\n([\s\S]+?)\n~{3,}$/gm, '\n\n<blockquote>\n\n$1\n\n</blockquote>\n\n');
|
98
|
+
},
|
99
|
+
|
100
|
+
makeHeadings: function(text) {
|
101
|
+
return text.replace(/^(={1,6})(.+?)=*?$/gm, function(wholeMatch, m1, m2) {
|
102
|
+
var depth = m1.length;
|
103
|
+
return '\n\n<h' + depth + '>' + m2.trim() + '</h' + depth + '>\n\n';
|
104
|
+
});
|
105
|
+
},
|
106
|
+
|
107
|
+
makeLists: function(text, baseLevel) {
|
108
|
+
var self = this;
|
109
|
+
|
110
|
+
if (baseLevel == undefined) {
|
111
|
+
baseLevel = '';
|
112
|
+
}
|
113
|
+
|
114
|
+
return text.replace(new RegExp('^(' + self.regexpEscape(baseLevel) + '[\\*\\#])[\\*\\#]* [^\n]+?\n(?:\\1[\\*\\#]* (?:[^\n])+?\n)*', 'gm'), function(content, level) {
|
115
|
+
content = self.makeLists(content, level);
|
116
|
+
content = content.replace(new RegExp('^' + self.regexpEscape(level) + ' (.+)$', 'gm'), '<li>$1</li>');
|
117
|
+
content = content.replace(/<\/li>\n<([uo])l>/gm, '<$1l>');
|
118
|
+
|
119
|
+
if (level.slice(-1) == '*') { // its an unordered list
|
120
|
+
content = '<ul>\n' + content + '</ul>';
|
121
|
+
} else {
|
122
|
+
content = '<ol>\n' + content + '</ol>';
|
123
|
+
}
|
124
|
+
|
125
|
+
if (baseLevel) {
|
126
|
+
content += '</li>\n';
|
127
|
+
} else {
|
128
|
+
content += '\n';
|
129
|
+
}
|
130
|
+
return content;
|
131
|
+
});
|
132
|
+
},
|
133
|
+
|
134
|
+
makeParagraphs: function(text) {
|
135
|
+
var self = this;
|
136
|
+
|
137
|
+
return text.replace(/^(?!<|!a!r!c!h!i!v!e!)([^\n]+\n)+\n/gm, function(content) {
|
138
|
+
return '\n\n<p>\n' + self.makeBreaks(content.trim()) + '</p>\n\n';
|
139
|
+
});
|
140
|
+
},
|
141
|
+
|
142
|
+
makeBreaks: function(text) {
|
143
|
+
return text.replace(/([^\n])\n(?!\n)/gm, '$1<br />\n');
|
144
|
+
},
|
145
|
+
|
146
|
+
makeInlines: function(text) {
|
147
|
+
var self = this;
|
148
|
+
|
149
|
+
text = text.
|
150
|
+
replace(/^((?: ?[\w\(\)']){3,}:) (?!\s*$)/gm, '<b>$1</b> '). // spaces are not allowed directly in front of the colon, there must be a space after it, and the rest of the line can't be blank
|
151
|
+
replace(/!\((.+?)\)(?:\:([a-zA-Z0-9\_]+))?/g, function(wholeMatch, m1, m2) {
|
152
|
+
if (m2) {
|
153
|
+
return '<img src="' + self.addToArchive(m1.trim()) + '" class="' + m2 + '" />';
|
154
|
+
} else {
|
155
|
+
return '<img src="' + self.addToArchive(m1.trim()) + '" />';
|
156
|
+
}
|
157
|
+
}).
|
158
|
+
replace(/\[(.+?)\]\((.+?)\)(?:\:([a-zA-Z0-9\_]+))?/g, function(wholeMatch, m1, m2, m3) {
|
159
|
+
if (m3) {
|
160
|
+
return '<a href="' + self.addToArchive(m2.trim()) + '" class="' + m3 + '">' + self.addToArchive(m1) + '</a>';
|
161
|
+
} else {
|
162
|
+
return '<a href="' + self.addToArchive(m2.trim()) + '">' + self.addToArchive(m1) + '</a>';
|
163
|
+
}
|
164
|
+
}).
|
165
|
+
replace(/([a-z0-9\.\-\_\+]+)@((?:[a-z0-9\-]{2,}\.)*)([a-z0-9\-]+\.)(com|org|net|biz|edu|info|gov|co\.uk|co\.us)/gi, function(wholeMatch, address, subdomain, domain, tld) {
|
166
|
+
return '<a href="mailto:' + self.addToArchive(address + '@' + subdomain + domain + tld) + '">' + self.addToArchive(address + '@' + subdomain + domain + tld) + '</a>';
|
167
|
+
}).
|
168
|
+
replace(/(https?:\/\/)?((?:[a-z0-9\-]{2,}\.)*)([a-z0-9\-]+\.)(com|org|net|biz|edu|info|gov|co\.uk|co\.us)((?:\/[a-z0-9\-\._=\?&;\%#]+)*\/?)/gi, function(wholeMatch, protocol, subdomain, domain, tld, file) {
|
169
|
+
if (!protocol) {
|
170
|
+
protocol = 'http://';
|
171
|
+
}
|
172
|
+
return '<a href="' + self.addToArchive(protocol + subdomain + domain + tld + file) + '">' + self.addToArchive(protocol + subdomain + domain + tld + file) + '</a>';
|
173
|
+
});
|
174
|
+
|
175
|
+
var inlines = [
|
176
|
+
['*', 'strong'],
|
177
|
+
['_', 'em'],
|
178
|
+
['`', 'code'],
|
179
|
+
['^', 'sup'],
|
180
|
+
['~', 'sub']
|
181
|
+
];
|
182
|
+
|
183
|
+
inlines.each(function(pair) {
|
184
|
+
var char = self.regexpEscape(pair[0]);
|
185
|
+
var tag = pair[1];
|
186
|
+
text = text.replace(new RegExp(char + '(?!\\s)((?:\\s*\\S)+?)' + char, 'g'), '<' + tag + '>$1</' + tag + '>');
|
187
|
+
});
|
188
|
+
|
189
|
+
return text;
|
190
|
+
}
|
191
|
+
});
|
192
|
+
|
193
|
+
var TypeOutPreview = new Class({
|
194
|
+
Implements: Options,
|
195
|
+
|
196
|
+
options: {
|
197
|
+
frequency: 500
|
198
|
+
},
|
199
|
+
|
200
|
+
initialize: function(textArea, previewDiv, options) {
|
201
|
+
var self = this;
|
202
|
+
|
203
|
+
self.textArea = $(textArea);
|
204
|
+
self.previewDiv = $(previewDiv);
|
205
|
+
|
206
|
+
self.setOptions(options);
|
207
|
+
|
208
|
+
self.typeOut = new TypeOut;
|
209
|
+
self.dirty = true;
|
210
|
+
self.ready = false;
|
211
|
+
|
212
|
+
self.textArea.addEvent('keyup', self.setDirty.bind(self));
|
213
|
+
self.setReady();
|
214
|
+
},
|
215
|
+
|
216
|
+
setDirty: function() {
|
217
|
+
var self = this;
|
218
|
+
|
219
|
+
self.dirty = true;
|
220
|
+
if (self.ready) {
|
221
|
+
self.setUnReady();
|
222
|
+
self.preview();
|
223
|
+
}
|
224
|
+
},
|
225
|
+
|
226
|
+
setUnReady: function() {
|
227
|
+
var self = this;
|
228
|
+
|
229
|
+
self.ready = false;
|
230
|
+
self.setReady.delay(self.options.frequency, self);
|
231
|
+
},
|
232
|
+
|
233
|
+
setReady: function() {
|
234
|
+
var self = this;
|
235
|
+
|
236
|
+
self.ready = true;
|
237
|
+
if (self.dirty) {
|
238
|
+
self.setUnReady();
|
239
|
+
self.preview();
|
240
|
+
}
|
241
|
+
},
|
242
|
+
|
243
|
+
render: function(text) {
|
244
|
+
var self = this;
|
245
|
+
|
246
|
+
self.typeOut.content = text;
|
247
|
+
return self.typeOut.toHTML();
|
248
|
+
},
|
249
|
+
|
250
|
+
preview: function() {
|
251
|
+
var self = this;
|
252
|
+
|
253
|
+
self.previewDiv.set('html', self.render(self.textArea.value));
|
254
|
+
self.dirty = false;
|
255
|
+
}
|
256
|
+
});
|
257
|
+
|
258
|
+
TypeOutTemplatePreview = new Class({
|
259
|
+
Extends: TypeOutPreview,
|
260
|
+
|
261
|
+
initialize: function(textArea, previewDiv, template, options) {
|
262
|
+
var self = this;
|
263
|
+
|
264
|
+
self.template = template;
|
265
|
+
self.parent(textArea, previewDiv, options);
|
266
|
+
},
|
267
|
+
|
268
|
+
render: function(content) {
|
269
|
+
var self = this;
|
270
|
+
|
271
|
+
self.template.tags.each(function(tag) {
|
272
|
+
content = content.replace(RegExp(self.regexpEscape(tag.name), 'gi'), tag.example);
|
273
|
+
});
|
274
|
+
return self.parent(content);
|
275
|
+
}
|
276
|
+
});
|
data/lib/typeout.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'sanitize'
|
2
|
+
|
3
|
+
class Typeout < String
|
4
|
+
VERSION = '1.4.1'
|
5
|
+
|
6
|
+
include ERB::Util
|
7
|
+
|
8
|
+
def self.convert(text)
|
9
|
+
self.new(text.to_s).to_html
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_html
|
13
|
+
text = self.dup
|
14
|
+
|
15
|
+
text = clean_whitespace text
|
16
|
+
text = "\n\n#{text}\n\n" # a few guaranteed newlines
|
17
|
+
|
18
|
+
@archive = []
|
19
|
+
|
20
|
+
text = archive_html text
|
21
|
+
|
22
|
+
text = html_escape text
|
23
|
+
|
24
|
+
text = archive_code text
|
25
|
+
text = archive_blockquotes text
|
26
|
+
|
27
|
+
text = make_headings text
|
28
|
+
text = make_lists text
|
29
|
+
text = make_paragraphs text
|
30
|
+
text = make_inlines text
|
31
|
+
|
32
|
+
text = remove_excess_newlines text
|
33
|
+
|
34
|
+
text = retrieve_archive text
|
35
|
+
|
36
|
+
text
|
37
|
+
end
|
38
|
+
|
39
|
+
def sanitize_html(text)
|
40
|
+
Sanitize.clean(text, Sanitize::Config::RELAXED)
|
41
|
+
end
|
42
|
+
|
43
|
+
def clean_whitespace(text)
|
44
|
+
text.gsub(/\r\n/, "\n").
|
45
|
+
gsub(/\r/, "\n").
|
46
|
+
gsub(/^ +$/, '')
|
47
|
+
end
|
48
|
+
|
49
|
+
def remove_excess_newlines(text)
|
50
|
+
text.gsub(/\n{3,}/, "\n\n")
|
51
|
+
end
|
52
|
+
|
53
|
+
def archive_html(text)
|
54
|
+
text.gsub(/\[html\](.*?)\[\/html\]/mi) { archive(sanitize_html($1)) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def archive_code(text)
|
58
|
+
text = text.gsub(/^-{3,}\n(.+?)\n-{3,}$/m) { archive("<pre><code>#{$1}</code></pre>") }
|
59
|
+
text.gsub(/`(?!\s)((?:\s*\S)+?)`/) { archive("<code>#{$1}</code>") }
|
60
|
+
end
|
61
|
+
|
62
|
+
def archive_blockquotes(text)
|
63
|
+
text.gsub(/^~{3,}\n(.+?)\n~{3,}$/m) do
|
64
|
+
content = remove_excess_newlines(make_paragraphs(make_lists("\n\n#{$1}\n\n"))) # blockquotes support paragraphs and lists
|
65
|
+
archive("<blockquote>#{content}</blockquote>")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def archive(text)
|
70
|
+
@archive << text
|
71
|
+
"!a!r!c!h!i!v!e!#{@archive.length-1}!a!r!c!h!i!v!e!" # nobody's going to type that!
|
72
|
+
end
|
73
|
+
|
74
|
+
def retrieve_archive(text)
|
75
|
+
@archive.each_with_index do |content, index|
|
76
|
+
text = text.sub("!a!r!c!h!i!v!e!#{index}!a!r!c!h!i!v!e!") { content } # the block is so gsub won't substitute \&
|
77
|
+
end
|
78
|
+
text
|
79
|
+
end
|
80
|
+
|
81
|
+
def make_headings(text)
|
82
|
+
text.gsub(/^(={1,6})(.+?)=*?$/) do
|
83
|
+
depth = $1.length
|
84
|
+
"\n\n<h#{depth}>#{$2.strip}</h#{depth}>\n\n"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def make_lists(text, base_level = nil)
|
89
|
+
text.gsub(/^(#{Regexp.escape(base_level.to_s)}[\*\#])[\*\#]* [^\n]+?\n(?:\1[\*\#]* (?:[^\n])+?\n)*/m) do |content|
|
90
|
+
level = $1
|
91
|
+
|
92
|
+
content = make_lists(content, level)
|
93
|
+
content = content.gsub(/^#{Regexp.escape(level)} (.+)$/, '<li>\1</li>')
|
94
|
+
content = content.gsub(/<\/li>\n<([uo])l>/m, '<\1l>')
|
95
|
+
|
96
|
+
if level.last == "*" # its an unordered list
|
97
|
+
content = "<ul>\n#{content}</ul>"
|
98
|
+
else
|
99
|
+
content = "<ol>\n#{content}</ol>"
|
100
|
+
end
|
101
|
+
|
102
|
+
if base_level
|
103
|
+
content += "</li>\n"
|
104
|
+
else
|
105
|
+
content += "\n"
|
106
|
+
end
|
107
|
+
content
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def make_paragraphs(text)
|
112
|
+
text.gsub(/^(?!<|!a!r!c!h!i!v!e!)([^\n]+\n)+\n/m) { |content| "\n\n<p>\n#{make_breaks(content.strip)}</p>\n\n" }
|
113
|
+
end
|
114
|
+
|
115
|
+
def make_breaks(text)
|
116
|
+
text.gsub(/([^\n])\n(?!\n)/m, "\\1<br />\n")
|
117
|
+
end
|
118
|
+
|
119
|
+
def make_inlines(text)
|
120
|
+
text = text.
|
121
|
+
gsub(/^((?: ?[\w\(\)']){3,}:) (?!\s*$)/, '<b>\1</b> '). # spaces are not allowed directly in front of the colon, there must be a space after it, and the rest of the line can't be blank
|
122
|
+
gsub(/!\((.+?)\)(?:\:([a-zA-Z0-9\_]+))?/) do
|
123
|
+
if $2
|
124
|
+
"<img src=\"#{archive($1)}\" class=\"#{$2}\" />"
|
125
|
+
else
|
126
|
+
"<img src=\"#{archive($1)}\" />"
|
127
|
+
end
|
128
|
+
end.
|
129
|
+
gsub(/\[(.+?)\]\((.+?)\)(?:\:([a-zA-Z0-9\_]+))?/) do
|
130
|
+
if $3
|
131
|
+
"<a href=\"#{archive($2)}\" class=\"#{$3}\">#{archive($1)}</a>"
|
132
|
+
else
|
133
|
+
"<a href=\"#{archive($2)}\">#{archive($1)}</a>"
|
134
|
+
end
|
135
|
+
end.
|
136
|
+
gsub(/([a-z0-9\.\-\_\+]+)@((?:[a-z0-9\-]{2,}\.)*)([a-z0-9\-]+\.)(com|org|net|biz|edu|info|gov|co\.uk|co\.us)/i) do
|
137
|
+
"<a href=\"mailto:#{archive($1 + '@' + $2 + $3 + $4)}\">#{archive($1 + '@' + $2 + $3 + $4)}</a>"
|
138
|
+
end.
|
139
|
+
gsub(/(https?:\/\/)?((?:[a-z0-9\-]{2,}\.)*)([a-z0-9\-]+\.)(com|org|net|biz|edu|info|gov|co\.uk|co\.us)((?:\/[a-z0-9\-\._=\?&;\%#]+)*\/?)/i) do
|
140
|
+
if $1.nil?
|
141
|
+
protocol = 'http://'
|
142
|
+
else
|
143
|
+
protocol = $1
|
144
|
+
end
|
145
|
+
"<a href=\"#{archive(protocol + $2 + $3 + $4 + $5)}\">#{archive(protocol + $2 + $3 + $4 + $5)}</a>"
|
146
|
+
end
|
147
|
+
|
148
|
+
inlines = [
|
149
|
+
['*', 'strong'],
|
150
|
+
['_', 'em'],
|
151
|
+
['`', 'code'],
|
152
|
+
['^', 'sup'],
|
153
|
+
['~', 'sub']
|
154
|
+
]
|
155
|
+
|
156
|
+
inlines.each do |char, tag|
|
157
|
+
char = Regexp.escape(char)
|
158
|
+
re = /#{char} # the opening character
|
159
|
+
(?!\s) # no spaces directly after the char
|
160
|
+
( # main capture group
|
161
|
+
(?:\s*\S)+? # because of no lookbehinds, every space must be followed by a non-space
|
162
|
+
) # with lookbehinds, the whole regex is: \*(?! )(.+?)(?<! )\*
|
163
|
+
#{char}/x # closing character, no spaces directly before it
|
164
|
+
#re = /#{char}(.+?)#{char}/
|
165
|
+
text.gsub!(re, "<#{tag}>\\1</#{tag}>")
|
166
|
+
end
|
167
|
+
|
168
|
+
text
|
169
|
+
end
|
170
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: typeout
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 5
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 4
|
9
|
+
- 1
|
10
|
+
version: 1.4.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Connor McKay
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-08 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: sanitize
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
description:
|
36
|
+
email:
|
37
|
+
- connor@verticalforest.com
|
38
|
+
executables: []
|
39
|
+
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files: []
|
43
|
+
|
44
|
+
files:
|
45
|
+
- README.rdoc
|
46
|
+
- LICENSE
|
47
|
+
- lib/typeout.js
|
48
|
+
- lib/typeout.rb
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: http://github.com/greneholt/typeout
|
51
|
+
licenses: []
|
52
|
+
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
hash: 3
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 17
|
73
|
+
segments:
|
74
|
+
- 1
|
75
|
+
- 3
|
76
|
+
- 5
|
77
|
+
version: 1.3.5
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project: typeout
|
81
|
+
rubygems_version: 1.3.7
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: Dead simple plain text to HTML converter
|
85
|
+
test_files: []
|
86
|
+
|