documentation-editor 0.5.0 → 0.6.0
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 +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
|