patricia 0.0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ef479d8eff067d6a70b52846c246763987cb72e8
4
- data.tar.gz: 538feff310e1df73d5f52c79d6da00e054bd268a
3
+ metadata.gz: 67fe48708657dc1541204315dcaedcb79f04479a
4
+ data.tar.gz: a3ebb2cc08e9f902773f89aa3c1d4894f9d1ec82
5
5
  SHA512:
6
- metadata.gz: e22e0b7aa71306f9e4015a98acedc315adf3bf21a38141b1f0f073918c0db23aa202e5fc43fcb38e0731e79755068a5fc86a44d290bc521c5d15bafa74fb952e
7
- data.tar.gz: d704365bc2c6e17f7966870f16f07905232899e4c185f4eeca0c7bfe623a49db1ba187fbbff1c4dd51960ff63261fc6dbcc4218ff46f11b9758419c0c248ad89
6
+ metadata.gz: 60803a1932ff8c839e000a8c1783af3de02e9a5a639e6b8df09c015098a4fa487c9f456974cbf2de51c6a60a14b9a421c0b99fad0790d1b6f979332c10218148
7
+ data.tar.gz: 6227b21360ab4648b651d0754da183b4c4b97da7a207ff0ef1a7dbd83f92a6a6cfbb2451b97780dbc0f3d9b696dca5b432c8d37221b9f32c48241b6ff61d2155
data/Gemfile CHANGED
@@ -7,6 +7,7 @@ gem 'kramdown'
7
7
  gem 'org-ruby'
8
8
  gem 'redcloth'
9
9
  gem 'pandoc-ruby'
10
+ gem 'github/markdown'
10
11
  gem 'rack'
11
12
  gem 'fileutils'
12
13
  gem 'sinatra'
data/README.md CHANGED
@@ -19,6 +19,16 @@ right file extension. Static files (images/PDFs/video/...) in the markup
19
19
  directory are served as is, so you can link to them from any page and the
20
20
  links will not break.
21
21
 
22
+ Patricia also comes with some optional goodies:
23
+
24
+ - Basic markup editor (`-e` flag) that allows you to select an element or
25
+ highlight text on a page and brings up the corresponding markup, if it
26
+ can figure it out.
27
+ - Tooltips (`-t` flag) which show you the full file path for every
28
+ directory and file in the sidebar by hovering over it.
29
+ - RegEx search across all pages (if you tag your pages using a consistent
30
+ pattern, you can use this to get a list of all pages for certain tags).
31
+
22
32
  ## Installation
23
33
 
24
34
  gem install patricia
@@ -75,3 +85,23 @@ To add support for a new language do this:
75
85
  possible, so that browsers will display them on a page instead of
76
86
  prompting to download the file.
77
87
  5. *Make sure the tests pass*
88
+
89
+ ## Editor
90
+
91
+ Patricia's rudimentary and completely optional markup editor can
92
+ be enabled using the `-e` flat. It allows you to edit the markup for
93
+ editable components by hovering over them or by simply selecting text. It
94
+ tries its best to figure out which part of the markup is responsible for
95
+ the selected element and highlights it so that one can immediately jump to
96
+ the corresponding markup for quick fixes or even larger changes. Keep in
97
+ mind that this is not perfect, but it definetly is usable and is quite
98
+ handy for a lot of small fixes across multiple files as well as correcting
99
+ typos.
100
+
101
+ ## Page search
102
+
103
+ There is the search box in the sidebar that allows you to quickly search
104
+ all page titles, but there is also a buillt-in search page that allows you
105
+ to search the actual text of each page using regular expressions. So if you
106
+ include (hash) tags on your pages, you can use this to search all matching
107
+ pages, for instance.
data/bin/patricia CHANGED
@@ -11,6 +11,8 @@ config = {
11
11
  :js_dir => CLI.options[:js],
12
12
  :markup_dir => CLI.options[:markup_dir],
13
13
  :tooltips => CLI.options[:tooltips],
14
+ :editor => CLI.options[:editor],
15
+ :gfm => CLI.options[:gfm],
14
16
  }
15
17
  config_file = File.dirname(__FILE__) + '/app_config.yml'
16
18
  # The thread ensures that the config file is written before the Sinatra app
data/lib/patricia/app.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'sinatra/base'
2
2
  require 'yaml'
3
+ require 'json'
4
+ require 'timeout'
3
5
 
4
6
 
5
7
  module PatriciaApp
@@ -36,6 +38,8 @@ module PatriciaApp
36
38
  set :app_js_path, '/patricia.js'
37
39
  set :app_public_folder, app_configs[:public_folder]
