documentation-editor 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -1
- data/app/assets/javascripts/documentation_editor/pages.js +113 -12
- data/app/assets/stylesheets/documentation_editor/pages.css.sass +5 -0
- data/app/controllers/documentation_editor/pages_controller.rb +3 -3
- data/app/views/documentation_editor/pages/edit.html.haml +39 -14
- data/app/views/documentation_editor/pages/history.html.haml +14 -2
- data/lib/documentation_editor.rb +2 -0
- data/lib/documentation_editor/parser.rb +17 -2
- data/lib/documentation_editor/version.rb +1 -1
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c1b887bb5ba7cc9014c287d2aa2ed28453c53f4
|
4
|
+
data.tar.gz: b7d916b3011f1837912961c63f48703189fe45c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a2f8125e3fc22d8af168e03d10ed5244e2e309524b0fc45cb254efebf04e55ede5a88934ccf193738607e882de6eb3ae9d7af7184400da17d3bdc07e31960c1
|
7
|
+
data.tar.gz: 4d3d7f39146758c4e62cd2bde32cfc6cff878d87ef69bf1ab4a3fa7a97c8c4bfbf62a763653ca4b480e76da64baaa9945c4d4bae01cf79d16e84f7163dd3ebb7
|
data/README.md
CHANGED
@@ -9,12 +9,13 @@ The goal of this project is to provide an easy & frictionless way to edit an onl
|
|
9
9
|
## Features
|
10
10
|
|
11
11
|
- Widgets
|
12
|
-
- Text:
|
12
|
+
- Text: full markdown support
|
13
13
|
- Callout: info/warning/danger
|
14
14
|
- Tables: customizable number of columns & rows
|
15
15
|
- Code: code snippet with highlighting
|
16
16
|
- Conditions: ability to display some sections based on some query parameters
|
17
17
|
- Image: image uploader + ability to store on S3
|
18
|
+
- Buttons: labeled buttons
|
18
19
|
- Caching
|
19
20
|
- Automatic TOC generation (anchors are automatically generated on each title)
|
20
21
|
- Raw edition mode
|
@@ -61,18 +61,20 @@ angular.module('documentationEditorApp', ['ngFileUpload'])
|
|
61
61
|
data.push(section.content + "\n");
|
62
62
|
} else {
|
63
63
|
data.push('[block:' + section.type + ']');
|
64
|
-
data.push(JSON.stringify(section.content));
|
64
|
+
data.push(JSON.stringify(section.content, null, 2));
|
65
65
|
data.push("[/block]\n");
|
66
66
|
}
|
67
67
|
});
|
68
68
|
return data.join("\n");
|
69
69
|
}
|
70
70
|
|
71
|
-
$scope.init = function(id, slug, thumbnailUrl, path) {
|
71
|
+
$scope.init = function(id, slug, thumbnailUrl, path, published_revision_id, last_revision_id) {
|
72
72
|
$scope.id = id;
|
73
73
|
$scope.slug = slug;
|
74
74
|
$scope.path = path;
|
75
75
|
$scope.thumbnailUrl = thumbnailUrl;
|
76
|
+
$scope.published_revision_id = published_revision_id;
|
77
|
+
$scope.last_revision_id = last_revision_id;
|
76
78
|
|
77
79
|
$http.get($scope.path + '/admin/' + id).then(function(content) {
|
78
80
|
$scope.source = content.data.source;
|
@@ -102,6 +104,9 @@ angular.module('documentationEditorApp', ['ngFileUpload'])
|
|
102
104
|
$scope.sections = $.grep($scope.sections, function(e) { return e.id !== o.data.obj.id });
|
103
105
|
} else if (o.type === 'deleteSection') {
|
104
106
|
$scope.sections = o.data.sections;
|
107
|
+
} else if (o.type === 'text') {
|
108
|
+
o.model.$setViewValue(o.data);
|
109
|
+
o.model.$render();
|
105
110
|
} else if (o.type === 'deleteLanguage' || o.type === 'addLanguage') {
|
106
111
|
var index = getIndex(o.data.id);
|
107
112
|
var obj = {
|
@@ -112,6 +117,16 @@ angular.module('documentationEditorApp', ['ngFileUpload'])
|
|
112
117
|
}
|
113
118
|
};
|
114
119
|
$scope.sections = [].concat($scope.sections.slice(0, index), [obj], $scope.sections.slice(index + 1));
|
120
|
+
} else if (o.type === 'deleteButton' || o.type === 'addButton') {
|
121
|
+
var index = getIndex(o.data.id);
|
122
|
+
var obj = {
|
123
|
+
id: $scope.sections[index].id,
|
124
|
+
type: 'buttonse',
|
125
|
+
content: {
|
126
|
+
buttons: o.data.buttons
|
127
|
+
}
|
128
|
+
};
|
129
|
+
$scope.sections = [].concat($scope.sections.slice(0, index), [obj], $scope.sections.slice(index + 1));
|
115
130
|
} else if (o.type === 'deleteColumn' || o.type === 'addColumn' || o.type === 'addRow' || o.type === 'deleteRow') {
|
116
131
|
var index = getIndex(o.data.id);
|
117
132
|
$scope.sections[index] = {
|
@@ -218,6 +233,22 @@ angular.module('documentationEditorApp', ['ngFileUpload'])
|
|
218
233
|
});
|
219
234
|
};
|
220
235
|
|
236
|
+
$scope.addButtons = function($event, id) {
|
237
|
+
$event.preventDefault();
|
238
|
+
add(id, {
|
239
|
+
id: getNextID(),
|
240
|
+
type: 'buttons',
|
241
|
+
content: {
|
242
|
+
buttons: [
|
243
|
+
{
|
244
|
+
label: 'FIXME',
|
245
|
+
link: 'http://example.org'
|
246
|
+
}
|
247
|
+
]
|
248
|
+
}
|
249
|
+
});
|
250
|
+
};
|
251
|
+
|
221
252
|
$scope.getIntegerIterator = function(v) {
|
222
253
|
var a = [];
|
223
254
|
for (var i = 0; i < v; ++i) {
|
@@ -263,15 +294,42 @@ angular.module('documentationEditorApp', ['ngFileUpload'])
|
|
263
294
|
$scope.sections[index].content.codes = [].concat($scope.sections[index].content.codes, [{ language: 'FIXME:language|label', code: '// FIXME' }]);
|
264
295
|
};
|
265
296
|
|
297
|
+
$scope.deleteButton = function($event, id, buttonIndex) {
|
298
|
+
$event.preventDefault();
|
299
|
+
var index = getIndex(id);
|
300
|
+
$scope.undoRedo.push({
|
301
|
+
type: 'deleteButton',
|
302
|
+
data: {
|
303
|
+
id: id,
|
304
|
+
buttons: $scope.sections[index].content.buttons
|
305
|
+
}
|
306
|
+
});
|
307
|
+
$scope.sections[index].content.buttons = $.grep($scope.sections[index].content.buttons, function(e, i) { return i !== buttonIndex; });
|
308
|
+
};
|
309
|
+
|
310
|
+
$scope.addButton = function($event, id) {
|
311
|
+
$event.preventDefault();
|
312
|
+
var index = getIndex(id);
|
313
|
+
$scope.undoRedo.push({
|
314
|
+
type: 'addButton',
|
315
|
+
data: {
|
316
|
+
id: id,
|
317
|
+
codes: $scope.sections[index].content.buttons
|
318
|
+
}
|
319
|
+
});
|
320
|
+
$scope.sections[index].content.buttons = [].concat($scope.sections[index].content.buttons, [{ label: 'FIXME', link: 'https://example.org' }]);
|
321
|
+
};
|
322
|
+
|
266
323
|
$scope.imageToAddAfter = 0;
|
267
324
|
$scope.saveImagePosition = function(id) {
|
268
325
|
$scope.imageToAddAfter = id;
|
269
326
|
};
|
270
|
-
$scope.addImage = function(url) {
|
327
|
+
$scope.addImage = function(url, caption) {
|
271
328
|
$('#editor-images').modal('hide');
|
272
329
|
add($scope.imageToAddAfter, {
|
273
330
|
type: 'image',
|
274
331
|
content: {
|
332
|
+
caption: caption,
|
275
333
|
images: [
|
276
334
|
{
|
277
335
|
image: [url]
|
@@ -370,6 +428,17 @@ angular.module('documentationEditorApp', ['ngFileUpload'])
|
|
370
428
|
section.content.rows = section.content.rows - 1;
|
371
429
|
};
|
372
430
|
|
431
|
+
$scope.addHTML = function($event, id) {
|
432
|
+
$event.preventDefault();
|
433
|
+
add(id, {
|
434
|
+
id: getNextID(),
|
435
|
+
type: 'html',
|
436
|
+
content: {
|
437
|
+
body: '<span>FIXME</span>'
|
438
|
+
}
|
439
|
+
});
|
440
|
+
};
|
441
|
+
|
373
442
|
$scope.addIf = function($event, id) {
|
374
443
|
$event.preventDefault();
|
375
444
|
add(id, {
|
@@ -421,8 +490,17 @@ angular.module('documentationEditorApp', ['ngFileUpload'])
|
|
421
490
|
});
|
422
491
|
};
|
423
492
|
|
493
|
+
$scope.diff = function() {
|
494
|
+
return $scope.undoRedo.length > 0 && !$scope.undoRedo[$scope.undoRedo.length - 1].saved;
|
495
|
+
};
|
496
|
+
|
424
497
|
function save(preview) {
|
425
|
-
return $http.post($scope.path + '/admin/' + $scope.id, { data: serialize($scope.sections), preview: preview })
|
498
|
+
return $http.post($scope.path + '/admin/' + $scope.id, { data: serialize($scope.sections), preview: preview }).then(function(content) {
|
499
|
+
if ($scope.undoRedo.length > 0) {
|
500
|
+
$scope.undoRedo[$scope.undoRedo.length - 1].saved = true;
|
501
|
+
}
|
502
|
+
$scope.last_revision_id = content.data.id;
|
503
|
+
});
|
426
504
|
}
|
427
505
|
|
428
506
|
$scope.save = function($event) {
|
@@ -464,7 +542,7 @@ angular.module('documentationEditorApp', ['ngFileUpload'])
|
|
464
542
|
file: file
|
465
543
|
}).success(function (data, status, headers, config) {
|
466
544
|
console.log('uploaded', data);
|
467
|
-
$scope.$parent.addImage(data.url);
|
545
|
+
$scope.$parent.addImage(data.url, data.caption);
|
468
546
|
}).error(function (data, status, headers, config) {
|
469
547
|
console.log('error', data);
|
470
548
|
alert('Error status: ' + status);
|
@@ -520,31 +598,54 @@ angular.module('documentationEditorApp', ['ngFileUpload'])
|
|
520
598
|
});
|
521
599
|
});
|
522
600
|
}]).directive('contenteditable', ['$document', function($document) {
|
601
|
+
var before = '';
|
602
|
+
|
523
603
|
return {
|
524
604
|
require: 'ngModel',
|
525
605
|
link: function(scope, element, attrs, ngModel) {
|
526
|
-
function
|
606
|
+
function update(undoable) {
|
527
607
|
// use the HTML form to keep the new lines
|
528
608
|
var text = element.html();
|
529
609
|
// replace the HTML newlines by \n
|
530
610
|
text = text.replace(/<div>/g, '<br>').replace(/<\/div>/g, '').replace(/<br>/g, "\n");
|
531
|
-
|
532
|
-
|
533
|
-
|
611
|
+
if (undoable) {
|
612
|
+
scope.undoRedo.push({
|
613
|
+
type: 'text',
|
614
|
+
model: ngModel,
|
615
|
+
data: before
|
616
|
+
});
|
617
|
+
ngModel.$setViewValue(text);
|
618
|
+
}
|
534
619
|
}
|
535
620
|
|
536
621
|
ngModel.$render = function() {
|
537
|
-
|
622
|
+
var val = ngModel.$viewValue || '';
|
623
|
+
if (val.indexOf('<') !== -1) {
|
624
|
+
// backward compat: before 2015/11/9 the values were not escaped
|
625
|
+
// consider them as text values
|
626
|
+
element.text(val);
|
627
|
+
} else {
|
628
|
+
element.html(val);
|
629
|
+
}
|
538
630
|
};
|
539
631
|
|
540
|
-
element.bind("
|
541
|
-
|
632
|
+
element.bind("focus", function() {
|
633
|
+
before = ngModel.$viewValue;
|
634
|
+
});
|
635
|
+
|
636
|
+
element.bind("blur", function() {
|
637
|
+
scope.$apply(update.bind(null, true));
|
638
|
+
});
|
639
|
+
|
640
|
+
element.bind("keyup change", function() {
|
641
|
+
scope.$apply(update.bind(null, false));
|
542
642
|
});
|
543
643
|
|
544
644
|
// force every copy/paste to be plain/text
|
545
645
|
element.bind('paste', function(e) {
|
546
646
|
e.preventDefault();
|
547
647
|
var text = (e.originalEvent || e).clipboardData.getData('text/plain') || '';
|
648
|
+
text = $('<div />').text(text).html(); // escape HTML entities, we're dealing with plain text here
|
548
649
|
$document[0].execCommand("insertHTML", false, text);
|
549
650
|
});
|
550
651
|
}
|
@@ -39,8 +39,8 @@ module DocumentationEditor
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def commit
|
42
|
-
@page.add_revision!(params[:data], params[:preview].to_s == 'false', respond_to?(:current_user) ? current_user.id : nil)
|
43
|
-
render
|
42
|
+
r = @page.add_revision!(params[:data], params[:preview].to_s == 'false', respond_to?(:current_user) ? current_user.id : nil)
|
43
|
+
render json: { id: r.id }
|
44
44
|
end
|
45
45
|
|
46
46
|
def create
|
@@ -81,7 +81,7 @@ module DocumentationEditor
|
|
81
81
|
image.caption = params[:caption]
|
82
82
|
image.image = params[:file]
|
83
83
|
image.save!
|
84
|
-
render json: { id: image.id, url: image.image.url }
|
84
|
+
render json: { id: image.id, url: image.image.url, caption: image.caption }
|
85
85
|
end
|
86
86
|
|
87
87
|
def upload_thumbnail
|
@@ -1,5 +1,5 @@
|
|
1
1
|
%div{"ng-app" => "documentationEditorApp"}
|
2
|
-
%section#documentation-editor{"ng-controller" => 'EditorController', "ng-init" => "init(#{@page.id}, #{@page.slug.to_json}, #{@page.thumbnail_url.to_json}, '#{admin_path.gsub(/\/admin$/, '')}')"}
|
2
|
+
%section#documentation-editor{"ng-controller" => 'EditorController', "ng-init" => "init(#{@page.id}, #{@page.slug.to_json}, #{@page.thumbnail_url.to_json}, '#{admin_path.gsub(/\/admin$/, '')}', #{@page.published_revision_id.to_i}, #{@page.revisions.last.id.to_i})"}
|
3
3
|
.editor-header
|
4
4
|
%ul.list-inline.pull-right
|
5
5
|
%li
|
@@ -7,15 +7,15 @@
|
|
7
7
|
%i.fa.fa-chevron-left
|
8
8
|
Back to Admin
|
9
9
|
%li
|
10
|
-
%a.btn.btn-
|
10
|
+
%a.btn.btn-default.btn-sm{href: '#', 'ng-click' => 'save($event)', 'ng-disabled' => '!diff()'}
|
11
11
|
%i.fa.fa-save
|
12
12
|
Save
|
13
13
|
%li
|
14
|
-
%a.btn.btn-
|
14
|
+
%a.btn.btn-success.btn-sm{href: '#', 'ng-click' => 'preview($event)'}
|
15
15
|
%i.fa.fa-eye
|
16
16
|
Save & Preview
|
17
17
|
%li
|
18
|
-
%a.btn.btn-danger.btn-sm{href: '#', 'ng-click' => 'publish($event)'}
|
18
|
+
%a.btn.btn-danger.btn-sm{href: '#', 'ng-click' => 'publish($event)', 'ng-disabled' => 'published_revision_id === last_revision_id'}
|
19
19
|
%i.fa.fa-save
|
20
20
|
Save & Publish
|
21
21
|
%li
|
@@ -54,12 +54,16 @@
|
|
54
54
|
%p
|
55
55
|
Caption:
|
56
56
|
%span.markdown{contenteditable: true, 'ng-model' => 'sections[sectionIndex].content.caption'}
|
57
|
-
%
|
58
|
-
|
59
|
-
|
60
|
-
%
|
61
|
-
|
62
|
-
|
57
|
+
%ul.list-inline
|
58
|
+
%li
|
59
|
+
Float:
|
60
|
+
%select{"ng-model" => "sections[sectionIndex].content.float"}
|
61
|
+
%option{value: ''} None
|
62
|
+
%option{value: 'left'} Left
|
63
|
+
%option{value: 'right'} Right
|
64
|
+
%li
|
65
|
+
Width:
|
66
|
+
%input{"ng-model" => "sections[sectionIndex].content.width", type: 'number'}
|
63
67
|
%table.table.parameters{"ng-if" => "section.type === 'parameters'"}
|
64
68
|
%thead
|
65
69
|
%tr
|
@@ -81,6 +85,17 @@
|
|
81
85
|
%i.fa.fa-plus-circle
|
82
86
|
%a{href: '#', 'ng-click' => 'deleteRow($event, section.id)'}>
|
83
87
|
%i.fa.fa-minus-circle
|
88
|
+
.buttons{"ng-if" => "section.type === 'buttons'"}
|
89
|
+
.pull-right
|
90
|
+
Button:
|
91
|
+
%a{href: '#', 'ng-click' => 'addButton($event, section.id)'}>
|
92
|
+
%i.fa.fa-plus-circle
|
93
|
+
%ul.list-inline
|
94
|
+
%li{"ng-repeat" => "button in section.content.buttons track by $index"}
|
95
|
+
%a.btn.btn-default{contenteditable: true, 'ng-model' => 'sections[sectionIndex].content.buttons[$index].label', href: '#'}
|
96
|
+
%input{'ng-model' => 'sections[sectionIndex].content.buttons[$index].link'}
|
97
|
+
%a{href: '#', 'ng-click' => 'deleteButton($event, section.id, $index)'}>
|
98
|
+
%i.fa.fa-remove>
|
84
99
|
.code{"ng-if" => "section.type === 'code'"}
|
85
100
|
.pull-right
|
86
101
|
Language:
|
@@ -93,6 +108,8 @@
|
|
93
108
|
%a{contenteditable: true, href: '#snippet_{{section.id}}_{{$index}}', 'data-toggle' => 'tab', 'ng-model' => 'sections[sectionIndex].content.codes[$index].language' }>
|
94
109
|
.tab-content
|
95
110
|
%pre.tab-pane{contenteditable: true, "ng-repeat" => "code in section.content.codes track by $index", "ng-class" => "{ 'in active': ($index === 0) }", id: 'snippet_{{section.id}}_{{$index}}', 'ng-model' => 'sections[sectionIndex].content.codes[$index].code'}
|
111
|
+
.html{"ng-if" => "section.type === 'html'"}
|
112
|
+
%pre.tab-pane{contenteditable: true, id: 'snippet_{{section.id}}_{{$index}}', 'ng-model' => 'sections[sectionIndex].content.body'}
|
96
113
|
|
97
114
|
.insert-sections
|
98
115
|
.pull-right
|
@@ -118,6 +135,9 @@
|
|
118
135
|
%a.btn.btn-default{href: '#', "data-toggle" => "modal", "data-target" => "#editor-images", "ng-click" => "saveImagePosition(section.id)"}
|
119
136
|
%i.fa.fa-image
|
120
137
|
Add image
|
138
|
+
%a.btn.btn-default{href: '#', 'ng-click' => 'addHTML($event, section.id)'}
|
139
|
+
%i.fa.fa-code
|
140
|
+
Add HTML
|
121
141
|
%a.btn.btn-info{href: '#', 'ng-click' => 'addCallout($event, "info", section.id)'}
|
122
142
|
%i.fa.fa-info-circle
|
123
143
|
Add info
|
@@ -136,6 +156,9 @@
|
|
136
156
|
%a.btn.btn-default{href: '#', 'ng-click' => 'addEndIf($event, section.id)'}
|
137
157
|
%i.fa.fa-scissors
|
138
158
|
\/IF
|
159
|
+
%a.btn.btn-default{href: '#', 'ng-click' => 'addButtons($event, section.id)'}
|
160
|
+
%i.fa.fa-link
|
161
|
+
Add buttons
|
139
162
|
|
140
163
|
#editor-images.modal.fade{"ng-controller" => "ImageUploaderController"}
|
141
164
|
.modal-dialog
|
@@ -147,19 +170,21 @@
|
|
147
170
|
|
148
171
|
.modal-body
|
149
172
|
- if DocumentationEditor::Image.count > 0
|
150
|
-
%
|
173
|
+
%h5 Choose an existing image:
|
151
174
|
.images
|
152
175
|
- DocumentationEditor::Image.limit(100).order('id DESC').each do |image|
|
153
|
-
%a{href: '#', 'ng-click' => "addImage('#{image.image.url}')"}= image_tag image.image.url
|
176
|
+
%a{href: '#', 'ng-click' => "addImage('#{image.image.url}', '#{escape_javascript image.caption}')"}= image_tag image.image.url
|
177
|
+
%hr
|
178
|
+
%h4.text-center Or
|
154
179
|
%hr
|
155
|
-
%
|
180
|
+
%h5 Upload a new one:
|
156
181
|
.form
|
157
182
|
.row.form-group
|
158
183
|
.col-sm-3.text-right Caption
|
159
184
|
.col-sm-9
|
160
185
|
%input.form-control{type: 'text', 'ng-model' => 'caption', placeholder: 'Optional caption'}
|
161
186
|
.text-center.form-group
|
162
|
-
.btn.btn-success{"ngf-select" => true, "ng-model" => 'files'}
|
187
|
+
.btn.btn-success{"ngf-select" => true, "ng-model" => 'files'} Browse & upload image
|
163
188
|
|
164
189
|
#upload-thumbnail.modal.fade{"ng-controller" => "ThumbnailUploaderController"}
|
165
190
|
.modal-dialog
|
@@ -8,15 +8,27 @@
|
|
8
8
|
%a.btn.btn-default.btn-sm{href: admin_path}
|
9
9
|
%i.fa.fa-chevron-left
|
10
10
|
Back to Admin
|
11
|
+
%li
|
12
|
+
%a.btn.btn-default.btn-sm{href: edit_page_path(@page)}
|
13
|
+
%i.fa.fa-edit
|
14
|
+
Edit
|
11
15
|
%h1
|
12
16
|
History of
|
13
17
|
%small
|
14
18
|
= page_path('')
|
15
19
|
= @page.slug
|
16
20
|
.editor-content
|
21
|
+
- if !@page.published_revision
|
22
|
+
.alert.alert-danger
|
23
|
+
%i.fa.fa-exclamation-triangle
|
24
|
+
This page has never been published
|
25
|
+
- elsif @page.revisions.last != @page.published_revision
|
26
|
+
.alert.alert-warning
|
27
|
+
%i.fa.fa-exclamation-triangle
|
28
|
+
The latest version of this page has not been published yet.
|
17
29
|
.row{"ng-controller" => 'HistoryController', "ng-init" => "init(#{@page.id}, '#{admin_path.gsub(/\/admin$/, '')}')"}
|
18
30
|
.col-sm-3
|
19
|
-
%table.table.table-striped
|
31
|
+
%table.table.table-striped.history
|
20
32
|
%thead
|
21
33
|
%tr
|
22
34
|
%th.text-right Revision
|
@@ -24,7 +36,7 @@
|
|
24
36
|
%th
|
25
37
|
%th Modification date
|
26
38
|
%tbody
|
27
|
-
%tr{"ng-repeat" => "revision in revisions track by revision.id"}
|
39
|
+
%tr{"ng-repeat" => "revision in revisions track by revision.id", 'ng-class' => "{published: (#{@page.published_revision_id.to_i} == revision.id)}"}
|
28
40
|
%td.text-right {{ revisions.length - $index }}
|
29
41
|
%td
|
30
42
|
%input{type: 'radio', name: 'prev', "ng-value" => "$index", "ng-model" => "diff.prev", "ng-if" => "$index > diff.cur"}
|
data/lib/documentation_editor.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'kramdown/document'
|
2
2
|
require 'kramdown/parser/kramdown'
|
3
3
|
require 'simplabs/highlight'
|
4
|
+
require 'htmlentities'
|
4
5
|
|
5
6
|
class Kramdown::Parser::BlockKramdown < Kramdown::Parser::Kramdown
|
6
7
|
|
@@ -45,6 +46,9 @@ class Kramdown::Parser::BlockKramdown < Kramdown::Parser::Kramdown
|
|
45
46
|
callout = new_block_el(:html_element, 'div', { class: "alert alert-#{content['type']}" })
|
46
47
|
callout.children << Element.new(:raw, parse_cached(content['body']))
|
47
48
|
@tree.children << callout
|
49
|
+
when 'html'
|
50
|
+
coder = HTMLEntities.new
|
51
|
+
@tree.children << Element.new(:raw, coder.decode(content['body']))
|
48
52
|
when 'image'
|
49
53
|
clazz = case content['float']
|
50
54
|
when 'left'
|
@@ -54,12 +58,23 @@ class Kramdown::Parser::BlockKramdown < Kramdown::Parser::Kramdown
|
|
54
58
|
else
|
55
59
|
nil
|
56
60
|
end
|
61
|
+
attrs = { src: content['images'][0]['image'][0] }
|
62
|
+
if !content['width'].blank?
|
63
|
+
attrs[:width] = content['width'].to_i
|
64
|
+
end
|
57
65
|
@tree.children << Element.new(:html_element, 'figure', { class: clazz })
|
58
|
-
@tree.children.last.children << Element.new(:img, nil,
|
66
|
+
@tree.children.last.children << Element.new(:img, nil, attrs)
|
59
67
|
unless content['caption'].blank?
|
60
68
|
@tree.children.last.children << Element.new(:html_element, 'figcaption')
|
61
69
|
@tree.children.last.children.last.children << Element.new(:raw, parse_cached(content['caption']))
|
62
70
|
end
|
71
|
+
when 'buttons'
|
72
|
+
buttons = new_block_el(:html_element, 'div', { class: "text-center" })
|
73
|
+
content['buttons'].each do |b|
|
74
|
+
buttons.children << Element.new(:html_element, 'a', { href: b['link'], class: 'btn btn-default' })
|
75
|
+
buttons.children.last.children << Element.new(:raw, b['label'])
|
76
|
+
end
|
77
|
+
@tree.children << buttons
|
63
78
|
when 'if'
|
64
79
|
@tree.children << Element.new(:comment, "if #{content['condition']}", { }, { start: true, condition: content['condition'], negation: false })
|
65
80
|
when 'ifnot'
|
@@ -127,7 +142,7 @@ class Kramdown::Parser::BlockKramdown < Kramdown::Parser::Kramdown
|
|
127
142
|
language
|
128
143
|
end
|
129
144
|
cache "#{language}_#{code.hash}" do
|
130
|
-
Simplabs::Highlight.highlight(language, code)
|
145
|
+
Simplabs::Highlight.highlight(language, CGI.unescapeHTML(code))
|
131
146
|
end
|
132
147
|
end
|
133
148
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: documentation-editor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Algolia Team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: htmlentities
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
125
139
|
description: The goal of this project is to provide an easy & frictionless way to
|
126
140
|
edit an online tech documentation. The sweet spot of this editor is to be able to
|
127
141
|
generate pages containing multiple snippets of highlighted code & conditional sections
|
@@ -186,7 +200,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
186
200
|
version: '0'
|
187
201
|
requirements: []
|
188
202
|
rubyforge_project:
|
189
|
-
rubygems_version: 2.
|
203
|
+
rubygems_version: 2.2.2
|
190
204
|
signing_key:
|
191
205
|
specification_version: 4
|
192
206
|
summary: Mountable Rails application providing an extended Markdown documentation
|