From 41b3521cf14904f880815fccee82d76fa18cd537 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Thu, 18 Jan 2018 15:36:01 +0000 Subject: [PATCH] Resurrect the old alpha Koenig editor (#916) requires https://github.com/TryGhost/Ghost/pull/9277 - added a `koenigEditor` feature flag - modified the feature service to accept a `developer` boolean on the options object passed into the internal `feature` method, if `true` the feature flag won't be enabled unless the `enableDeveloperExperiments` config option is also enabled - added "developer feature testing" section in labs that's only visible if `enableDeveloperExperiments` config flag is enabled - added koenig editor toggle to the developer section in labs - enabled a switch between the markdown and koenig editors - modified the default value of the `mobiledoc` attr in the Post model to be a blank mobiledoc or blank markdown mobiledoc depending on the feature flag - modified the `autofocus` switch in editor controller's `setPost` method so that it is always switched, even for new->edit where the post model isn't swapped - added a compatibility check to the editor controller's `setPost` method that shows an alert and force enables the koenig editor if the koenig flag is not enabled and the opened post is not compatible with the markdown editor - fixed various issues that have appeared due to the old koenig alpha becoming out of sync with master --- app/components/gh-editor.js | 2 +- app/controllers/editor.js | 49 +++- app/models/post.js | 48 +++- app/services/feature.js | 25 +- app/styles/addons/gh-koenig/gh-koenig.css | 2 +- .../koenig-cardmenu.css} | 0 app/styles/app-dark.css | 2 +- app/styles/app.css | 2 +- app/templates/editor.hbs | 216 +++++++++++------- app/templates/settings/labs.hbs | 15 ++ ember-cli-build.js | 3 + .../addon/components/cards/card-markdown.js | 6 +- lib/gh-koenig/addon/components/gh-koenig.js | 17 +- .../addon/components/koenig-title-input.js | 2 +- lib/gh-koenig/addon/lib/format-markdown.js | 34 --- .../templates/components/card-markdown.hbs | 4 +- lib/gh-koenig/package.json | 4 +- package.json | 4 +- tests/acceptance/editor-test.js | 2 +- tests/unit/controllers/editor-test.js | 1 + tests/unit/models/post-test.js | 3 + tests/unit/serializers/post-test.js | 5 + yarn.lock | 15 ++ 23 files changed, 305 insertions(+), 156 deletions(-) rename app/styles/addons/{ghost-editor/cardmenu.css => gh-koenig/koenig-cardmenu.css} (100%) delete mode 100644 lib/gh-koenig/addon/lib/format-markdown.js diff --git a/app/components/gh-editor.js b/app/components/gh-editor.js index 37a9ee8b6..2cb795d0b 100644 --- a/app/components/gh-editor.js +++ b/app/components/gh-editor.js @@ -99,7 +99,7 @@ export default Component.extend({ }, _setHeaderClass() { - let $editorTitle = this.$('.gh-editor-title'); + let $editorTitle = this.$('.gh-editor-title, .kg-title-input'); let smallHeaderClass = 'gh-editor-header-small'; if (this.get('isSplitScreen')) { diff --git a/app/controllers/editor.js b/app/controllers/editor.js index d6fbac1e7..6cc474499 100644 --- a/app/controllers/editor.js +++ b/app/controllers/editor.js @@ -77,6 +77,7 @@ const messageMap = { export default Controller.extend({ application: controller(), + feature: service(), notifications: service(), router: service(), slugGenerator: service(), @@ -89,6 +90,10 @@ export default Controller.extend({ showDeletePostModal: false, showLeaveEditorModal: false, showReAuthenticateModal: false, + useKoenig: false, + + // koenig related properties + wordcount: 0, /* private properties ----------------------------------------------------*/ @@ -240,6 +245,11 @@ export default Controller.extend({ toggleReAuthenticateModal() { this.toggleProperty('showReAuthenticateModal'); + }, + + // TODO: this should be part of the koenig component + setWordcount(count) { + this.set('wordcount', count); } }, @@ -472,7 +482,27 @@ export default Controller.extend({ // called by the new/edit routes to change the post model setPost(post) { - // don't do anything if the post is the same + // switch between markdown/koenig depending on feature flag and post + // compatibility + let koenigEnabled = this.get('feature.koenigEditor'); + let postIsMarkdownCompatible = post.isCompatibleWithMarkdownEditor(); + if (koenigEnabled || !postIsMarkdownCompatible) { + this.set('useKoenig', true); + + // display an alert if koenig is disabled but we use it anyway + // because the post is incompatible with the markdown editor + if (!koenigEnabled) { + alert('This post will be opened with the Koenig editor because it\'s not compatible with the markdown editor'); + } + } else { + this.set('useKoenig', false); + } + + // autofocus the editor if we have a new post, this also acts as a + // change signal to the persistent editor on new->edit + this.set('shouldFocusEditor', post.get('isNew')); + + // don't do anything else if we're setting the same post if (post === this.get('post')) { return; } @@ -482,9 +512,6 @@ export default Controller.extend({ this.set('post', post); - // only autofocus the editor if we have a new post - this.set('shouldFocusEditor', post.get('isNew')); - // need to set scratch values because they won't be present on first // edit of the post // TODO: can these be `boundOneWay` on the model as per the other attrs? @@ -641,10 +668,16 @@ export default Controller.extend({ } // scratch isn't an attr so needs a manual dirty check - let mobiledoc = JSON.stringify(post.get('mobiledoc')); - let scratch = JSON.stringify(post.get('scratch')); - if (scratch !== mobiledoc) { - return true; + let mobiledoc = post.get('mobiledoc'); + let scratch = post.get('scratch'); + // additional guard in case we are trying to compare null with undefined + if (scratch || mobiledoc) { + let mobiledocJSON = JSON.stringify(mobiledoc); + let scratchJSON = JSON.stringify(scratch); + + if (scratchJSON !== mobiledocJSON) { + return true; + } } // new+unsaved posts always return `hasDirtyAttributes: true` diff --git a/app/models/post.js b/app/models/post.js index 9d3242352..6b4aacf53 100644 --- a/app/models/post.js +++ b/app/models/post.js @@ -4,7 +4,8 @@ import ValidationEngine from 'ghost-admin/mixins/validation-engine'; import attr from 'ember-data/attr'; import boundOneWay from 'ghost-admin/utils/bound-one-way'; import moment from 'moment'; -import {BLANK_DOC} from 'ghost-admin/components/gh-markdown-editor'; +import {BLANK_DOC as BLANK_MARKDOWN} from 'ghost-admin/components/gh-markdown-editor'; +import {BLANK_DOC as BLANK_MOBILEDOC} from 'gh-koenig/components/gh-koenig'; import {belongsTo, hasMany} from 'ember-data/relationships'; import {compare} from '@ember/utils'; import {computed} from '@ember/object'; @@ -69,6 +70,7 @@ function publishedAtCompare(postA, postB) { export default Model.extend(Comparable, ValidationEngine, { config: service(), + feature: service(), ghostPaths: service(), clock: service(), settings: service(), @@ -93,7 +95,7 @@ export default Model.extend(Comparable, ValidationEngine, { locale: attr('string'), metaDescription: attr('string'), metaTitle: attr('string'), - mobiledoc: attr('json-string', {defaultValue: () => BLANK_DOC}), + mobiledoc: attr('json-string'), page: attr('boolean', {defaultValue: false}), plaintext: attr('string'), publishedAtUTC: attr('moment-utc'), @@ -113,6 +115,28 @@ export default Model.extend(Comparable, ValidationEngine, { async: false }), + init() { + // we can't use the defaultValue property on the attr because it won't + // have access to `this` for the feature check so we do it manually here. + if (!this.get('mobiledoc')) { + let defaultValue; + + if (this.get('feature.koenigEditor')) { + defaultValue = BLANK_MOBILEDOC; + } else { + defaultValue = BLANK_MARKDOWN; + } + + // using this.set() adds the property to the changedAttributes list + // which means the editor always sees new posts as dirty. Assigning + // the value directly works around that so you can exit the editor + // without a warning + this.mobiledoc = defaultValue; + } + + this._super(...arguments); + }, + scratch: null, titleScratch: null, @@ -313,5 +337,25 @@ export default Model.extend(Comparable, ValidationEngine, { let publishedAtBlogTZ = this.get('publishedAtBlogTZ'); let publishedAtUTC = publishedAtBlogTZ ? publishedAtBlogTZ.utc() : null; this.set('publishedAtUTC', publishedAtUTC); + }, + + // the markdown editor expects a very specific mobiledoc format, if it + // doesn't match then we'll need to handle it by forcing Koenig + isCompatibleWithMarkdownEditor() { + let mobiledoc = this.get('mobiledoc'); + + if ( + mobiledoc.markups.length === 0 + && mobiledoc.cards.length === 1 + && mobiledoc.cards[0][0] === 'card-markdown' + && mobiledoc.sections.length === 1 + && mobiledoc.sections[0].length === 2 + && mobiledoc.sections[0][0] === 10 + && mobiledoc.sections[0][1] === 0 + ) { + return true; + } + + return false; } }); diff --git a/app/services/feature.js b/app/services/feature.js index 74de23ef3..ae133045d 100644 --- a/app/services/feature.js +++ b/app/services/feature.js @@ -12,18 +12,24 @@ export function feature(name, options = {}) { return computed.apply(Ember, watchedProps.concat({ get() { + let enabled = false; + if (user) { - return this.get(`accessibility.${name}`); + enabled = this.get(`accessibility.${name}`); + } else if (this.get(`config.${name}`)) { + enabled = this.get(`config.${name}`); + } else { + enabled = this.get(`labs.${name}`) || false; } - if (this.get(`config.${name}`)) { - return this.get(`config.${name}`); + if (options.developer) { + enabled = enabled && this.get('config.enableDeveloperExperiments'); } - return this.get(`labs.${name}`) || false; + return enabled; }, set(key, value) { - this.update(key, value, user); + this.update(key, value, options); if (onChange) { // value must be passed here because the value isn't set until @@ -44,6 +50,7 @@ export default Service.extend({ notifications: service(), lazyLoader: service(), + koenigEditor: feature('koenigEditor', {developer: true}), publicAPI: feature('publicAPI'), subscribers: feature('subscribers'), nightShift: feature('nightShift', {user: true, onChange: '_setAdminTheme'}), @@ -80,9 +87,9 @@ export default Service.extend({ }); }, - update(key, value, user = false) { - let serviceProperty = user ? 'accessibility' : 'labs'; - let model = this.get(user ? '_user' : 'settings'); + update(key, value, options = {}) { + let serviceProperty = options.user ? 'accessibility' : 'labs'; + let model = this.get(options.user ? '_user' : 'settings'); let featureObject = this.get(serviceProperty); // set the new key value for either the labs property or the accessibility property @@ -102,7 +109,7 @@ export default Service.extend({ // we'll always have an errors object unless we hit a // validation error if (!error) { - throw new EmberError(`Validation of the feature service ${user ? 'user' : 'settings'} model failed when updating ${serviceProperty}.`); + throw new EmberError(`Validation of the feature service ${options.user ? 'user' : 'settings'} model failed when updating ${serviceProperty}.`); } this.get('notifications').showAPIError(error); diff --git a/app/styles/addons/gh-koenig/gh-koenig.css b/app/styles/addons/gh-koenig/gh-koenig.css index 625edca54..23f822326 100644 --- a/app/styles/addons/gh-koenig/gh-koenig.css +++ b/app/styles/addons/gh-koenig/gh-koenig.css @@ -1,6 +1,6 @@ @import "koenig-toolbar.css"; @import "koenig-menu.css"; -@import "../ghost-editor/cardmenu.css"; +@import "koenig-cardmenu.css"; .gh-koenig-container { height: 100%; diff --git a/app/styles/addons/ghost-editor/cardmenu.css b/app/styles/addons/gh-koenig/koenig-cardmenu.css similarity index 100% rename from app/styles/addons/ghost-editor/cardmenu.css rename to app/styles/addons/gh-koenig/koenig-cardmenu.css diff --git a/app/styles/app-dark.css b/app/styles/app-dark.css index 5ac82f883..689598732 100644 --- a/app/styles/app-dark.css +++ b/app/styles/app-dark.css @@ -56,7 +56,7 @@ /* Addons: gh-koenig /* ---------------------------------------------------------- */ -/*@import "addons/gh-koenig/gh-koenig.css";*/ +@import "addons/gh-koenig/gh-koenig.css"; :root { diff --git a/app/styles/app.css b/app/styles/app.css index 8c3daf823..023435a8f 100644 --- a/app/styles/app.css +++ b/app/styles/app.css @@ -56,7 +56,7 @@ /* Addons: gh-koenig /* ---------------------------------------------------------- */ -/*@import "addons/gh-koenig/gh-koenig.css";*/ +@import "addons/gh-koenig/gh-koenig.css"; /* ---------------------------✈️----------------------------- */ diff --git a/app/templates/editor.hbs b/app/templates/editor.hbs index 3e8bcd7a4..40a466b88 100644 --- a/app/templates/editor.hbs +++ b/app/templates/editor.hbs @@ -32,100 +32,144 @@ - {{!-- - NOTE: title is part of the markdown editor container so that it has - access to the markdown editor's "focus" action - --}} - {{#gh-markdown-editor - tabindex="2" - placeholder="Begin writing your story..." - autofocus=shouldFocusEditor - uploadedImageUrls=editor.uploadedImageUrls - mobiledoc=(readonly post.scratch) - isFullScreen=editor.isFullScreen - onChange=(action "updateScratch") - onFullScreenToggle=(action editor.toggleFullScreen) - onPreviewToggle=(action editor.togglePreview) - onSplitScreenToggle=(action editor.toggleSplitScreen) - onImageFilesSelected=(action editor.uploadImages) - imageMimeTypes=editor.imageMimeTypes - as |markdown| - }} -
- {{gh-textarea post.titleScratch - class="gh-editor-title" - placeholder="Post Title" - tabindex="1" - autoExpand=".gh-markdown-editor-pane" - update=(action "updateTitleScratch") - focusOut=(action (perform saveTitle)) - keyEvents=(hash - 9=(action markdown.focus 'bottom') - 13=(action markdown.focus 'top') - ) - data-test-editor-title-input=true - }} - {{markdown.editor}} -
+ {{#if useKoenig}} +
+
+ {{!-- + NOTE: the mobiledoc property is unbound so that the setting the + serialized version onChange doesn't cause a deserialization and + re-render of the editor on every key press / editor change - {{#if markdown.isSplitScreen}} -
-

{{post.titleScratch}}

-
+ TODO: note above is no longer correct, changed to readonly to + fix a persistent editor content bug that occurred due to the + editor not being re-rendered on edit->new transition. + + Needs perf investigation! + --}} + {{#gh-koenig + mobiledoc=(readonly model.scratch) + onChange=(action "updateScratch") + autofocus=shouldFocusEditor + tabindex="2" + titleSelector="#kg-title-input" + containerSelector=".gh-editor-container" + wordcountDidChange=(action "setWordcount") + as |koenig| + }} + {{koenig-title-input + id="koenig-title-input" + val=(readonly model.titleScratch) + onChange=(action "updateTitleScratch") + tabindex="1" + autofocus=shouldFocusTitle + focusOut=(action (perform saveTitle)) + editor=(readonly koenig.editor) + editorHasRendered=koenig.hasRendered + editorMenuIsOpen=koenig.isMenuOpen + }} + {{/gh-koenig}} +
+
+
{{pluralize wordcount 'word'}}.
+ + {{else}} + + {{!-- + NOTE: title is part of the markdown editor container so that it has + access to the markdown editor's "focus" action + --}} + {{#gh-markdown-editor + tabindex="2" + placeholder="Begin writing your story..." + autofocus=shouldFocusEditor + uploadedImageUrls=editor.uploadedImageUrls + mobiledoc=(readonly post.scratch) + isFullScreen=editor.isFullScreen + onChange=(action "updateScratch") + onFullScreenToggle=(action editor.toggleFullScreen) + onPreviewToggle=(action editor.togglePreview) + onSplitScreenToggle=(action editor.toggleSplitScreen) + onImageFilesSelected=(action editor.uploadImages) + imageMimeTypes=editor.imageMimeTypes + as |markdown| + }} +
+ {{gh-textarea post.titleScratch + class="gh-editor-title" + placeholder="Post Title" + tabindex="1" + autoExpand=".gh-markdown-editor-pane" + update=(action "updateTitleScratch") + focusOut=(action (perform saveTitle)) + keyEvents=(hash + 9=(action markdown.focus 'bottom') + 13=(action markdown.focus 'top') + ) + data-test-editor-title-input=true + }} + {{markdown.editor}} +
+ + {{#if markdown.isSplitScreen}} +
+

{{post.titleScratch}}

+
+
+ {{/if}} + + {{gh-tour-item "using-the-editor" + target=".gh-editor-footer" + throbberAttachment="top left" + throbberOffset="0 20%" + popoverTriangleClass="bottom" + }} + {{/gh-markdown-editor}} + + {{!-- TODO: put tool/status bar in here so that scroll area can be fixed --}} +
+ + {{!-- files are dragged over editor pane --}} + {{#if editor.isDraggedOver}} +
+
+

Drop image(s) here to upload

+
{{/if}} - {{gh-tour-item "using-the-editor" - target=".gh-editor-footer" - throbberAttachment="top left" - throbberOffset="0 20%" - popoverTriangleClass="bottom" - }} - {{/gh-markdown-editor}} + {{!-- files have been dropped ready to be uploaded --}} + {{#if editor.droppedFiles}} + {{#gh-uploader + files=editor.droppedFiles + accept=editor.imageMimeTypes + extensions=editor.imageExtensions + onComplete=(action editor.uploadComplete) + onCancel=(action editor.uploadCancelled) + as |upload| + }} +
+
+ {{#if upload.errors}} +

Upload failed

- {{!-- TODO: put tool/status bar in here so that scroll area can be fixed --}} -
+ {{#each upload.errors as |error|}} +
{{error.fileName}} - {{error.message}}
+ {{/each}} - {{!-- files are dragged over editor pane --}} - {{#if editor.isDraggedOver}} -
-
-

Drop image(s) here to upload

-
-
- {{/if}} + + {{else}} - {{!-- files have been dropped ready to be uploaded --}} - {{#if editor.droppedFiles}} - {{#gh-uploader - files=editor.droppedFiles - accept=editor.imageMimeTypes - extensions=editor.imageExtensions - onComplete=(action editor.uploadComplete) - onCancel=(action editor.uploadCancelled) - as |upload| - }} -
-
- {{#if upload.errors}} -

Upload failed

- - {{#each upload.errors as |error|}} -
{{error.fileName}} - {{error.message}}
- {{/each}} - - - {{else}} - -

Uploading {{pluralize upload.files.length "image"}}...

- {{upload.progressBar}} - {{/if}} +

Uploading {{pluralize upload.files.length "image"}}...

+ {{upload.progressBar}} + {{/if}} +
-
- {{/gh-uploader}} - {{/if}} + {{/gh-uploader}} + {{/if}} + + {{/if}} {{!-- end Koenig conditional --}} {{/gh-editor}} {{#if showDeletePostModal}} diff --git a/app/templates/settings/labs.hbs b/app/templates/settings/labs.hbs index 7f18426fd..627f6aade 100644 --- a/app/templates/settings/labs.hbs +++ b/app/templates/settings/labs.hbs @@ -155,6 +155,21 @@ {{/gh-uploader}}
+ {{#if config.enableDeveloperExperiments}} +
⚠️ Developer-only Feature Testing ⚠️
+
+
+
Koenig Editor
+
+ Highly experimental (i.e. broken) editor. For developer use only.
+ Warning: Stories created or edited with Koenig will no longer be compatible with the old markdown editor.
+
+
+
{{gh-feature-flag "koenigEditor"}}
+
+
+ {{/if}} + diff --git a/ember-cli-build.js b/ember-cli-build.js index 9ac8e1d7e..636e7255e 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -149,6 +149,9 @@ module.exports = function (defaults) { 'jquery-deparam': { import: ['jquery-deparam.js'] }, + 'mobiledoc-kit': { + import: ['dist/amd/mobiledoc-kit.js', 'dist/amd/mobiledoc-kit.map'] + }, 'password-generator': { import: ['lib/password-generator.js'] }, diff --git a/lib/gh-koenig/addon/components/cards/card-markdown.js b/lib/gh-koenig/addon/components/cards/card-markdown.js index d15e05566..e709e64ca 100644 --- a/lib/gh-koenig/addon/components/cards/card-markdown.js +++ b/lib/gh-koenig/addon/components/cards/card-markdown.js @@ -1,5 +1,6 @@ import Component from '@ember/component'; import counter from 'ghost-admin/utils/word-count'; +import formatMarkdown from 'ghost-admin/utils/format-markdown'; import layout from '../../templates/components/card-markdown'; import { UnsupportedMediaTypeError, @@ -8,7 +9,6 @@ import { isVersionMismatchError } from 'ghost-admin/services/ajax'; import {computed} from '@ember/object'; -import {formatMarkdown} from '../../lib/format-markdown'; import {invokeAction} from 'ember-invoke-action'; import {isBlank} from '@ember/utils'; import {isArray as isEmberArray} from '@ember/array'; @@ -25,7 +25,7 @@ export default Component.extend({ extensions: null, preview: computed('value', function () { - return formatMarkdown([this.get('payload').markdown]); + return formatMarkdown(this.get('payload').markdown); }), // TODO: remove observer @@ -47,7 +47,7 @@ export default Component.extend({ didReceiveAttrs() { if (!this.get('isEditing')) { - this.set('preview', formatMarkdown([this.get('payload').markdown])); + this.set('preview', formatMarkdown(this.get('payload').markdown)); } else { run.next(() => { this.$('textarea').focus(); diff --git a/lib/gh-koenig/addon/components/gh-koenig.js b/lib/gh-koenig/addon/components/gh-koenig.js index c0358ff2a..ae04ca954 100644 --- a/lib/gh-koenig/addon/components/gh-koenig.js +++ b/lib/gh-koenig/addon/components/gh-koenig.js @@ -72,7 +72,7 @@ export default Component.extend({ cards = defaultCards.concat(cards).map(card => createCard(card)); // add our default atoms - atoms.concat([{ + atoms = atoms.concat([{ name: 'soft-return', type: 'dom', render() { @@ -117,6 +117,15 @@ export default Component.extend({ this._startedRunLoop = false; }, + didReceiveAttrs() { + this._super(...arguments); + + if (this.get('autofocus') !== this._autofocus) { + this._autofocus = this.get('autofocus'); + this._hasAutofocused = false; + } + }, + willRender() { // Use a default mobiledoc. If there are no changes, then return early. let mobiledoc = this.get('mobiledoc') || BLANK_DOC; @@ -235,7 +244,7 @@ export default Component.extend({ // the first lot of content is entered and we expect the caret to be at // the end of the document. // TODO: can this be removed if we refactor the new/edit screens to not re-render? - if (this.get('autofocus')) { + if (this._autofocus && !this._hasAutofocused) { let range = document.createRange(); range.selectNodeContents(this.editor.element); range.collapse(false); @@ -243,6 +252,10 @@ export default Component.extend({ sel.removeAllRanges(); sel.addRange(range); editor._ensureFocus(); // PRIVATE API + + // ensure we don't run the autofocus more than once between + // `autofocus` attr changes + this._hasAutofocused = true; } this.processWordcount(); diff --git a/lib/gh-koenig/addon/components/koenig-title-input.js b/lib/gh-koenig/addon/components/koenig-title-input.js index 448c64d6f..ecdd85f84 100644 --- a/lib/gh-koenig/addon/components/koenig-title-input.js +++ b/lib/gh-koenig/addon/components/koenig-title-input.js @@ -185,7 +185,7 @@ export default Component.extend({ // if the current paragraph is empty then the position is 0 if (!cursorPositionOnScreen || cursorPositionOnScreen.top === 0) { - if (editor.activeSection.renderNode) { + if (editor.activeSection && editor.activeSection.renderNode) { cursorPositionOnScreen = editor.activeSection.renderNode.element.getBoundingClientRect(); } else { this.setCursorAtOffset(0); diff --git a/lib/gh-koenig/addon/lib/format-markdown.js b/lib/gh-koenig/addon/lib/format-markdown.js deleted file mode 100644 index 33e0d722a..000000000 --- a/lib/gh-koenig/addon/lib/format-markdown.js +++ /dev/null @@ -1,34 +0,0 @@ -/* global Showdown, html_sanitize*/ -import cajaSanitizers from './caja-sanitizers'; -import {helper} from '@ember/component/helper'; -import {htmlSafe} from '@ember/string'; - -// eslint-disable-next-line new-cap -let showdown = new Showdown.converter({extensions: ['ghostgfm', 'footnotes', 'highlight']}); - -export function formatMarkdown(params) { - if (!params || !params.length) { - return; - } - - let markdown = params[0] || ''; - let escapedhtml = ''; - - // convert markdown to HTML - escapedhtml = showdown.makeHtml(markdown); - - // replace script and iFrame - escapedhtml = escapedhtml.replace(/)<[^<]*)*<\/script>/gi, - '
Embedded JavaScript
'); - escapedhtml = escapedhtml.replace(/)<[^<]*)*<\/iframe>/gi, - '
Embedded iFrame
'); - - // sanitize html - // jscs:disable requireCamelCaseOrUpperCaseIdentifiers - escapedhtml = html_sanitize(escapedhtml, cajaSanitizers.url, cajaSanitizers.id); - // jscs:enable requireCamelCaseOrUpperCaseIdentifiers - - return htmlSafe(escapedhtml); -} - -export default helper(formatMarkdown); diff --git a/lib/gh-koenig/addon/templates/components/card-markdown.hbs b/lib/gh-koenig/addon/templates/components/card-markdown.hbs index e04044047..5099320b7 100644 --- a/lib/gh-koenig/addon/templates/components/card-markdown.hbs +++ b/lib/gh-koenig/addon/templates/components/card-markdown.hbs @@ -4,9 +4,7 @@ ondrop={{action "didDrop"}} ondragover={{action "didDragOver"}} ondragleave={{action "didDragLeave"}} - > - {{value}} - + >{{value}} {{else}} {{{preview}}} {{/if}} diff --git a/lib/gh-koenig/package.json b/lib/gh-koenig/package.json index 087b78a58..0fcb41e2d 100644 --- a/lib/gh-koenig/package.json +++ b/lib/gh-koenig/package.json @@ -6,7 +6,7 @@ ], "readmeFilename": "README.md", "dependencies": { - "ember-cli-babel": "5.2.4", - "ember-cli-htmlbars": "1.1.1" + "ember-cli-babel": "^6.11.0", + "ember-cli-htmlbars": "^2.0.3" } } diff --git a/package.json b/package.json index f002089d1..63087ecd4 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "markdown-it-lazy-headers": "0.1.3", "markdown-it-mark": "2.0.0", "matchdep": "2.0.0", + "mobiledoc-kit": "0.10.20", "password-generator": "2.2.0", "postcss-color-function": "4.0.1", "postcss-custom-properties": "6.2.0", @@ -121,7 +122,8 @@ }, "ember-addon": { "paths": [ - "lib/asset-delivery" + "lib/asset-delivery", + "lib/gh-koenig" ] } } diff --git a/tests/acceptance/editor-test.js b/tests/acceptance/editor-test.js index d030d46a7..ec543dd30 100644 --- a/tests/acceptance/editor-test.js +++ b/tests/acceptance/editor-test.js @@ -126,7 +126,7 @@ describe('Acceptance: Editor', function () { await datepickerSelect('[data-test-date-time-picker-datepicker]', validTime); // hide psm - await click('[data-test-psm-trigger]'); + await click('[data-test-close-settings-menu]'); // checking the flow of the saving button for a draft expect( diff --git a/tests/unit/controllers/editor-test.js b/tests/unit/controllers/editor-test.js index 7abeb2985..4e73c9018 100644 --- a/tests/unit/controllers/editor-test.js +++ b/tests/unit/controllers/editor-test.js @@ -11,6 +11,7 @@ describe('Unit: Controller: editor', function () { setupTest('controller:editor', { needs: [ 'controller:application', + 'service:feature', 'service:notifications', // 'service:router', 'service:slugGenerator', diff --git a/tests/unit/models/post-test.js b/tests/unit/models/post-test.js index 8ed917fd9..522a20244 100644 --- a/tests/unit/models/post-test.js +++ b/tests/unit/models/post-test.js @@ -9,10 +9,13 @@ describe('Unit: Model: post', function () { 'model:user', 'model:tag', 'model:role', + 'service:ajax', 'service:clock', 'service:config', 'service:feature', 'service:ghostPaths', + 'service:lazyLoader', + 'service:notifications', 'service:session', 'service:settings' ] diff --git a/tests/unit/serializers/post-test.js b/tests/unit/serializers/post-test.js index 59a68e4a7..140dc21a2 100644 --- a/tests/unit/serializers/post-test.js +++ b/tests/unit/serializers/post-test.js @@ -10,9 +10,14 @@ describe('Unit: Serializer: post', function () { 'transform:json-string', 'model:user', 'model:tag', + 'service:ajax', 'service:clock', 'service:config', + 'service:feature', 'service:ghostPaths', + 'service:lazyLoader', + 'service:notifications', + 'service:session', 'service:settings' ] }); diff --git a/yarn.lock b/yarn.lock index 3f3102a21..dbbea86d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6835,6 +6835,21 @@ mktemp@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/mktemp/-/mktemp-0.4.0.tgz#6d0515611c8a8c84e484aa2000129b98e981ff0b" +mobiledoc-dom-renderer@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/mobiledoc-dom-renderer/-/mobiledoc-dom-renderer-0.6.5.tgz#56c0302c4f9c30840ab5b9b20dfe905aed1e437b" + +mobiledoc-kit@0.10.20: + version "0.10.20" + resolved "https://registry.yarnpkg.com/mobiledoc-kit/-/mobiledoc-kit-0.10.20.tgz#2925a6223bac2a1eeb3468a4a94d992618089312" + dependencies: + mobiledoc-dom-renderer "0.6.5" + mobiledoc-text-renderer "0.3.2" + +mobiledoc-text-renderer@0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/mobiledoc-text-renderer/-/mobiledoc-text-renderer-0.3.2.tgz#126a167a6cf8b6cd7e58c85feb18043603834580" + mocha@^2.5.3: version "2.5.3" resolved "https://registry.yarnpkg.com/mocha/-/mocha-2.5.3.tgz#161be5bdeb496771eb9b35745050b622b5aefc58"