38
40
  set :app_tooltips, app_configs[:tooltips]
41
+ set :app_editor, app_configs[:editor]
42
+ set :app_gfm, app_configs[:gfm]
39
43
  # CSS
40
44
  if app_configs[:css_dir] != nil
41
45
  set :app_css_dir, app_configs[:css_dir]
@@ -79,10 +83,12 @@ module PatriciaApp
79
83
  'sh' => 'application/x-sh',
80
84
  '7z' => 'application/x-7z-compressed',
81
85
  'swf' => 'application/x-shockwave-flash',
86
+ 'xml' => 'application/xml;charset=utf-8',
82
87
  'avi' => 'video/x-msvideo',
83
88
  'txt' => 'text/plain',
84
89
  'css' => 'text/css',
85
90
  'csv' => 'text/csv',
91
+ 'html' => 'text/html',
86
92
  # Send markup source files
87
93
  #
88
94
  # One can argue if this is semantically correct (alternative:
@@ -121,8 +127,14 @@ module PatriciaApp
121
127
  end
122
128
 
123
129
  get '/' do
124
- @html = Patricia::Converter .to_html(File.dirname(__FILE__) +
130
+ if settings.app_gfm
131
+ html = Patricia::Converter.gfm_to_html(File.dirname(__FILE__) +
132
+ '/views/wiki/welcome.md')
133
+ else
134
+ html = Patricia::Converter.to_html(File.dirname(__FILE__) +
125
135
  '/views/wiki/welcome.md')
136
+ end
137
+ @html = '<div id="p-welcome-text-page" class="">' + html + '</div>'
126
138
  @toc = build_toc
127
139
  @title = generate_page_title
128
140
  @page_title = ''
@@ -161,11 +173,11 @@ module PatriciaApp
161
173
  beautiful_file_name = capitalize_all(file_name.gsub(/-/, ' '))
162
174
  beautiful_path = no_ext.split('/').collect do |s|
163
175
  capitalize_all(s.gsub(/-/, ' '))
164
- end.join(' > ')
176
+ end.delete_if(&:empty?).join(' > ')
165
177
  content = File.read(path)
166
178
  lines = content.split("\n").length
167
179
  if content =~ search_query
168
- @results << [beautiful_file_name, '/' + no_ext, beautiful_path,
180
+ @results << [beautiful_file_name, no_ext, beautiful_path,
169
181
  lines]
170
182
  end
171
183
  end
@@ -173,6 +185,52 @@ module PatriciaApp
173
185
  haml :search, :layout => :application
174
186
  end
175
187
 
188
+ post %r{/patricia/offsets/?} do
189
+ if settings.app_editor
190
+ file_content = File.read(File.join(settings.app_markup_dir,
191
+ params[:markup_url]))
192
+ offsets = []
193
+ # Time out if the RegEx is too complex.
194
+ begin
195
+ Timeout::timeout(3) do
196
+ filler = " *\n*\w*"
197
+ pattern = Regexp.new(filler, Regexp::MULTILINE)
198
+ params[:string].split.each do |token|
199
+ pattern =
200
+ Regexp.new(pattern.to_s + filler + Regexp.quote(token),
201
+ Regexp::MULTILINE)
202
+ end
203
+ pattern = Regexp.new(pattern.to_s + filler, Regexp::MULTILINE)
204
+
205
+ # Skip empty/whitespace only queries.
206
+ if !pattern.to_s.empty? && !(pattern.to_s =~ /^\s*$/)
207
+ file_content.scan(pattern) do |c|
208
+ offsets << [$~.offset(0)[0], $~.offset(0)[1]]
209
+ end
210
+ end
211
+ end
212
+ rescue
213
+ # Ignore.
214
+ end
215
+
216
+ content_type 'json'
217
+ {:markup => file_content, :offsets => offsets}.to_json
218
+ else
219
+ redirect to('/404')
220
+ end
221
+ end
222
+
223
+ post %r{/patricia/edit/?} do
224
+ if settings.app_editor
225
+ File.open(File.join(settings.app_markup_dir,
226
+ params[:markup_url]), 'w') do |f|
227
+ f.puts(params[:string])
228
+ end
229
+ else
230
+ redirect to('/404')
231
+ end
232
+ end
233
+
176
234
  get settings.app_css_path do
177
235
  pwd = File.dirname(__FILE__)
178
236
  css = ''
@@ -196,6 +254,7 @@ module PatriciaApp
196
254
  '/assets/javascripts/app.js',
197
255
  ]
198
256
  files << '/assets/javascripts/tooltips.js' if settings.app_tooltips
257
+ files << '/assets/javascripts/editor.js' if settings.app_editor
199
258
  files.each do |path|
200
259
  js << File.read(pwd + path) + "\n\n"
201
260
  end
@@ -215,7 +274,11 @@ module PatriciaApp
215
274
  file_path = Dir[settings.app_markup_dir + '/' + path +
216
275
  settings.app_markdown_glob].first
217
276
  @markup_url = file_path.gsub(/#{settings.app_markup_dir}/, '')
218
- @html = Patricia::Converter.to_html(file_path)
277
+ if settings.app_gfm
278
+ @html = Patricia::Converter.gfm_to_html(file_path)
279
+ else
280
+ @html = Patricia::Converter.to_html(file_path)
281
+ end
219
282
  @toc = build_toc
220
283
  arrow = ' > '
221
284
  @title = generate_page_title
@@ -1,21 +1,20 @@
1
1
  $(document).ready(function() {
2
2
 
3
+ var self = $(this);
4
+
3
5
  // Helpers
4
6
 
5
7
  $.fn.elementText = function() {
6
- var str = '';
7
- $(this).contents().each(function() {
8
- if ($(this).nodeType == 3) {
9
- str += $(this).textContent || $(this).innerText || '';
10
- }
11
- });
12
- return str;
8
+ return $(this).clone().children().remove().end().text();
13
9
  };
14
10
 
11
+ // Aesthetical enhancements
12
+
13
+ $('table').addClass('table table-striped table-responsive')
14
+
15
15
 
16
16
  // Sidebar expanding
17
17
 
18
- var self = $(this);
19
18
  self.sidebarExpanded = false;
20
19
  self.originalSidebarStyle = {
21
20
  'width': '',
@@ -82,6 +81,11 @@ href="/">Close</a></p>\
82
81
  <p><code>s</code><span>: Select sidebar search box</span></p>\
83
82
  <p><code>Esc</code><span>: Unselect sidebar search box</span></p>\
84
83
  <p><code>p</code><span>: Go to page search page</span></p>\
84
+ <p><code>e</code><span>: Edit selected text</span></p>\
85
+ <strong>Editor textarea:</strong>\
86
+ <p><code>Ctrl</code> + <code>RET</code><span>: Save changes \
87
+ <p><code>Alt</code> + <code>p</code><span>: Go to previous match \
88
+ <p><code>Alt</code> + <code>n</code><span>: Go to next match \
85
89
  </div>\
86
90
  '
87
91
  );
@@ -94,7 +98,7 @@ href="/">Close</a></p>\
94
98
  'top': '50%',
95
99
  'left': '50%',
96
100
  'width': '500px',
97
- 'height': '400px',
101
+ 'height': '430px',
98
102
  'margin-left': '-200px',
99
103
  'margin-top': '-300px',
100
104
  'z-index': '9999',
@@ -0,0 +1,269 @@
1
+ $(document).ready(function() {
2
+
3
+ var self = $(this);
4
+
5
+ // Helpers
6
+
7
+ self.getSelectedText = function() {
8
+ var text = '';
9
+ if (window.getSelection) {
10
+ text = window.getSelection().toString();
11
+ } else if (document.getSelection) {
12
+ text = document.getSelection().toString();
13
+ } else if (document.selection) {
14
+ text = document.selection.createRange().text;
15
+ }
16
+ return text;
17
+ };
18
+
19
+ self.scrollToSelectedText = function(textArea, start, end) {
20
+ var charsPerRow = textArea.attr('cols');
21
+ var selectionRow = (start - (start % charsPerRow)) / charsPerRow;
22
+ var lineHeight = textArea.height() / textArea.attr('rows');
23
+ textArea.scrollTop(lineHeight * selectionRow +
24
+ (textArea.height() / 2));
25
+ };
26
+
27
+
28
+ // Editing
29
+
30
+ self.hoveredText = '';
31
+ self.hoveredElementBgColor = '';
32
+ self.hoveredElementHighlightColor = '#DFDFD2';
33
+ self.editButton = $('#edit-button');
34
+ self.mouseX = 0;
35
+ self.mouseY = 0;
36
+ $(document).mousemove(function(e) {
37
+ self.mouseX = e.pageX;
38
+ self.mouseY = e.pageY;
39
+ });
40
+
41
+ self.currentlyHighlightedText = '';
42
+
43
+ // Get all leaf node children of `#content'.
44
+ //
45
+ // Alternative: The following is a method to get only the first level
46
+ // children of `#content'.
47
+ //
48
+ // $('#content > *:not(:has(*))').hover(function() {
49
+ //
50
+ $('#content *').filter(function(index) {
51
+ var isLeaf = $(this).children().length == 0;
52
+ return isLeaf;
53
+ }).hover(function() {
54
+ if (!$('#p-welcome-text-page').length) {
55
+ $(this).css({'background-color': self.hoveredElementHighlightColor});
56
+ }
57
+ // Postion the edit button
58
+ if (self.editButton.hasClass('hidden')) {
59
+ self.editButton.removeClass('hidden');
60
+ } else {
61
+ self.editButton.toggle();
62
+ }
63
+ var that = $(this);
64
+ that.setCurrentlyHighlightedText = function() {
65
+ self.currentlyHighlightedText = that.elementText();
66
+ };
67
+ // Append the edit button after setting
68
+ // `self.currentlyHighlightedText' or else the text will include the
69
+ // edit button text.
70
+ $.when(that.setCurrentlyHighlightedText()).done(function() {
71
+ that.append(self.editButton);
72
+ });
73
+ }, function() {
74
+ $(this).css({'background-color': self.hoveredElementBgColor});
75
+ // Hide the edit button.
76
+ self.editButton.toggle();
77
+ // Remove it from the previous element, but keep it in the DOM. If it
78
+ // is not in the DOM, it is not clickable.
79
+ $('body').append(self.editButton);
80
+ });
81
+
82
+ self.offsets = [];
83
+ self.currentOffset = [];
84
+ self.currentOffsetIndex = 0;
85
+ self.textArea = $('#edit-modal textarea');
86
+ self.editModal = $('#edit-modal');
87
+ self.editNavigationBox = $('#edit-navigation-box');
88
+ self.editSelectedText = function() {
89
+ $.ajax({
90
+ 'type': 'POST',
91
+ 'url': self.editButton.attr('href'),
92
+ 'data': {
93
+ 'string': self.currentlyHighlightedText,
94
+ 'markup_url': self.editButton.attr('data-markup-url'),
95
+ },
96
+ 'success': function(data, status) {
97
+ // Show the edit modal
98
+ self.editModal.modal('show');
99
+ $('#currently-selected-text-info').text(
100
+ self.currentlyHighlightedText);
101
+ self.textArea.val(data['markup']);
102
+ self.textArea.focus();
103
+ self.offsets = 0; // Reset.
104
+ self.offsets = data['offsets']
105
+ self.currentOffsetIndex = 0;
106
+ self.currentOffset = self.offsets[self.currentOffsetIndex] ||
107
+ [0, 0];
108
+ $('#current-offset-index').text(self.currentOffsetIndex + 1 +
109
+ ' / ');
110
+ $('#offsets-length').text(self.offsets.length);
111
+ if (self.offsets.length > 1) {
112
+ $('#edit-navigation-box').show();
113
+ self.editNavigationBox.show();
114
+ } else {
115
+ self.editNavigationBox.hide();
116
+ }
117
+
118
+ self.editModal.on('shown.bs.modal', function() {
119
+ self.textArea.focus();
120
+ self.textArea[0].setSelectionRange(self.currentOffset[0],
121
+ self.currentOffset[1])
122
+ self.scrollToSelectedText(self.textArea, self.currentOffset[0],
123
+ self.currentOffset[1]);
124
+ });
125
+ },
126
+ 'data-type': 'json',
127
+ });
128
+ };
129
+
130
+ self.editButton.on('click', function(e) {
131
+ e.preventDefault();
132
+ self.leftEditButton.hide();
133
+ self.editSelectedText();
134
+ });
135
+
136
+ self.leftEditButton = $('#left-edit-button');
137
+
138
+ self.leftEditButton.on('click', function(e) {
139
+ e.preventDefault();
140
+ // e.stopPropagation();
141
+ self.leftEditButton.hide();
142
+ self.editSelectedText();
143
+ });
144
+
145
+ $('#content').on('mouseup', function(e) {
146
+ if (self.getSelectedText() != '') {
147
+ self.currentlyHighlightedText = self.getSelectedText();
148
+ }
149
+ if (self.getSelectedText() != '') {
150
+ // if (self.currentlyHighlightedText != '') {
151
+ if (self.leftEditButton.hasClass('hidden')) {
152
+ self.leftEditButton.removeClass('hidden');
153
+ self.leftEditButton.show();
154
+ } else {
155
+ self.leftEditButton.show();
156
+ }
157
+ self.leftEditButton.css({
158
+ 'position': 'absolute',
159
+ 'top': self.mouseY - (self.leftEditButton.height() + 20),
160
+ 'left': self.mouseX - (self.leftEditButton.width()),
161
+ 'z-index': 9998,
162
+ 'box-shadow': '0px 5px 3px rgba(0, 0, 0, 0.3), \
163
+ 0px 0px 8px rgba(102, 175, 233, 0.6)',
164
+ 'background-color': '#121212',
165
+ 'color': '#F1F1F1',
166
+ 'margin-top': '0',
167
+ 'font-weight': 'bold',
168
+ 'border': 'none',
169
+ 'border-radius': '5px',
170
+ 'border': '2px solid #F1F1F1',
171
+ });
172
+ self.leftEditButton.animate({
173
+ 'margin-top': '-10px',
174
+ }, 230);
175
+ }
176
+ });
177
+
178
+ $('*').not(self.leftEditButton).on('mousedown', function(e) {
179
+ e.stopPropagation();
180
+ window.setTimeout(function() {
181
+ self.leftEditButton.hide();
182
+ }, 130);
183
+ });
184
+
185
+ $(document).keypress(function() {
186
+ self.leftEditButton.hide();
187
+ });
188
+
189
+ // Navigation between matches
190
+
191
+ $('#previous-match-button').click(function(e) {
192
+ e.preventDefault();
193
+ if (self.currentOffsetIndex > 0) {
194
+ self.currentOffsetIndex -= 1;
195
+ } else {
196
+ self.currentOffsetIndex = self.offsets.length - 1;
197
+ }
198
+ self.currentOffset = self.offsets[self.currentOffsetIndex];
199
+ self.textArea.focus();
200
+ self.textArea[0].setSelectionRange(self.currentOffset[0],
201
+ self.currentOffset[1])
202
+ self.scrollToSelectedText(self.textArea, self.currentOffset[0],
203
+ self.currentOffset[1]);
204
+ $('#current-offset-index').text(self.currentOffsetIndex + 1 + ' / ');
205
+ });
206
+
207
+ $('#next-match-button').click(function(e) {
208
+ e.preventDefault();
209
+ if (self.currentOffsetIndex < self.offsets.length - 1) {
210
+ self.currentOffsetIndex += 1;
211
+ } else {
212
+ self.currentOffsetIndex = 0;
213
+ }
214
+ self.currentOffset = self.offsets[self.currentOffsetIndex];
215
+ self.textArea.focus();
216
+ self.textArea[0].setSelectionRange(self.currentOffset[0],
217
+ self.currentOffset[1])
218
+ self.scrollToSelectedText(self.textArea, self.currentOffset[0],
219
+ self.currentOffset[1]);
220
+ $('#current-offset-index').text(self.currentOffsetIndex + 1 + ' / ');
221
+ });
222
+
223
+
224
+ // 101: e - Edit selected text
225
+ $(document).on('keypress', function(e) {
226
+ if (e.target.nodeName != 'INPUT' && e.target.nodeName != 'TEXTAREA') {
227
+ if (e.which == 101) {
228
+ self.editSelectedText();
229
+ }
230
+ }
231
+ });
232
+
233
+ // Saving
234
+
235
+ self.saveButton = $('#edit-save-button');
236
+ self.saveButton.on('click', function() {
237
+ $.ajax({
238
+ 'type': 'POST',
239
+ 'url': self.saveButton.attr('data-url'),
240
+ 'data': {
241
+ 'markup_url': self.editButton.attr('data-markup-url'),
242
+ 'string': self.textArea.val(),
243
+ },
244
+ });
245
+ location.reload();
246
+ });
247
+
248
+ // 17: Ctrl
249
+ // 13: RET
250
+ // 18: Alt
251
+ // 80: p
252
+ // 78: n
253
+ self.keys = {};
254
+ self.textArea.on('keydown', function(e) {
255
+ self.keys[e.which] = true;
256
+ if (self.keys[13] == true && self.keys[17] == true) {
257
+ self.saveButton.click();
258
+ } else if (self.keys[18] == true && self.keys[80] == true) {
259
+ $('#previous-match-button').click();
260
+ } else if (self.keys[18] == true && self.keys[78] == true) {
261
+ $('#next-match-button').click();
262
+ }
263
+ });
264
+
265
+ self.textArea.on('keyup', function(e) {
266
+ delete self.keys[e.which];
267
+ });
268
+
269
+ });
@@ -20,3 +20,86 @@ img {
20
20
  max-width: 100%;
21
21
  height: auto;
22
22
  }
23
+
24
+ #edit-button {
25
+ margin-left: 10px;
26
+ z-index: 9998;
27
+ }
28
+
29
+ #edit-modal .modal-dialog {
30
+ width: 80%;
31
+ }
32
+
33
+ #edit-modal textarea {
34
+ font-family: monospace;
35
+ }
36
+
37
+ /* Page title */
38
+
39
+ .page-title {
40
+ line-height: 30px;
41
+ border-top: 1px solid #E3E3E3;
42
+ }
43
+
44
+ body {
45
+ background-color: #F5F5F5;
46
+ margin-top: 20px;
47
+ }
48
+
49
+ #content {
50
+ background-color: #FFFFFF;
51
+ color: #393939;
52
+ padding: 25px;
53
+ border: 1px solid #E3E3E3;
54
+ border-radius: 4px;
55
+ }
56
+
57
+ #p-sidebar {
58
+ background-color: #FFFFFF;
59
+ color: #393939;
60
+ border: 1px solid #E3E3E3;
61
+ border-radius: 4px;
62
+ }
63
+
64
+ .top-panel {
65
+ margin-bottom: 40px;
66
+ }
67
+
68
+ /* Welcome page */
69
+
70
+ #p-welcome-text-page h1 {
71
+ background-color: #428BCA;
72
+ padding: 10px;
73
+ padding-top: 15px;
74
+ color: #F1F1F1;
75
+ border-radius: 5px;
76
+ text-align: center;
77
+ }
78
+
79
+ #p-welcome-text-page {
80
+ border: 5px solid #E3E3E3;
81
+ background-color: #ECF4FF;
82
+ padding: 20px;
83
+ padding-top: 5px;
84
+ border-radius: 5px;
85
+ }
86
+
87
+ #p-welcome-text-page h2 {
88
+ background-color: #FFFFFF;
89
+ border: 5px solid #428BCA;
90
+ color: #428BCA;
91
+ padding: 10px;
92
+ padding-top: 15px;
93
+ border-radius: 5px;
94
+ text-align: center;
95
+ }
96
+
97
+ #currently-selected-text-info {
98
+ height: 50px;
99
+ overflow: hidden;
100
+ border: none !important;
101
+ }
102
+
103
+ #currently-selected-text-info:hover {
104
+ overflow: scroll;
105
+ }
data/lib/patricia/cli.rb CHANGED
@@ -16,6 +16,11 @@ module CLI
16
16
  opts.separator ''
