From f63c7f92894aec3df5db61d370bb888fb6e775b6 Mon Sep 17 00:00:00 2001 From: Dmitry Maksyoma Date: Sat, 16 May 2020 00:27:46 +1200 Subject: [PATCH] Fix Runkit tags not being activated when comment is added (#6767) * Fix Runkit tags not being activated when comment is added * Runkit tag activation was ran once, on page load. I've changed it to run on on comment preview and submit. * It was necessary to add a check to skip already activated Runkit tags. The code didn't take into account that a tag could be already processed, and would just crash. * Fix Runkit tags and add tests * Add test for previewing article with Runkit tag * "Fix CodeClimate not finding waitForRunkitAndActivateTags() * Refactor a test for readability * Make Rubocop happy * Improve test for previewing article with Runkit tag * Use one method for determining active Runkit tags * Defer loading of JS from embed.runkit.com * Add utility function dynamicallyLoadScript(url) * Switch to dynamic loading of Runkit * parsed content code block is also hidden now to avoid displaying block before runkit iframe loads. * Fix Runkit test * Remove Runkit script caching in v2 form * Use <%== instead of .html_safe in v2 form * Update app/assets/javascripts/utilities/dynamicallyLoadScript.js Co-authored-by: Nick Taylor Co-authored-by: Nick Taylor --- .../initializers/initializeCommentPreview.js | 7 +- .../initializeCommentsPage.js.erb | 1 + .../utilities/dynamicallyLoadScript.js | 10 +++ app/javascript/article-form/articleForm.jsx | 46 +++++------ app/liquid_tags/runkit_tag.rb | 80 ++++++++++++++----- app/views/articles/_v2_form.html.erb | 6 +- app/views/articles/show.html.erb | 4 - app/views/liquids/_runkit.html.erb | 2 +- .../files/article_with_runkit_tag.txt | 12 +++ .../runkit_liquid_tag_spec.approved.html | 2 +- spec/support/runkit_tag_context.rb | 18 +++++ .../articles/user_creates_an_article_spec.rb | 31 +++++++ .../comments/user_fills_out_comment_spec.rb | 39 +++++++++ 13 files changed, 197 insertions(+), 61 deletions(-) create mode 100644 app/assets/javascripts/utilities/dynamicallyLoadScript.js create mode 100644 spec/fixtures/files/article_with_runkit_tag.txt create mode 100644 spec/support/runkit_tag_context.rb diff --git a/app/assets/javascripts/initializers/initializeCommentPreview.js b/app/assets/javascripts/initializers/initializeCommentPreview.js index f254a351b..c9332a2ff 100644 --- a/app/assets/javascripts/initializers/initializeCommentPreview.js +++ b/app/assets/javascripts/initializers/initializeCommentPreview.js @@ -1,4 +1,4 @@ -'use strict'; +/* global activateRunkitTags */ function getAndShowPreview(markdownPreviewPane, markdownEditor) { function successCb(body) { @@ -6,6 +6,7 @@ function getAndShowPreview(markdownPreviewPane, markdownEditor) { markdownEditor.classList.toggle('preview-loading'); markdownEditor.classList.toggle('preview-toggle'); markdownPreviewPane.innerHTML = body.processed_html; // eslint-disable-line no-param-reassign + activateRunkitTags(); } const payload = JSON.stringify({ @@ -15,11 +16,11 @@ function getAndShowPreview(markdownPreviewPane, markdownEditor) { }); getCsrfToken() .then(sendFetch('comment-preview', payload)) - .then(response => { + .then((response) => { return response.json(); }) .then(successCb) - .catch(err => { + .catch((err) => { console.log('error!'); // eslint-disable-line console.log(err); // eslint-disable-line no-console }); diff --git a/app/assets/javascripts/initializers/initializeCommentsPage.js.erb b/app/assets/javascripts/initializers/initializeCommentsPage.js.erb index 92d26dbfd..62fde8dde 100644 --- a/app/assets/javascripts/initializers/initializeCommentsPage.js.erb +++ b/app/assets/javascripts/initializers/initializeCommentsPage.js.erb @@ -247,6 +247,7 @@ function handleCommentSubmit(event) { initializeCommentsPage(); initializeCommentDate(); initializeCommentDropdown(); + activateRunkitTags(); }) } else { response.json().then(function parseError(errorReponse) { diff --git a/app/assets/javascripts/utilities/dynamicallyLoadScript.js b/app/assets/javascripts/utilities/dynamicallyLoadScript.js new file mode 100644 index 000000000..5dcbc5fc2 --- /dev/null +++ b/app/assets/javascripts/utilities/dynamicallyLoadScript.js @@ -0,0 +1,10 @@ + + +function dynamicallyLoadScript(url) { + if (document.querySelector(`script[src='${url}']`)) return; + + const script = document.createElement('script'); + script.src = url; + + document.head.appendChild(script); +} diff --git a/app/javascript/article-form/articleForm.jsx b/app/javascript/article-form/articleForm.jsx index 45716ae92..91f9fd685 100644 --- a/app/javascript/article-form/articleForm.jsx +++ b/app/javascript/article-form/articleForm.jsx @@ -21,6 +21,8 @@ import KeyboardShortcutsHandler from './elements/keyboardShortcutsHandler'; import Tags from '../shared/components/tags'; import { OrganizationPicker } from '../organization/OrganizationPicker'; +/* global activateRunkitTags */ + const SetupImageButton = ({ className, imgSrc, @@ -53,19 +55,7 @@ export default class ArticleForm extends Component { } static handleRunkitPreview() { - const targets = document.getElementsByClassName('runkit-element'); - for (let i = 0; i < targets.length; i += 1) { - if (targets[i].children.length > 0) { - const preamble = targets[i].children[0].textContent; - const content = targets[i].children[1].textContent; - targets[i].innerHTML = ''; - window.RunKit.createNotebook({ - element: targets[i], - source: content, - preamble, - }); - } - } + activateRunkitTags(); } static propTypes = { @@ -173,7 +163,7 @@ export default class ArticleForm extends Component { }; }; - toggleHelp = e => { + toggleHelp = (e) => { const { helpShowing } = this.state; e.preventDefault(); window.scrollTo(0, 0); @@ -182,7 +172,7 @@ export default class ArticleForm extends Component { }); }; - fetchPreview = e => { + fetchPreview = (e) => { const { previewShowing, bodyMarkdown } = this.state; e.preventDefault(); if (previewShowing) { @@ -194,7 +184,7 @@ export default class ArticleForm extends Component { } }; - toggleImageManagement = e => { + toggleImageManagement = (e) => { const { imageManagementShowing } = this.state; e.preventDefault(); window.scrollTo(0, 0); @@ -205,7 +195,7 @@ export default class ArticleForm extends Component { }); }; - toggleMoreConfig = e => { + toggleMoreConfig = (e) => { const { moreConfigShowing } = this.state; e.preventDefault(); this.setState({ @@ -213,7 +203,7 @@ export default class ArticleForm extends Component { }); }; - showPreview = response => { + showPreview = (response) => { if (response.processed_html) { this.setState({ ...this.setCommonProps({ previewShowing: true }), @@ -228,25 +218,25 @@ export default class ArticleForm extends Component { } }; - handleOrgIdChange = e => { + handleOrgIdChange = (e) => { const organizationId = e.target.selectedOptions[0].value; this.setState({ organizationId }); }; - failedPreview = response => { + failedPreview = (response) => { // TODO: console.log should not be part of production code. Remove it! // eslint-disable-next-line no-console console.log(response); }; - handleConfigChange = e => { + handleConfigChange = (e) => { e.preventDefault(); const newState = {}; newState[e.target.name] = e.target.value; this.setState(newState); }; - handleMainImageUrlChange = payload => { + handleMainImageUrlChange = (payload) => { this.setState({ mainImage: payload.links[0], imageManagementShowing: false, @@ -259,7 +249,7 @@ export default class ArticleForm extends Component { window.removeEventListener('beforeunload', this.localStoreContent); }; - onPublish = e => { + onPublish = (e) => { e.preventDefault(); this.setState({ submitting: true, published: true }); const { state } = this; @@ -267,7 +257,7 @@ export default class ArticleForm extends Component { submitArticle(state, this.removeLocalStorage, this.handleArticleError); }; - onSaveDraft = e => { + onSaveDraft = (e) => { e.preventDefault(); this.setState({ submitting: true, published: false }); const { state } = this; @@ -275,15 +265,15 @@ export default class ArticleForm extends Component { submitArticle(state, this.removeLocalStorage, this.handleArticleError); }; - handleTitleKeyDown = e => { + handleTitleKeyDown = (e) => { if (e.keyCode === 13) { e.preventDefault(); } }; - handleBodyKeyDown = _e => {}; + handleBodyKeyDown = (_e) => {}; - onClearChanges = e => { + onClearChanges = (e) => { e.preventDefault(); // eslint-disable-next-line no-alert const revert = window.confirm( @@ -314,7 +304,7 @@ export default class ArticleForm extends Component { }); }; - handleArticleError = response => { + handleArticleError = (response) => { window.scrollTo(0, 0); this.setState({ errors: response, diff --git a/app/liquid_tags/runkit_tag.rb b/app/liquid_tags/runkit_tag.rb index 29e68b164..3df3ffd57 100644 --- a/app/liquid_tags/runkit_tag.rb +++ b/app/liquid_tags/runkit_tag.rb @@ -2,32 +2,68 @@ class RunkitTag < Liquid::Block PARTIAL = "liquids/runkit".freeze SCRIPT = <<~JAVASCRIPT.freeze - var checkRunkit = setInterval(function() { - try { - if (typeof(RunKit) !== 'undefined') { - var targets = document.getElementsByClassName("runkit-element"); - for (var i = 0; i < targets.length; i++) { - var wrapperContent = targets[i].textContent; - if (/^(\