├── .gitignore ├── README.md ├── dist ├── index.js └── index.mjs ├── package.json ├── rollup.config.js ├── samples ├── .gitignore ├── README.md ├── package.json ├── public │ ├── bundle.css │ ├── bundle.js │ ├── bundle.js.map │ ├── favicon.png │ ├── global.css │ ├── index.html │ └── public │ │ └── bundle.css ├── rollup.config.js └── src │ ├── App.svelte │ └── main.js └── src ├── Ckeditor.svelte └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | yarn.lock 4 | package-lock.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## CKEditor5 editor component for Svelte 3 2 | 3 | This component is a thin wrapper around ckeditor5 document editor. 4 | Below are the set of instructions to create svelte project, install component and a basic setup with CKEditor DocumentEditor build. 5 | 6 | ### How to install package 7 | 8 | ```bash 9 | $ npm i ckeditor5-svelte 10 | ``` 11 | 12 | ### Getting started 13 | 14 | #### Create a new svelte project and install dependencies 15 | 16 | ```bash 17 | npx degit sveltejs/template my-svelte-project 18 | # or download and extract 19 | cd my-svelte-project 20 | # to use Typescript run: 21 | # node scripts/setupTypeScript.js 22 | 23 | npm install 24 | ``` 25 | 26 | #### Install ckeditor5-svelte package 27 | 28 | ```bash 29 | npm i ckeditor5-svelte 30 | ``` 31 | 32 | #### Install DocumentEditor build of ckeditor 33 | 34 | ```bash 35 | npm i @ckeditor/ckeditor5-build-decoupled-document 36 | ``` 37 | 38 | #### Update App.svelte in your project with the following 39 | 40 | ```js 41 | 80 | 81 | 83 | 84 |
85 |
86 | 91 |
92 |
93 | ``` 94 | 95 | #### Run your project 96 | 97 | ```bash 98 | npm run dev 99 | ``` 100 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Ckeditor = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | function noop() { } 8 | function run(fn) { 9 | return fn(); 10 | } 11 | function blank_object() { 12 | return Object.create(null); 13 | } 14 | function run_all(fns) { 15 | fns.forEach(run); 16 | } 17 | function is_function(thing) { 18 | return typeof thing === 'function'; 19 | } 20 | function safe_not_equal(a, b) { 21 | return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); 22 | } 23 | function is_empty(obj) { 24 | return Object.keys(obj).length === 0; 25 | } 26 | function insert(target, node, anchor) { 27 | target.insertBefore(node, anchor || null); 28 | } 29 | function detach(node) { 30 | node.parentNode.removeChild(node); 31 | } 32 | function element(name) { 33 | return document.createElement(name); 34 | } 35 | function attr(node, attribute, value) { 36 | if (value == null) 37 | node.removeAttribute(attribute); 38 | else if (node.getAttribute(attribute) !== value) 39 | node.setAttribute(attribute, value); 40 | } 41 | function children(element) { 42 | return Array.from(element.childNodes); 43 | } 44 | function custom_event(type, detail) { 45 | const e = document.createEvent('CustomEvent'); 46 | e.initCustomEvent(type, false, false, detail); 47 | return e; 48 | } 49 | 50 | let current_component; 51 | function set_current_component(component) { 52 | current_component = component; 53 | } 54 | function get_current_component() { 55 | if (!current_component) 56 | throw new Error('Function called outside component initialization'); 57 | return current_component; 58 | } 59 | function onMount(fn) { 60 | get_current_component().$$.on_mount.push(fn); 61 | } 62 | function onDestroy(fn) { 63 | get_current_component().$$.on_destroy.push(fn); 64 | } 65 | function createEventDispatcher() { 66 | const component = get_current_component(); 67 | return (type, detail) => { 68 | const callbacks = component.$$.callbacks[type]; 69 | if (callbacks) { 70 | // TODO are there situations where events could be dispatched 71 | // in a server (non-DOM) environment? 72 | const event = custom_event(type, detail); 73 | callbacks.slice().forEach(fn => { 74 | fn.call(component, event); 75 | }); 76 | } 77 | }; 78 | } 79 | 80 | const dirty_components = []; 81 | const binding_callbacks = []; 82 | const render_callbacks = []; 83 | const flush_callbacks = []; 84 | const resolved_promise = Promise.resolve(); 85 | let update_scheduled = false; 86 | function schedule_update() { 87 | if (!update_scheduled) { 88 | update_scheduled = true; 89 | resolved_promise.then(flush); 90 | } 91 | } 92 | function add_render_callback(fn) { 93 | render_callbacks.push(fn); 94 | } 95 | let flushing = false; 96 | const seen_callbacks = new Set(); 97 | function flush() { 98 | if (flushing) 99 | return; 100 | flushing = true; 101 | do { 102 | // first, call beforeUpdate functions 103 | // and update components 104 | for (let i = 0; i < dirty_components.length; i += 1) { 105 | const component = dirty_components[i]; 106 | set_current_component(component); 107 | update(component.$$); 108 | } 109 | set_current_component(null); 110 | dirty_components.length = 0; 111 | while (binding_callbacks.length) 112 | binding_callbacks.pop()(); 113 | // then, once components are updated, call 114 | // afterUpdate functions. This may cause 115 | // subsequent updates... 116 | for (let i = 0; i < render_callbacks.length; i += 1) { 117 | const callback = render_callbacks[i]; 118 | if (!seen_callbacks.has(callback)) { 119 | // ...so guard against infinite loops 120 | seen_callbacks.add(callback); 121 | callback(); 122 | } 123 | } 124 | render_callbacks.length = 0; 125 | } while (dirty_components.length); 126 | while (flush_callbacks.length) { 127 | flush_callbacks.pop()(); 128 | } 129 | update_scheduled = false; 130 | flushing = false; 131 | seen_callbacks.clear(); 132 | } 133 | function update($$) { 134 | if ($$.fragment !== null) { 135 | $$.update(); 136 | run_all($$.before_update); 137 | const dirty = $$.dirty; 138 | $$.dirty = [-1]; 139 | $$.fragment && $$.fragment.p($$.ctx, dirty); 140 | $$.after_update.forEach(add_render_callback); 141 | } 142 | } 143 | const outroing = new Set(); 144 | function transition_in(block, local) { 145 | if (block && block.i) { 146 | outroing.delete(block); 147 | block.i(local); 148 | } 149 | } 150 | function mount_component(component, target, anchor) { 151 | const { fragment, on_mount, on_destroy, after_update } = component.$$; 152 | fragment && fragment.m(target, anchor); 153 | // onMount happens before the initial afterUpdate 154 | add_render_callback(() => { 155 | const new_on_destroy = on_mount.map(run).filter(is_function); 156 | if (on_destroy) { 157 | on_destroy.push(...new_on_destroy); 158 | } 159 | else { 160 | // Edge case - component was destroyed immediately, 161 | // most likely as a result of a binding initialising 162 | run_all(new_on_destroy); 163 | } 164 | component.$$.on_mount = []; 165 | }); 166 | after_update.forEach(add_render_callback); 167 | } 168 | function destroy_component(component, detaching) { 169 | const $$ = component.$$; 170 | if ($$.fragment !== null) { 171 | run_all($$.on_destroy); 172 | $$.fragment && $$.fragment.d(detaching); 173 | // TODO null out other refs, including component.$$ (but need to 174 | // preserve final state?) 175 | $$.on_destroy = $$.fragment = null; 176 | $$.ctx = []; 177 | } 178 | } 179 | function make_dirty(component, i) { 180 | if (component.$$.dirty[0] === -1) { 181 | dirty_components.push(component); 182 | schedule_update(); 183 | component.$$.dirty.fill(0); 184 | } 185 | component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31)); 186 | } 187 | function init(component, options, instance, create_fragment, not_equal, props, dirty = [-1]) { 188 | const parent_component = current_component; 189 | set_current_component(component); 190 | const $$ = component.$$ = { 191 | fragment: null, 192 | ctx: null, 193 | // state 194 | props, 195 | update: noop, 196 | not_equal, 197 | bound: blank_object(), 198 | // lifecycle 199 | on_mount: [], 200 | on_destroy: [], 201 | before_update: [], 202 | after_update: [], 203 | context: new Map(parent_component ? parent_component.$$.context : []), 204 | // everything else 205 | callbacks: blank_object(), 206 | dirty, 207 | skip_bound: false 208 | }; 209 | let ready = false; 210 | $$.ctx = instance 211 | ? instance(component, options.props || {}, (i, ret, ...rest) => { 212 | const value = rest.length ? rest[0] : ret; 213 | if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) { 214 | if (!$$.skip_bound && $$.bound[i]) 215 | $$.bound[i](value); 216 | if (ready) 217 | make_dirty(component, i); 218 | } 219 | return ret; 220 | }) 221 | : []; 222 | $$.update(); 223 | ready = true; 224 | run_all($$.before_update); 225 | // `false` as a special case of no DOM component 226 | $$.fragment = create_fragment ? create_fragment($$.ctx) : false; 227 | if (options.target) { 228 | if (options.hydrate) { 229 | const nodes = children(options.target); 230 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 231 | $$.fragment && $$.fragment.l(nodes); 232 | nodes.forEach(detach); 233 | } 234 | else { 235 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 236 | $$.fragment && $$.fragment.c(); 237 | } 238 | if (options.intro) 239 | transition_in(component.$$.fragment); 240 | mount_component(component, options.target, options.anchor); 241 | flush(); 242 | } 243 | set_current_component(parent_component); 244 | } 245 | /** 246 | * Base class for Svelte components. Used when dev=false. 247 | */ 248 | class SvelteComponent { 249 | $destroy() { 250 | destroy_component(this, 1); 251 | this.$destroy = noop; 252 | } 253 | $on(type, callback) { 254 | const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = [])); 255 | callbacks.push(callback); 256 | return () => { 257 | const index = callbacks.indexOf(callback); 258 | if (index !== -1) 259 | callbacks.splice(index, 1); 260 | }; 261 | } 262 | $set($$props) { 263 | if (this.$$set && !is_empty($$props)) { 264 | this.$$.skip_bound = true; 265 | this.$$set($$props); 266 | this.$$.skip_bound = false; 267 | } 268 | } 269 | } 270 | 271 | var justDebounceIt = debounce; 272 | 273 | function debounce(fn, wait, callFirst) { 274 | var timeout; 275 | return function() { 276 | if (!wait) { 277 | return fn.apply(this, arguments); 278 | } 279 | var context = this; 280 | var args = arguments; 281 | var callNow = callFirst && !timeout; 282 | clearTimeout(timeout); 283 | timeout = setTimeout(function() { 284 | timeout = null; 285 | if (!callNow) { 286 | return fn.apply(context, args); 287 | } 288 | }, wait); 289 | 290 | if (callNow) { 291 | return fn.apply(this, arguments); 292 | } 293 | }; 294 | } 295 | 296 | /* src/Ckeditor.svelte generated by Svelte v3.32.1 */ 297 | 298 | function create_fragment(ctx) { 299 | let div; 300 | 301 | return { 302 | c() { 303 | div = element("div"); 304 | attr(div, "id", "_editor"); 305 | }, 306 | m(target, anchor) { 307 | insert(target, div, anchor); 308 | }, 309 | p: noop, 310 | i: noop, 311 | o: noop, 312 | d(detaching) { 313 | if (detaching) detach(div); 314 | } 315 | }; 316 | } 317 | 318 | const INPUT_EVENT_DEBOUNCE_WAIT = 300; 319 | 320 | function instance_1($$self, $$props, $$invalidate) { 321 | let { editor = null } = $$props; 322 | let { value = "" } = $$props; 323 | let { config = () => ({}) } = $$props; 324 | let { disabled = false } = $$props; 325 | 326 | // Instance variables 327 | let instance = null; 328 | 329 | let lastEditorData = ""; 330 | let editorElement; 331 | const dispatch = createEventDispatcher(); 332 | 333 | function watchValue(x) { 334 | if (instance && x !== lastEditorData) { 335 | instance.setData(x); 336 | } 337 | } 338 | 339 | onMount(() => { 340 | // If value is passed then add it to config 341 | if (value) { 342 | Object.assign(config, { initialData: value }); 343 | } 344 | 345 | // Get dom element to mount initialised editor instance 346 | editorElement = document.getElementById("_editor"); 347 | 348 | editor.create(editorElement, config).then(editor => { 349 | // Save the reference to the instance for future use. 350 | instance = editor; 351 | 352 | // Set initial disabled state. 353 | editor.isReadOnly = disabled; 354 | 355 | // Let the world know the editor is ready. 356 | dispatch("ready", editor); 357 | 358 | setUpEditorEvents(); 359 | }).catch(error => { 360 | console.error(error); 361 | }); 362 | }); 363 | 364 | onDestroy(() => { 365 | if (instance) { 366 | instance.destroy(); 367 | instance = null; 368 | } 369 | 370 | // Note: By the time the editor is destroyed (promise resolved, editor#destroy fired) 371 | // the Vue component will not be able to emit any longer. 372 | // So emitting #destroy a bit earlier. 373 | dispatch("destroy", instance); 374 | }); 375 | 376 | function setUpEditorEvents() { 377 | const emitInputEvent = evt => { 378 | // Cache the last editor data. This kind of data is a result of typing, 379 | // editor command execution, collaborative changes to the document, etc. 380 | // This data is compared when the component value changes in a 2-way binding. 381 | const data = $$invalidate(0, value = lastEditorData = instance.getData()); 382 | 383 | dispatch("input", { data, evt, instance }); 384 | }; 385 | 386 | // Debounce emitting the #input event. When data is huge, instance#getData() 387 | // takes a lot of time to execute on every single key press and ruins the UX. 388 | instance.model.document.on("change:data", justDebounceIt(emitInputEvent, INPUT_EVENT_DEBOUNCE_WAIT)); 389 | 390 | instance.editing.view.document.on("focus", evt => { 391 | dispatch("focus", { evt, instance }); 392 | }); 393 | 394 | instance.editing.view.document.on("blur", evt => { 395 | dispatch("blur", { evt, instance }); 396 | }); 397 | } 398 | 399 | $$self.$$set = $$props => { 400 | if ("editor" in $$props) $$invalidate(1, editor = $$props.editor); 401 | if ("value" in $$props) $$invalidate(0, value = $$props.value); 402 | if ("config" in $$props) $$invalidate(2, config = $$props.config); 403 | if ("disabled" in $$props) $$invalidate(3, disabled = $$props.disabled); 404 | }; 405 | 406 | $$self.$$.update = () => { 407 | if ($$self.$$.dirty & /*value*/ 1) { 408 | watchValue(value); 409 | } 410 | }; 411 | 412 | return [value, editor, config, disabled]; 413 | } 414 | 415 | class Ckeditor extends SvelteComponent { 416 | constructor(options) { 417 | super(); 418 | 419 | init(this, options, instance_1, create_fragment, safe_not_equal, { 420 | editor: 1, 421 | value: 0, 422 | config: 2, 423 | disabled: 3 424 | }); 425 | } 426 | } 427 | 428 | return Ckeditor; 429 | 430 | }))); 431 | -------------------------------------------------------------------------------- /dist/index.mjs: -------------------------------------------------------------------------------- 1 | function noop() { } 2 | function run(fn) { 3 | return fn(); 4 | } 5 | function blank_object() { 6 | return Object.create(null); 7 | } 8 | function run_all(fns) { 9 | fns.forEach(run); 10 | } 11 | function is_function(thing) { 12 | return typeof thing === 'function'; 13 | } 14 | function safe_not_equal(a, b) { 15 | return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); 16 | } 17 | function is_empty(obj) { 18 | return Object.keys(obj).length === 0; 19 | } 20 | function insert(target, node, anchor) { 21 | target.insertBefore(node, anchor || null); 22 | } 23 | function detach(node) { 24 | node.parentNode.removeChild(node); 25 | } 26 | function element(name) { 27 | return document.createElement(name); 28 | } 29 | function attr(node, attribute, value) { 30 | if (value == null) 31 | node.removeAttribute(attribute); 32 | else if (node.getAttribute(attribute) !== value) 33 | node.setAttribute(attribute, value); 34 | } 35 | function children(element) { 36 | return Array.from(element.childNodes); 37 | } 38 | function custom_event(type, detail) { 39 | const e = document.createEvent('CustomEvent'); 40 | e.initCustomEvent(type, false, false, detail); 41 | return e; 42 | } 43 | 44 | let current_component; 45 | function set_current_component(component) { 46 | current_component = component; 47 | } 48 | function get_current_component() { 49 | if (!current_component) 50 | throw new Error('Function called outside component initialization'); 51 | return current_component; 52 | } 53 | function onMount(fn) { 54 | get_current_component().$$.on_mount.push(fn); 55 | } 56 | function onDestroy(fn) { 57 | get_current_component().$$.on_destroy.push(fn); 58 | } 59 | function createEventDispatcher() { 60 | const component = get_current_component(); 61 | return (type, detail) => { 62 | const callbacks = component.$$.callbacks[type]; 63 | if (callbacks) { 64 | // TODO are there situations where events could be dispatched 65 | // in a server (non-DOM) environment? 66 | const event = custom_event(type, detail); 67 | callbacks.slice().forEach(fn => { 68 | fn.call(component, event); 69 | }); 70 | } 71 | }; 72 | } 73 | 74 | const dirty_components = []; 75 | const binding_callbacks = []; 76 | const render_callbacks = []; 77 | const flush_callbacks = []; 78 | const resolved_promise = Promise.resolve(); 79 | let update_scheduled = false; 80 | function schedule_update() { 81 | if (!update_scheduled) { 82 | update_scheduled = true; 83 | resolved_promise.then(flush); 84 | } 85 | } 86 | function add_render_callback(fn) { 87 | render_callbacks.push(fn); 88 | } 89 | let flushing = false; 90 | const seen_callbacks = new Set(); 91 | function flush() { 92 | if (flushing) 93 | return; 94 | flushing = true; 95 | do { 96 | // first, call beforeUpdate functions 97 | // and update components 98 | for (let i = 0; i < dirty_components.length; i += 1) { 99 | const component = dirty_components[i]; 100 | set_current_component(component); 101 | update(component.$$); 102 | } 103 | set_current_component(null); 104 | dirty_components.length = 0; 105 | while (binding_callbacks.length) 106 | binding_callbacks.pop()(); 107 | // then, once components are updated, call 108 | // afterUpdate functions. This may cause 109 | // subsequent updates... 110 | for (let i = 0; i < render_callbacks.length; i += 1) { 111 | const callback = render_callbacks[i]; 112 | if (!seen_callbacks.has(callback)) { 113 | // ...so guard against infinite loops 114 | seen_callbacks.add(callback); 115 | callback(); 116 | } 117 | } 118 | render_callbacks.length = 0; 119 | } while (dirty_components.length); 120 | while (flush_callbacks.length) { 121 | flush_callbacks.pop()(); 122 | } 123 | update_scheduled = false; 124 | flushing = false; 125 | seen_callbacks.clear(); 126 | } 127 | function update($$) { 128 | if ($$.fragment !== null) { 129 | $$.update(); 130 | run_all($$.before_update); 131 | const dirty = $$.dirty; 132 | $$.dirty = [-1]; 133 | $$.fragment && $$.fragment.p($$.ctx, dirty); 134 | $$.after_update.forEach(add_render_callback); 135 | } 136 | } 137 | const outroing = new Set(); 138 | function transition_in(block, local) { 139 | if (block && block.i) { 140 | outroing.delete(block); 141 | block.i(local); 142 | } 143 | } 144 | function mount_component(component, target, anchor) { 145 | const { fragment, on_mount, on_destroy, after_update } = component.$$; 146 | fragment && fragment.m(target, anchor); 147 | // onMount happens before the initial afterUpdate 148 | add_render_callback(() => { 149 | const new_on_destroy = on_mount.map(run).filter(is_function); 150 | if (on_destroy) { 151 | on_destroy.push(...new_on_destroy); 152 | } 153 | else { 154 | // Edge case - component was destroyed immediately, 155 | // most likely as a result of a binding initialising 156 | run_all(new_on_destroy); 157 | } 158 | component.$$.on_mount = []; 159 | }); 160 | after_update.forEach(add_render_callback); 161 | } 162 | function destroy_component(component, detaching) { 163 | const $$ = component.$$; 164 | if ($$.fragment !== null) { 165 | run_all($$.on_destroy); 166 | $$.fragment && $$.fragment.d(detaching); 167 | // TODO null out other refs, including component.$$ (but need to 168 | // preserve final state?) 169 | $$.on_destroy = $$.fragment = null; 170 | $$.ctx = []; 171 | } 172 | } 173 | function make_dirty(component, i) { 174 | if (component.$$.dirty[0] === -1) { 175 | dirty_components.push(component); 176 | schedule_update(); 177 | component.$$.dirty.fill(0); 178 | } 179 | component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31)); 180 | } 181 | function init(component, options, instance, create_fragment, not_equal, props, dirty = [-1]) { 182 | const parent_component = current_component; 183 | set_current_component(component); 184 | const $$ = component.$$ = { 185 | fragment: null, 186 | ctx: null, 187 | // state 188 | props, 189 | update: noop, 190 | not_equal, 191 | bound: blank_object(), 192 | // lifecycle 193 | on_mount: [], 194 | on_destroy: [], 195 | before_update: [], 196 | after_update: [], 197 | context: new Map(parent_component ? parent_component.$$.context : []), 198 | // everything else 199 | callbacks: blank_object(), 200 | dirty, 201 | skip_bound: false 202 | }; 203 | let ready = false; 204 | $$.ctx = instance 205 | ? instance(component, options.props || {}, (i, ret, ...rest) => { 206 | const value = rest.length ? rest[0] : ret; 207 | if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) { 208 | if (!$$.skip_bound && $$.bound[i]) 209 | $$.bound[i](value); 210 | if (ready) 211 | make_dirty(component, i); 212 | } 213 | return ret; 214 | }) 215 | : []; 216 | $$.update(); 217 | ready = true; 218 | run_all($$.before_update); 219 | // `false` as a special case of no DOM component 220 | $$.fragment = create_fragment ? create_fragment($$.ctx) : false; 221 | if (options.target) { 222 | if (options.hydrate) { 223 | const nodes = children(options.target); 224 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 225 | $$.fragment && $$.fragment.l(nodes); 226 | nodes.forEach(detach); 227 | } 228 | else { 229 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 230 | $$.fragment && $$.fragment.c(); 231 | } 232 | if (options.intro) 233 | transition_in(component.$$.fragment); 234 | mount_component(component, options.target, options.anchor); 235 | flush(); 236 | } 237 | set_current_component(parent_component); 238 | } 239 | /** 240 | * Base class for Svelte components. Used when dev=false. 241 | */ 242 | class SvelteComponent { 243 | $destroy() { 244 | destroy_component(this, 1); 245 | this.$destroy = noop; 246 | } 247 | $on(type, callback) { 248 | const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = [])); 249 | callbacks.push(callback); 250 | return () => { 251 | const index = callbacks.indexOf(callback); 252 | if (index !== -1) 253 | callbacks.splice(index, 1); 254 | }; 255 | } 256 | $set($$props) { 257 | if (this.$$set && !is_empty($$props)) { 258 | this.$$.skip_bound = true; 259 | this.$$set($$props); 260 | this.$$.skip_bound = false; 261 | } 262 | } 263 | } 264 | 265 | var justDebounceIt = debounce; 266 | 267 | function debounce(fn, wait, callFirst) { 268 | var timeout; 269 | return function() { 270 | if (!wait) { 271 | return fn.apply(this, arguments); 272 | } 273 | var context = this; 274 | var args = arguments; 275 | var callNow = callFirst && !timeout; 276 | clearTimeout(timeout); 277 | timeout = setTimeout(function() { 278 | timeout = null; 279 | if (!callNow) { 280 | return fn.apply(context, args); 281 | } 282 | }, wait); 283 | 284 | if (callNow) { 285 | return fn.apply(this, arguments); 286 | } 287 | }; 288 | } 289 | 290 | /* src/Ckeditor.svelte generated by Svelte v3.32.1 */ 291 | 292 | function create_fragment(ctx) { 293 | let div; 294 | 295 | return { 296 | c() { 297 | div = element("div"); 298 | attr(div, "id", "_editor"); 299 | }, 300 | m(target, anchor) { 301 | insert(target, div, anchor); 302 | }, 303 | p: noop, 304 | i: noop, 305 | o: noop, 306 | d(detaching) { 307 | if (detaching) detach(div); 308 | } 309 | }; 310 | } 311 | 312 | const INPUT_EVENT_DEBOUNCE_WAIT = 300; 313 | 314 | function instance_1($$self, $$props, $$invalidate) { 315 | let { editor = null } = $$props; 316 | let { value = "" } = $$props; 317 | let { config = () => ({}) } = $$props; 318 | let { disabled = false } = $$props; 319 | 320 | // Instance variables 321 | let instance = null; 322 | 323 | let lastEditorData = ""; 324 | let editorElement; 325 | const dispatch = createEventDispatcher(); 326 | 327 | function watchValue(x) { 328 | if (instance && x !== lastEditorData) { 329 | instance.setData(x); 330 | } 331 | } 332 | 333 | onMount(() => { 334 | // If value is passed then add it to config 335 | if (value) { 336 | Object.assign(config, { initialData: value }); 337 | } 338 | 339 | // Get dom element to mount initialised editor instance 340 | editorElement = document.getElementById("_editor"); 341 | 342 | editor.create(editorElement, config).then(editor => { 343 | // Save the reference to the instance for future use. 344 | instance = editor; 345 | 346 | // Set initial disabled state. 347 | editor.isReadOnly = disabled; 348 | 349 | // Let the world know the editor is ready. 350 | dispatch("ready", editor); 351 | 352 | setUpEditorEvents(); 353 | }).catch(error => { 354 | console.error(error); 355 | }); 356 | }); 357 | 358 | onDestroy(() => { 359 | if (instance) { 360 | instance.destroy(); 361 | instance = null; 362 | } 363 | 364 | // Note: By the time the editor is destroyed (promise resolved, editor#destroy fired) 365 | // the Vue component will not be able to emit any longer. 366 | // So emitting #destroy a bit earlier. 367 | dispatch("destroy", instance); 368 | }); 369 | 370 | function setUpEditorEvents() { 371 | const emitInputEvent = evt => { 372 | // Cache the last editor data. This kind of data is a result of typing, 373 | // editor command execution, collaborative changes to the document, etc. 374 | // This data is compared when the component value changes in a 2-way binding. 375 | const data = $$invalidate(0, value = lastEditorData = instance.getData()); 376 | 377 | dispatch("input", { data, evt, instance }); 378 | }; 379 | 380 | // Debounce emitting the #input event. When data is huge, instance#getData() 381 | // takes a lot of time to execute on every single key press and ruins the UX. 382 | instance.model.document.on("change:data", justDebounceIt(emitInputEvent, INPUT_EVENT_DEBOUNCE_WAIT)); 383 | 384 | instance.editing.view.document.on("focus", evt => { 385 | dispatch("focus", { evt, instance }); 386 | }); 387 | 388 | instance.editing.view.document.on("blur", evt => { 389 | dispatch("blur", { evt, instance }); 390 | }); 391 | } 392 | 393 | $$self.$$set = $$props => { 394 | if ("editor" in $$props) $$invalidate(1, editor = $$props.editor); 395 | if ("value" in $$props) $$invalidate(0, value = $$props.value); 396 | if ("config" in $$props) $$invalidate(2, config = $$props.config); 397 | if ("disabled" in $$props) $$invalidate(3, disabled = $$props.disabled); 398 | }; 399 | 400 | $$self.$$.update = () => { 401 | if ($$self.$$.dirty & /*value*/ 1) { 402 | watchValue(value); 403 | } 404 | }; 405 | 406 | return [value, editor, config, disabled]; 407 | } 408 | 409 | class Ckeditor extends SvelteComponent { 410 | constructor(options) { 411 | super(); 412 | 413 | init(this, options, instance_1, create_fragment, safe_not_equal, { 414 | editor: 1, 415 | value: 0, 416 | config: 2, 417 | disabled: 3 418 | }); 419 | } 420 | } 421 | 422 | export default Ckeditor; 423 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ckeditor5-svelte", 3 | "version": "0.0.10", 4 | "description": "Svelte3 component for CKEditor5", 5 | "homepage": "https://github.com/techlab23/ckeditor5-svelte", 6 | "author": "Shirish Nigam ", 7 | "license": "GPL-2.0-or-later", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/techlab23/ckeditor5-svelte" 11 | }, 12 | "publishConfig": { 13 | "registry": "https://registry.npmjs.org/" 14 | }, 15 | "svelte": "src/index.js", 16 | "module": "dist/index.mjs", 17 | "main": "dist/index.js", 18 | "scripts": { 19 | "build": "rollup -c", 20 | "prepublishOnly": "npm run build" 21 | }, 22 | "keywords": [ 23 | "wysiwyg", 24 | "rich text", 25 | "editor", 26 | "html", 27 | "contentEditable", 28 | "editing", 29 | "svelte", 30 | "svelte.js", 31 | "svelte component", 32 | "svelte3 component", 33 | "ckeditor", 34 | "ckeditor5", 35 | "ckeditor 5" 36 | ], 37 | "files": [ 38 | "src", 39 | "dist" 40 | ], 41 | "devDependencies": { 42 | "@rollup/plugin-commonjs": "^17.1.0", 43 | "rollup": "^2.38.4", 44 | "rollup-plugin-node-resolve": "^5.2.0", 45 | "rollup-plugin-svelte": "^7.1.0", 46 | "svelte": "^3.32.1" 47 | }, 48 | "dependencies": { 49 | "just-debounce-it": "^1.1.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | import resolve from 'rollup-plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import pkg from './package.json'; 5 | 6 | export default { 7 | input: 'src/Ckeditor.svelte', 8 | output: [ 9 | { file: pkg.module, format: 'es' }, 10 | { file: pkg.main, format: 'umd', name: 'Ckeditor' }, 11 | ], 12 | plugins: [svelte(), resolve(), commonjs()], 13 | }; 14 | -------------------------------------------------------------------------------- /samples/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | yarn.lock 4 | package-lock.json -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | *Psst — looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)* 2 | 3 | --- 4 | 5 | # svelte app 6 | 7 | This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template. 8 | 9 | To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit): 10 | 11 | ```bash 12 | npx degit sveltejs/template svelte-app 13 | cd svelte-app 14 | ``` 15 | 16 | *Note that you will need to have [Node.js](https://nodejs.org) installed.* 17 | 18 | 19 | ## Get started 20 | 21 | Install the dependencies... 22 | 23 | ```bash 24 | cd svelte-app 25 | npm install 26 | ``` 27 | 28 | ...then start [Rollup](https://rollupjs.org): 29 | 30 | ```bash 31 | npm run dev 32 | ``` 33 | 34 | Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes. 35 | 36 | 37 | ## Deploying to the web 38 | 39 | ### With [now](https://zeit.co/now) 40 | 41 | Install `now` if you haven't already: 42 | 43 | ```bash 44 | npm install -g now 45 | ``` 46 | 47 | Then, from within your project folder: 48 | 49 | ```bash 50 | cd public 51 | now 52 | ``` 53 | 54 | As an alternative, use the [Now desktop client](https://zeit.co/download) and simply drag the unzipped project folder to the taskbar icon. 55 | 56 | ### With [surge](https://surge.sh/) 57 | 58 | Install `surge` if you haven't already: 59 | 60 | ```bash 61 | npm install -g surge 62 | ``` 63 | 64 | Then, from within your project folder: 65 | 66 | ```bash 67 | npm run build 68 | surge public 69 | ``` 70 | -------------------------------------------------------------------------------- /samples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "version": "1.0.0", 4 | "devDependencies": { 5 | "npm-run-all": "^4.1.5", 6 | "rollup": "^2.38.4", 7 | "rollup-plugin-commonjs": "^10.0.0", 8 | "rollup-plugin-css-only": "^3.1.0", 9 | "rollup-plugin-livereload": "^2.0.0", 10 | "rollup-plugin-node-resolve": "^5.2.0", 11 | "rollup-plugin-svelte": "^7.1.0", 12 | "rollup-plugin-terser": "^7.0.2", 13 | "sirv-cli": "^1.0.11", 14 | "svelte": "^3.32.1" 15 | }, 16 | "dependencies": { 17 | "@ckeditor/ckeditor5-build-decoupled-document": "^25.0.0" 18 | }, 19 | "scripts": { 20 | "build": "rollup -c", 21 | "autobuild": "rollup -c -w", 22 | "dev": "run-p start:dev autobuild", 23 | "start": "sirv public --single", 24 | "start:dev": "sirv public --single --dev" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/public/bundle.css: -------------------------------------------------------------------------------- 1 | .preview-area.svelte-1ep30s{margin-top:40px;margin-left:10px} -------------------------------------------------------------------------------- /samples/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techlab23/ckeditor5-svelte/03632ac9151383c034e2918f3630f14988a4ea4e/samples/public/favicon.png -------------------------------------------------------------------------------- /samples/public/global.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | body { 8 | color: #333; 9 | margin: 0; 10 | padding: 8px; 11 | box-sizing: border-box; 12 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 13 | } 14 | 15 | a { 16 | color: rgb(0,100,200); 17 | text-decoration: none; 18 | } 19 | 20 | a:hover { 21 | text-decoration: underline; 22 | } 23 | 24 | a:visited { 25 | color: rgb(0,80,160); 26 | } 27 | 28 | label { 29 | display: block; 30 | } 31 | 32 | input, button, select, textarea { 33 | font-family: inherit; 34 | font-size: inherit; 35 | padding: 0.4em; 36 | margin: 0 0 0.5em 0; 37 | box-sizing: border-box; 38 | border: 1px solid #ccc; 39 | border-radius: 2px; 40 | } 41 | 42 | input:disabled { 43 | color: #ccc; 44 | } 45 | 46 | input[type="range"] { 47 | height: 0; 48 | } 49 | 50 | button { 51 | color: #333; 52 | background-color: #f4f4f4; 53 | outline: none; 54 | } 55 | 56 | button:active { 57 | background-color: #ddd; 58 | } 59 | 60 | button:focus { 61 | border-color: #666; 62 | } 63 | -------------------------------------------------------------------------------- /samples/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte app 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /samples/public/public/bundle.css: -------------------------------------------------------------------------------- 1 | .preview-area.svelte-1ep30s{margin-top:40px;margin-left:10px} -------------------------------------------------------------------------------- /samples/rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | import resolve from 'rollup-plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import livereload from 'rollup-plugin-livereload'; 5 | import { terser } from 'rollup-plugin-terser'; 6 | import css from 'rollup-plugin-css-only'; 7 | 8 | const production = !process.env.ROLLUP_WATCH; 9 | 10 | export default { 11 | input: 'src/main.js', 12 | output: { 13 | sourcemap: true, 14 | format: 'iife', 15 | name: 'app', 16 | file: 'public/bundle.js', 17 | }, 18 | plugins: [ 19 | css({ output: 'bundle.css' }), 20 | svelte({ 21 | // enable run-time checks when not in production 22 | compilerOptions: { 23 | dev: !production, 24 | }, 25 | // we'll extract any component CSS out into 26 | // a separate file — better for performance 27 | // css: (css) => { 28 | // css.write('public/bundle.css'); 29 | // }, 30 | }), 31 | 32 | // If you have external dependencies installed from 33 | // npm, you'll most likely need these plugins. In 34 | // some cases you'll need additional configuration — 35 | // consult the documentation for details: 36 | // https://github.com/rollup/rollup-plugin-commonjs 37 | resolve({ 38 | browser: true, 39 | dedupe: (importee) => 40 | importee === 'svelte' || importee.startsWith('svelte/'), 41 | }), 42 | commonjs(), 43 | 44 | // Watch the `public` directory and refresh the 45 | // browser on changes when not in production 46 | !production && livereload('public'), 47 | 48 | // If we're building for production (npm run build 49 | // instead of npm run dev), minify 50 | production && terser(), 51 | ], 52 | watch: { 53 | clearScreen: false, 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /samples/src/App.svelte: -------------------------------------------------------------------------------- 1 | 41 | 42 |
43 | 48 |
49 | -------------------------------------------------------------------------------- /samples/src/main.js: -------------------------------------------------------------------------------- 1 | import App from "./App.svelte"; 2 | 3 | const app = new App({ 4 | target: document.body 5 | }); 6 | 7 | export default app; 8 | -------------------------------------------------------------------------------- /src/Ckeditor.svelte: -------------------------------------------------------------------------------- 1 | 89 | 90 |
91 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Ckeditor.svelte'; 2 | --------------------------------------------------------------------------------