17
17
  opts.separator 'Options:'
18
18
 
19
+ opts.on('-p', '--port PORT', Integer,
20
+ 'Port to run the server on') do |v|
21
+ options[:port] = v
22
+ end
23
+
19
24
  opts.on('-c', '--css DIR', 'Directory with CSS files to be',
20
25
  'included in the output',
21
26
  ' (All .css files in this directory',
@@ -35,9 +40,13 @@ module CLI
35
40
  options[:tooltips] = v
36
41
  end
37
42
 
38
- opts.on('-p', '--port PORT', Integer,
39
- 'Port to run the server on') do |v|
40
- options[:port] = v
43
+ opts.on('-e', '--editor', 'Enable markup editor') do |v|
44
+ options[:editor] = v
45
+ end
46
+
47
+ opts.on('-g', '--gfm',
48
+ 'Use GitHub Flavored Markdown for all', ' rendering') do |v|
49
+ options[:gfm] = v
41
50
  end
42
51
 
43
52
  # Description
@@ -3,6 +3,7 @@ require 'kramdown'
3
3
  require 'org-ruby'
4
4
  require 'redcloth'
5
5
  require 'pandoc-ruby'
6
+ require 'github/markup'
6
7
 
7
8
 
8
9
  module Patricia
@@ -51,10 +52,13 @@ module Patricia
51
52
  end
52
53
 
53
54
  def self.to_html(path)
54
- markup = File.open(path, 'r').read
55
+ markup = File.read(path)
55
56
  self.send @@converters[File.extname(path).sub(/^\./, '')], markup
56
57
  end
57
58
 
59
+ def self.gfm_to_html(path)
60
+ GitHub::Markup.render(path)
61
+ end
58
62
  end
59
63
 
60
64
  class Wiki
@@ -1,3 +1,3 @@
1
1
  module Patricia
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -1,21 +1,23 @@
1
1
  %br
2
2
  %div.container
3
3
  %div.row
4
- %div.col.col-md-7.col-md-offset-1#content
5
- %div.row
6
- %div.col.col-md-8
7
- %a{:href => "/"}
8
- %strong= @title if defined?(@title)
9
- - if defined?(@page_title)
10
- - if !@page_title.empty?
11
- |
12
- %span.text-muted= @breadcrumb
13
- = @page_title if defined?(@page_title)
14
- %div.col.col-md-4
4
+ %div.col.col-md-7.col-md-offset-1
5
+ %div.row.panel.panel-default.panel-body.top-panel
6
+ %div.col.col-md-9
7
+ %p
8
+ %strong
9
+ %a{:href => "/"}= @title if defined?(@title)
10
+ %div.col.col-md-3
15
11
  %p.text-right
16
12
  %a.text-muted.small#p-page-search-link{:href => "/patricia/search"} Search pages >
17
- %hr
18
- = @html if defined?(@html)
13
+ %div.row
14
+ %div.col.col-md-12.page-title
15
+ %span.text-muted= @breadcrumb
16
+ = @page_title if defined?(@page_title)
17
+
18
+ %div.row#content
19
+ = preserve do
20
+ = @html if defined?(@html)
19
21
  %hr
20
22
  - if defined?(@markup_url)
21
23
  %p.text-center
@@ -29,3 +31,29 @@
29
31
  - if defined?(@toc)
30
32
  %div#toc
31
33
  = @toc
34
+ - if defined?(@markup_url)
35
+ %a.hidden#edit-button{:href => "/patricia/offsets", 'data-markup-url' => "#{@markup_url}"} Edit
36
+ %a.hidden#left-edit-button.btn.btn-default{:href => "/patricia/offsets", 'data-markup-url' => "#{@markup_url}"} Edit
37
+
38
+ %div.modal.fade#edit-modal{:tabindex => '-1', :role => 'dialog'}
39
+ %div.modal-dialog.modal-lg
40
+ %div.modal-content
41
+ %div.modal-header
42
+ %button.close{:type => 'button', 'data-dismiss' => 'modal', 'aria-hidden' => 'true'} &times;
43
+ %h4.no-style.modal-title Edit Page Markup
44
+ %div.modal-body
45
+ %pre#currently-selected-text-info{:title => 'Selected text', 'data-toggle' => 'tooltip', 'data-placement' => 'top'}
46
+ %textarea.form-control{:rows => 15, :cols => 100}
47
+ %div.container#edit-navigation-box
48
+ %div.row
49
+ %br
50
+ %div.col-md-2.col-md-offset-3
51
+ %a#previous-match-button.btn.btn-default.btn-block{:href => '#'} < Previous match
52
+ %div.col-md-1
53
+ %span#current-offset-index
54
+ %span#offsets-length
55
+ %div.col-md-2
56
+ %a#next-match-button.btn.btn-default.btn-block{:href => '#'} Next match >
57
+ %div.modal-footer
58
+ %button.btn.btn-default{:type => 'button', 'data-dismiss' => 'modal'} Cancel
59
+ %button.btn.btn-primary#edit-save-button{:type => 'button', 'data-dismiss' => 'modal', 'data-url' => '/patricia/edit'} Save
@@ -93,8 +93,34 @@ user, run:
93
93
  ## Sidebar
94
94
 
95
95
  - The search box is case-insensitive, but otherwise RegEx-aware.
96
-
97
96
  - The sidebar can be widened. Click `Widen sidebar` or hit the `w` key.
97
+ - Hit `s` to focus the search bar from wherever you are on a page.
98
+
99
+ ## Editor
100
+
101
+ - Use the `-e` flag to enable the editor.
102
+ - There are three ways to edit text:
103
+ 1. Hover over elements. Editable elements will present you with an `Edit`
104
+ link.
105
+ 2. Hover over an editable element and press the `e` key.
106
+ 3. Highlight some arbitrary text and press the `Edit` button that appears
107
+ or hit the `e` key.
108
+ - Patricia tries to figure out which markup source matches the selected
109
+ text. If there are multiple matches, you can step through all of them.
110
+
111
+ ## Tooltips
112
+
113
+ ... can be enabled using the `-t` flag and will show tooltips for every
114
+ sidebar item.
115
+
116
+ ## Page Search
117
+
118
+ - There is a `Search pages` link on every page.
119
+ - The search is performed across all markup source files and will return a
120
+ list of matching pages.
121
+ - Regular expressions can be used.
122
+
123
+ ## Keyboard Shortcuts
98
124
 
99
125
  - Press `?` to get a list of all keyboard shortcuts.
100
126
 
data/spec/app_spec.rb CHANGED
@@ -25,6 +25,7 @@ describe "PatriciaApp::App" do
25
25
  :css_dir => nil,
26
26
  :js_dir => nil,
27
27
  :tooltips => false,
28
+ :editor => true,
28
29
  }
