From be106c478491263d480f8310a145337359d94231 Mon Sep 17 00:00:00 2001 From: Tyler Millis Date: Tue, 18 Feb 2020 13:50:08 -0500 Subject: [PATCH] Implemented a rich text editor cell editor for source html. Made this testable on Expedia and through a new site adapter for Blogger. --- package.json | 1 + src/cell_editors/richTextEditor.css | 14 +++++ src/cell_editors/richTextEditor.js | 84 +++++++++++++++++++++++++++++ src/core.ts | 4 +- src/site_adapters/blogger.ts | 32 +++++++++++ src/site_adapters/expedia.ts | 3 +- src/wildcard.ts | 4 +- yarn.lock | 5 ++ 8 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 src/cell_editors/richTextEditor.css create mode 100644 src/cell_editors/richTextEditor.js create mode 100644 src/site_adapters/blogger.ts diff --git a/package.json b/package.json index 7d2c116..19ed7b3 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "author": "Geoffrey Litt ", "license": "MIT", "dependencies": { + "@ckeditor/ckeditor5-build-classic": "^16.0.0", "@fullcalendar/core": "^4.3.1", "@fullcalendar/daygrid": "^4.3.0", "@fullcalendar/interaction": "^4.3.0", diff --git a/src/cell_editors/richTextEditor.css b/src/cell_editors/richTextEditor.css new file mode 100644 index 0000000..32e6e9d --- /dev/null +++ b/src/cell_editors/richTextEditor.css @@ -0,0 +1,14 @@ +#open-apps-rich-text-editor-container { + background-color: white; + padding: 20px; + position: fixed; + height: 500px; + width: 700px; + bottom: 100px; + right: 50px; + z-index: 1000; +} + +.cke_show_borders { + overflow: scroll; +} \ No newline at end of file diff --git a/src/cell_editors/richTextEditor.js b/src/cell_editors/richTextEditor.js new file mode 100644 index 0000000..2edc66c --- /dev/null +++ b/src/cell_editors/richTextEditor.js @@ -0,0 +1,84 @@ +import Handsontable from 'handsontable'; +import ClassicEditor from '@ckeditor/ckeditor5-build-classic'; + +import './richTextEditor.css' + +let TEXTAREA_ID = "open-apps-rich-text-editor"; + +function htmlToElement(html) { + var template = document.createElement('template'); + html = html.trim(); + template.innerHTML = html; + return template.content.firstChild; +} + +class RichTextEditor extends Handsontable.editors.BaseEditor { + constructor(hotInstance) { + super(hotInstance); + } + + init(){ + this.text = this.originalValue; + this.initializeHTML(); + } + + getValue(){ + return this.editor.getData(); + } + + setValue(newValue){ + this.text = newValue; + } + + open(){ + let oldValue = document.getElementById(TEXTAREA_ID).value; + + //Has to update the textarea for the rich text editor api to update as well + if(this.text !== oldValue){ + this.destroyEditor(); + document.getElementById(TEXTAREA_ID).value = this.text; + this.createEditor(); + } + + this.textEditorDiv.style.display = ''; + } + + initializeHTML(){ + this.textEditorDiv = htmlToElement(`
`); + document.body.appendChild(this.textEditorDiv); + + this.createEditor(); + + this.textEditorDiv.style.display = "none"; + this.textEditorDiv.addEventListener('mousedown', e => { + event.stopPropagation(); + }); + } + + createEditor(){ + this.richTextEditor = ClassicEditor.create( document.querySelector(`#${TEXTAREA_ID}`)) + .then( newEditor => { + this.editor = newEditor; + }) + .catch( error => { + console.error(error); + }); + } + + destroyEditor(){ + this.editor.destroy() + .catch( error => { + console.log( error ); + }); + } + + close(){ + this.textEditorDiv.style.display = 'none'; + } + + focus(){ + this.textEditorDiv.focus(); + } +} + +export {RichTextEditor}; diff --git a/src/core.ts b/src/core.ts index f8a7ea5..fd24b0e 100644 --- a/src/core.ts +++ b/src/core.ts @@ -189,7 +189,7 @@ const createTable = (options: SiteAdapterOptions) => { tableData = rows.map(r => { return _.mapValues(r.dataValues, value => { - if (value instanceof HTMLInputElement) { + if (value instanceof HTMLInputElement || value instanceof HTMLTextAreaElement) { return value.value } else if (value instanceof HTMLElement) { return value.textContent @@ -257,7 +257,7 @@ const createTable = (options: SiteAdapterOptions) => { let row = rows[rowIndex] // this won't work with re-sorting; change to ID let el = row.dataValues[prop] - if (el instanceof HTMLInputElement) { + if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) { el.value = newValue } else if (el instanceof HTMLElement) { el.innerText = newValue diff --git a/src/site_adapters/blogger.ts b/src/site_adapters/blogger.ts new file mode 100644 index 0000000..4eb336b --- /dev/null +++ b/src/site_adapters/blogger.ts @@ -0,0 +1,32 @@ +import { RichTextEditor } from '../cell_editors/richTextEditor.js' + +export const BloggerAdapter = { + name: "Blogger", + urlPattern: "blogger.com", + colSpecs: [ + { name: "id", type: "text", hidden: true }, + { name: "document", editable: true, renderer: 'html', type: "text", editor: RichTextEditor }, + { name: "source", editable: true, renderer: 'html', type: "text", editor: RichTextEditor }, + ], + getDataRows: () => { + let container = document.getElementById("blogger-app"); + console.log(container); + let container2 : HTMLElement = container; + return [ + { + el: container2, + dataValues: { + id: 1, // only one row so we can just hardcode an ID + document: container.querySelector("#postingComposeBox"), + source: container.querySelector("#postingHtmlBox"), + } + } + ] + }, + // Reload data anytime there's a click or keypress on the page + setupReloadTriggers: (reload) => { + document.addEventListener("click", (e) => { reload() }); + document.addEventListener("keydown", (e) => { reload() }); + } +}; + diff --git a/src/site_adapters/expedia.ts b/src/site_adapters/expedia.ts index 39383e1..3e44655 100644 --- a/src/site_adapters/expedia.ts +++ b/src/site_adapters/expedia.ts @@ -1,4 +1,5 @@ import { FullCalendarEditor } from '../cell_editors/fullCalendarEditor.js' +import { RichTextEditor } from '../cell_editors/richTextEditor.js' export const ExpediaAdapter = { name: "Expedia2", @@ -6,7 +7,7 @@ export const ExpediaAdapter = { colSpecs: [ { name: "id", type: "text", hidden: true }, { name: "origin", editable: true, type: "text" }, - { name: "destination", editable: true, type: "text" }, + { name: "destination", editable: true, type: "text", editor: RichTextEditor }, { name: "departDate", editable: true, type: "text", editor: FullCalendarEditor }, { name: "returnDate", editable: true, type: "text", editor: FullCalendarEditor } ], diff --git a/src/wildcard.ts b/src/wildcard.ts index 9667a75..3ddfb06 100644 --- a/src/wildcard.ts +++ b/src/wildcard.ts @@ -3,10 +3,12 @@ import { createTable } from './core' import { ExpediaAdapter } from './site_adapters/expedia'; import { AirbnbAdapter } from './site_adapters/airbnb'; +import {BloggerAdapter} from "./site_adapters/blogger"; const siteAdapters = [ ExpediaAdapter, -AirbnbAdapter +AirbnbAdapter, + BloggerAdapter ] const run = function () { diff --git a/yarn.lock b/yarn.lock index 7a6c9c3..d104d77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@ckeditor/ckeditor5-build-classic@^16.0.0": + version "16.0.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-build-classic/-/ckeditor5-build-classic-16.0.0.tgz#9141e94ea6765eda4925aaf062448507410bbe70" + integrity sha512-gBfZqWg3hmCvhq6/wX5UJp4qwl6gB+NJPpOkya2Y3jWKA0HKf3UdlhIvaHq7dRaqhi4unmqaJZVEk5VpZ1vDOg== + "@fullcalendar/core@^4.3.1": version "4.3.1" resolved "https://registry.yarnpkg.com/@fullcalendar/core/-/core-4.3.1.tgz#a061c6d2e998d4155439dbc8aefdfe01cdf648d8"