patricia 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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