29
30
  load_config(config)
30
31
  end
@@ -69,8 +70,8 @@ request" do
69
70
  end
70
71
 
71
72
  describe "Markup language: reStructuredText" do
72
- it "reads a reStructuredText file and renders it as HTML upon a GET \
73
- request" do
73
+ it "reads a reStructuredText file and renders it as HTML upon a \
74
+ GET request" do
74
75
  get '/colors/light-pink'
75
76
  expect(last_response).to be_ok
76
77
  expect(last_response.body)
@@ -83,8 +84,18 @@ request" do
83
84
  describe "Return static files" do
84
85
  describe "Static file: PNG image" do
85
86
  it "returns a PNG image uppon a GET request" do
86
- get '/colors/red.md'
87
+ get '/colors/image.png'
88
+ expect(last_response).to be_ok
89
+ expect(last_response.headers['Content-Type']).to eq('image/png')
90
+ end
91
+ end
92
+
93
+ describe "Static file: HTML image" do
94
+ it "returns a HTML image uppon a GET request" do
95
+ get '/colors/cyan.html'
87
96
  expect(last_response).to be_ok
97
+ expect(last_response.headers['Content-Type'])
98
+ .to eq('text/html;charset=utf-8')
88
99
  end
89
100
  end
90
101
 
@@ -142,6 +153,24 @@ upon a GET request" do
142
153
  end
