Skip to content

Commit 0dd8547

Browse files
committed
Markdown editor toolbar for document edit
Summary: * Using [SimpleMDE](https://simplemde.com/) * Affixing the toolbars on scroll * Disabled fullscreen and side-by-side mode Resolves T288 Test Plan: * Go to document edit page * Use the markdown editor * Try each button, including preview * Save and make sure things save ok Reviewers: doshitan Reviewed By: doshitan Maniphest Tasks: T288 Differential Revision: https://phabricator.opengovfoundation.org/D177
1 parent e872e14 commit 0dd8547

File tree

11 files changed

+400
-927
lines changed

11 files changed

+400
-927
lines changed

gulpfile.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ elixir((mix) => {
2020
.webpack('annotator-madison.js')
2121
.webpack('app.js')
2222
.webpack('document.js')
23+
.webpack('document-edit.js')
2324
.combine(
2425
[
2526
'node_modules/jquery/dist/jquery.min.js',
2627
'node_modules/bootstrap-sass/assets/javascripts/bootstrap.min.js',
2728
'node_modules/select2/dist/js/select2.min.js',
2829
'resources/assets/vendor/js/annotator-full.min.js',
2930
'resources/assets/vendor/js/modernizr-custom.js',
31+
'node_modules/simplemde/dist/simplemde.min.js',
3032
],
3133
'public/js/vendor.js'
3234
)
@@ -38,6 +40,7 @@ elixir((mix) => {
3840
'js/annotator-madison.js',
3941
'js/app.js',
4042
'js/document.js',
43+
'js/document-edit.js',
4144
'js/vendor.js'
4245
])
4346
;

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
},
77
"devDependencies": {
88
"bootstrap-sass": "^3.3.7",
9+
"font-awesome": "^4.7.0",
910
"gulp": "^3.9.1",
1011
"jquery": "^3.1.0",
1112
"laravel-elixir": "^6.0.0-14",
1213
"laravel-elixir-webpack-official": "^1.0.2",
1314
"lodash": "^4.16.2",
14-
"font-awesome": "^4.7.0",
1515
"select2": "^4.0.3",
16-
"select2-bootstrap-theme": "0.1.0-beta.9"
16+
"select2-bootstrap-theme": "0.1.0-beta.9",
17+
"simplemde": "^1.11.2"
1718
}
1819
}

resources/assets/js/app.js

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,6 @@ window.redirectToLogin = function() {
4242
window.location.href = '/login?redirect='+window.location.pathname;
4343
};
4444

45-
// http://stackoverflow.com/a/37192700/738052
46-
window.autoHeightTextarea = function(textarea) {
47-
$(textarea)
48-
.each(function () { adjustHeight(this); })
49-
.on('input', function () { adjustHeight(this); });
50-
51-
function adjustHeight(ctrl) {
52-
$(ctrl).css({'height':'auto','overflow-y':'hidden'}).height(ctrl.scrollHeight);
53-
}
54-
};
55-
5645
// https://www.lullabot.com/articles/importing-css-breakpoints-into-javascript
5746
window.screenSize = function () {
5847
return window
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// http://stackoverflow.com/a/37192700/738052
2+
window.autoHeightTextarea = function(textarea) {
3+
$(textarea)
4+
.each(function () { adjustHeight(this); })
5+
.on('input', function () { adjustHeight(this); });
6+
7+
function adjustHeight(ctrl) {
8+
$(ctrl).css({'height':'auto','overflow-y':'hidden'}).height(ctrl.scrollHeight);
9+
}
10+
};
11+
12+
window.affixMarkdownToolbar = function (textareaSelector) {
13+
var $toolbar = $(textareaSelector).siblings('.editor-toolbar');
14+
var $editArea = $(textareaSelector).siblings('.CodeMirror-wrap');
15+
16+
var bottomOfEditArea = $editArea.height() + $toolbar.offset().top;
17+
var offsetBottom = $(document).height() - bottomOfEditArea;
18+
19+
var affixOpts = {
20+
offset: {
21+
top: $toolbar.offset().top,
22+
bottom: offsetBottom
23+
}
24+
};
25+
26+
$toolbar.affix(affixOpts);
27+
};

resources/assets/sass/app.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@import "node_modules/font-awesome/scss/font-awesome";
88
@import "node_modules/select2/dist/css/select2";
99
@import "node_modules/select2-bootstrap-theme/src/select2-bootstrap";
10+
@import "node_modules/simplemde/dist/simplemde.min";
1011

1112
/**
1213
* Pages

resources/assets/sass/document-edit.scss

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,56 @@
2929
}
3030

3131
.settings-sidebar {
32-
&.affix {
32+
&.affix,
33+
&.affix-bottom {
3334
width: 360px;
34-
top: 5px;
35+
36+
@media only screen and (max-width: $screen-lg-min) {
37+
width: 293px;
38+
}
39+
}
40+
41+
&.affix { top: 10px; }
42+
&.affix-bottom { position: absolute; }
43+
}
44+
45+
.editor-toolbar {
46+
border-radius: 0;
47+
48+
&.affix,
49+
&.affix-bottom {
50+
position: fixed;
51+
z-index: 3;
52+
width: 750px;
53+
border-bottom: 1px solid #bbb;
54+
border-top: 0;
55+
background: white;
56+
opacity: 1;
57+
58+
@media only screen and (max-width: $screen-lg-min) {
59+
width: 616px;
60+
}
61+
}
62+
&.affix { top: 0; }
63+
&.affix-bottom { position: absolute; }
64+
65+
a.smde-preview {
66+
float: right;
67+
margin-top: 3px;
68+
width: 30%;
69+
border-radius: 0;
70+
71+
&:hover,
72+
&:focus,
73+
&:active {
74+
border: 0;
75+
outline: 0;
76+
}
77+
78+
&:after {
79+
content: attr(title);
80+
float: right;
81+
}
3582
}
3683
}
3784
}

resources/lang/en/messages.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
'relevance' => 'Relevance',
4747
'relevance_ordering_warning' => 'Ordering by relevance only works with a search query, default order has been used.',
4848
'manage' => 'Manage',
49+
'preview' => 'Preview',
4950

5051
'browser_support_banner' => 'Your browser does not support needed features for Madison to function properly. Please upgrade to a modern browser.',
5152

resources/views/documents/manage/settings.blade.php

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,19 +81,69 @@ class="btn btn-default pull-right add-page"
8181
@endsection
8282

8383
@push('scripts')
84+
<script src="{{ elixir('js/document-edit.js') }}"></script>
8485
<script>
85-
autoHeightTextarea($('textarea[name="title"]')[0]);
86-
autoHeightTextarea($('textarea[name="introtext"]')[0]);
87-
88-
// Only set side affix and auto content height on md+ screens
89-
if (['xs', 'sm'].indexOf(window.screenSize()) === -1) {
90-
let $settingsSidebar = $('.settings-sidebar');
91-
92-
$settingsSidebar.affix({
93-
offset: { top: $settingsSidebar.parent().position().top - 10 }
94-
});
95-
96-
autoHeightTextarea($('textarea[name="page_content"]')[0]);
97-
}
86+
window.loadTranslations([
87+
'messages.preview'
88+
]).then(function () {
89+
var introtextSelector = 'textarea[name=introtext]';
90+
var contentSelector = 'textarea[name=page_content]';
91+
92+
var mdeDefaults = {
93+
hideIcons: ['fullscreen', 'side-by-side', 'image'],
94+
spellChecker: false,
95+
status: false,
96+
toolbar: [
97+
'heading-1',
98+
'heading-2',
99+
'heading-3',
100+
'|',
101+
'bold',
102+
'italic',
103+
'quote',
104+
'link',
105+
'|',
106+
'unordered-list',
107+
'ordered-list',
108+
'|',
109+
'guide',
110+
{
111+
name: 'preview',
112+
action: SimpleMDE.togglePreview,
113+
className: 'smde-preview no-disable',
114+
title: window.trans['messages.preview']
115+
}
116+
],
117+
shortcuts: { 'togglePreview': null } // So the "Ctrl-P" doesn't show up in the label
118+
};
119+
var introMde = new SimpleMDE(Object.assign(mdeDefaults, {
120+
element: document.querySelector(introtextSelector)
121+
}));
122+
var contentMde = new SimpleMDE(Object.assign(mdeDefaults, {
123+
element: document.querySelector(contentSelector)
124+
}));
125+
126+
autoHeightTextarea($('textarea[name="title"]')[0]);
127+
autoHeightTextarea($('textarea[name="introtext"]')[0]);
128+
129+
// Only perform these things on md+ screens
130+
if (['xs', 'sm'].indexOf(window.screenSize()) === -1) {
131+
// Affix the settings sidebar
132+
let $settingsSidebar = $('.settings-sidebar');
133+
$settingsSidebar.affix({
134+
offset: {
135+
top: $settingsSidebar.parent().offset().top - 10,
136+
bottom: $('#main-footer').outerHeight() + 10
137+
}
138+
});
139+
140+
// Match content editor height to content length
141+
autoHeightTextarea($('textarea[name="page_content"]')[0]);
142+
143+
// Affix the markdown editor toolbars
144+
affixMarkdownToolbar(introtextSelector);
145+
affixMarkdownToolbar(contentSelector);
146+
}
147+
});
98148
</script>
99149
@endpush

tests/Browser/Document/Manage/SettingsTest.php

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,16 @@ protected function userCanEditDocTest($user)
9696
->loginAs($user)
9797
->visit($this->page)
9898
->type('title', $newData['title'])
99-
->type('introtext', $newData['introtext'])
99+
->waitForCodeMirror()
100+
->setCodeMirrorTextForField('introtext', $newData['introtext'])
100101
->select('publish_state', $newData['publish_state'])
101102
->select('discussion_state', $newData['discussion_state'])
102103
->click('@submitBtn')
103104
->assertPathIs($this->page->url())
104105
->assertVisible('.alert.alert-info')
106+
->waitForCodeMirror()
105107
->assertInputValue('title', $newData['title'])
106-
->assertInputValue('introtext', $newData['introtext'])
108+
->assertSeeIn(SettingsPage::codeMirrorSelectorForField('introtext'), $newData['introtext'])
107109
->assertSelected('publish_state', $newData['publish_state'])
108110
->assertSelected('discussion_state', $newData['discussion_state'])
109111
;
@@ -114,18 +116,21 @@ protected function userCanEditDocTest($user)
114116
$this->assertEquals($newData['publish_state'], $this->document->publish_state);
115117
$this->assertEquals($newData['discussion_state'], $this->document->discussion_state);
116118

119+
$browser->driver->executeScript('document.getElementById("add-page-form").submit();');
117120
$browser
118-
->click('@addPageBtn')
121+
//->click('@addPageBtn')
119122
->assertPathIs($this->page->url())
120123
->assertQueryStringHas('page', '2')
121124
->assertVisible('.document-pages-toolbar .pagination')
122125
->assertInputValue('page_content', '')
123-
->type('page_content', $newData['new_page_content'])
126+
->waitForCodeMirror()
127+
->setCodeMirrorTextForField('page_content', $newData['new_page_content'])
124128
->click('@submitBtn')
125129
->assertPathIs($this->page->url())
126130
->assertQueryStringHas('page', '2')
127131
->assertVisible('.alert.alert-info')
128-
->assertInputValue('page_content', $newData['new_page_content'])
132+
->waitForCodeMirror()
133+
->assertSeeIn(SettingsPage::codeMirrorSelectorForField('page_content'), $newData['new_page_content'])
129134
;
130135

131136
$this->assertEquals($newData['new_page_content'], $this->document->content()->where('page', 2)->first()->content);

tests/Browser/Pages/Document/Manage/SettingsPage.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,25 @@ public function elements()
3434
{
3535
return [
3636
'@submitBtn' => '#content form button[type=submit]',
37-
'@addPageBtn' => '.document-pages-toolbar .add-page',
3837
];
3938
}
39+
40+
public function waitForCodeMirror(Browser $browser)
41+
{
42+
$selector = static::codeMirrorSelectorForField('introtext');
43+
return $browser->waitUntil('!!document.querySelector("' . $selector . '").CodeMirror;');
44+
}
45+
46+
public function setCodeMirrorTextForField(Browser $browser, $field, $text)
47+
{
48+
$selector = static::codeMirrorSelectorForField($field);
49+
$script = 'document.querySelector("' . $selector . '").CodeMirror.setValue("' . $text . '");';
50+
51+
return $browser->driver->executeScript($script);
52+
}
53+
54+
public static function codeMirrorSelectorForField($field)
55+
{
56+
return '[name=' . $field . '] + .editor-toolbar + .CodeMirror';
57+
}
4058
}

0 commit comments

Comments
 (0)