143
154
  end
144
155
 
156
+ # Page Editing
157
+
158
+ describe "Page Editing" do
159
+ it "finds all matches for a sting in a page's markup and returns \
160
+ the markup together with the match offsets (POST)" do
161
+ post '/patricia/offsets', {:markup_url =>
162
+ 'colors/red.md', :string => 'red'}
163
+ expect(last_response).to be_ok
164
+ end
165
+
166
+ # it "updates the markup for a page with new markup (POST)" do
167
+ # new_markup = "# New markup\n\nThis is some *new* markup.\n"
168
+ # post '/patricia/edit', {:markup_url =>
169
+ # 'colors/red.md', :string => new_markup}
170
+ # expect(last_response).to be_ok
171
+ # end
172
+ end
173
+
145
174
  end
146
175
 
147
176
  describe "Use custom CSS and JavaScript" do
@@ -152,6 +181,7 @@ upon a GET request" do
152
181
  :js_dir => File.join(File.dirname(__FILE__),
153
182
  'assets/javascripts/'),
154
183
  :tooltips => false,
184
+ :editor => false,
155
185
  }
156
186
  load_config(config)
157
187
  end
@@ -0,0 +1,18 @@
1
+ <html>
2
+ <head>
3
+ <title>Cyan</title>
4
+ <link rel="stylesheet" href="" type="text/css" />
5
+ </head>
6
+
7
+ <body>
8
+
9
+ <h1>Color: Cyan</h1>
10
+ <h2>Description</h2>
11
+ <p>This is cyan.</p>
12
+ <hr />
13
+ <h2>Info</h2>
14
+ <p>Here is some info.</p>
15
+
16
+ <script type="text/javascript" src=""></script>
17
+ </body>
18
+ </html>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: patricia
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - nounch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-03 00:00:00.000000000 Z
11
+ date: 2014-06-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -57,6 +57,7 @@ files:
57
57
  - lib/patricia/app.rb
58
58
  - lib/patricia/assets/javascripts/app.js
59
59
  - lib/patricia/assets/javascripts/bootstrap.min.js
60
+ - lib/patricia/assets/javascripts/editor.js
60
61
  - lib/patricia/assets/javascripts/jquery-1.11.0.min.js
61
62
  - lib/patricia/assets/javascripts/tooltips.js
62
63
  - lib/patricia/assets/stylesheets/app.css
@@ -85,6 +86,7 @@ files:
85
86
  - spec/random-test-wiki/amazing-animals/tall/giraffe.md
86
87
  - spec/random-test-wiki/colors/blue.md
87
88
  - spec/random-test-wiki/colors/bright-orange.org
89
+ - spec/random-test-wiki/colors/cyan.html
88
90
  - spec/random-test-wiki/colors/dark-yellow.textile
89
91
  - spec/random-test-wiki/colors/file.txt
90
92
  - spec/random-test-wiki/colors/green.markdown
@@ -132,6 +134,7 @@ test_files:
132
134
  - spec/random-test-wiki/amazing-animals/tall/giraffe.md
133
135
  - spec/random-test-wiki/colors/blue.md
134
136
  - spec/random-test-wiki/colors/bright-orange.org
137
+ - spec/random-test-wiki/colors/cyan.html
135
138
  - spec/random-test-wiki/colors/dark-yellow.textile
136
139
  - spec/random-test-wiki/colors/file.txt
137
140
  - spec/random-test-wiki/colors/green.markdown