├── test
├── index.js
├── mocha.opts
├── specs
│ └── editor.spec.js
└── istanbul.reporter.js
├── .gitignore
├── assets
├── fonts
│ ├── Noto-Sans-700
│ │ ├── Noto-Sans-700.eot
│ │ ├── Noto-Sans-700.ttf
│ │ ├── Noto-Sans-700.woff
│ │ ├── Noto-Sans-700.woff2
│ │ └── Noto-Sans-700.svg
│ ├── Noto-Sans-italic
│ │ ├── Noto-Sans-italic.eot
│ │ ├── Noto-Sans-italic.ttf
│ │ ├── Noto-Sans-italic.woff
│ │ └── Noto-Sans-italic.woff2
│ ├── Noto-Sans-regular
│ │ ├── Noto-Sans-regular.eot
│ │ ├── Noto-Sans-regular.ttf
│ │ ├── Noto-Sans-regular.woff
│ │ └── Noto-Sans-regular.woff2
│ └── Noto-Sans-700italic
│ │ ├── Noto-Sans-700italic.eot
│ │ ├── Noto-Sans-700italic.ttf
│ │ ├── Noto-Sans-700italic.woff
│ │ ├── Noto-Sans-700italic.woff2
│ │ └── Noto-Sans-700italic.svg
├── css
│ └── style.scss
└── js
│ └── scale.fix.js
├── .babelrc
├── src
├── helpers
│ ├── store.js
│ ├── EditorColorPicker.svelte
│ ├── EditorModal.svelte
│ ├── util.js
│ └── actions.js
├── Editor.svelte.d.ts
├── app.js
└── Editor.svelte
├── tsconfig.json
├── dist
├── index.html
└── index.min.js
├── .github
└── workflows
│ └── build-publish.yml
├── LICENSE
├── rollup.config.js
├── package.json
├── index.md
├── _sass
├── fonts.scss
├── rouge-github.scss
└── jekyll-theme-minimal.scss
├── _layouts
└── default.html
└── README.md
/test/index.js:
--------------------------------------------------------------------------------
1 | require('browser-env')();
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | npm-debug.log
4 | coverage
5 | .history
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --reporter test/istanbul.reporter.js --recursive --require babel-core/register
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-700/Noto-Sans-700.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-700/Noto-Sans-700.eot
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-700/Noto-Sans-700.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-700/Noto-Sans-700.ttf
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-700/Noto-Sans-700.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-700/Noto-Sans-700.woff
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-700/Noto-Sans-700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-700/Noto-Sans-700.woff2
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-italic/Noto-Sans-italic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-italic/Noto-Sans-italic.eot
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-italic/Noto-Sans-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-italic/Noto-Sans-italic.ttf
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-italic/Noto-Sans-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-italic/Noto-Sans-italic.woff
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-italic/Noto-Sans-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-italic/Noto-Sans-italic.woff2
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-regular/Noto-Sans-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-regular/Noto-Sans-regular.eot
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-regular/Noto-Sans-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-regular/Noto-Sans-regular.ttf
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-regular/Noto-Sans-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-regular/Noto-Sans-regular.woff
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-regular/Noto-Sans-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-regular/Noto-Sans-regular.woff2
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-700italic/Noto-Sans-700italic.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-700italic/Noto-Sans-700italic.eot
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-700italic/Noto-Sans-700italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-700italic/Noto-Sans-700italic.ttf
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nenadpnc/cl-editor/HEAD/assets/fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff2
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "modules": false
7 | }
8 | ]
9 | ],
10 | "env": {
11 | "test": {
12 | "plugins": ["istanbul"]
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/assets/css/style.scss:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 | @import "jekyll-theme-minimal";
5 |
6 | a {
7 | cursor: pointer;
8 | }
9 |
10 | #inlineEdit {
11 | display: inline-block;
12 | }
13 |
14 | #inlineEdit .cl {
15 | margin-top: -46.5px;
16 | margin-left: -10px;
17 | margin-right: 10px;
18 | }
--------------------------------------------------------------------------------
/src/helpers/store.js:
--------------------------------------------------------------------------------
1 | import {writable} from "svelte/store";
2 |
3 | const state = (function(name) {
4 | let state = {
5 | actionBtns: [],
6 | actionObj: {}
7 | }
8 |
9 | const { subscribe, set, update } = writable(state);
10 |
11 | return {
12 | name,
13 | set,
14 | update,
15 | subscribe
16 | }
17 | });
18 |
19 | export const createStateStore = state;
20 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es2015",
5 | "declaration": true,
6 | "noImplicitAny": false,
7 | "sourceMap": false,
8 | "noImplicitReturns": true,
9 | "typeRoots": [
10 | "types"
11 | ]
12 | },
13 | "exclude": [
14 | "node_modules",
15 | "dist"
16 | ],
17 | "include": [
18 | "src/**/*.ts"
19 | ]
20 | }
--------------------------------------------------------------------------------
/test/specs/editor.spec.js:
--------------------------------------------------------------------------------
1 | const Editor = require('../../dist/index');
2 | const { expect } = require('chai');
3 |
4 | describe('Editor', () => {
5 | const div = () => document.createElement('div');
6 |
7 | describe('output', () => {
8 | it('renders edtitor', () => {
9 | const el = div();
10 | new Editor({
11 | target: el
12 | });
13 |
14 | const editorWrapper = el.querySelector('.cl');
15 | expect(editorWrapper).to.not.be.undefined;
16 | });
17 | });
18 | });
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Demo - cl-editor
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Edit this line of text
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/test/istanbul.reporter.js:
--------------------------------------------------------------------------------
1 | const instanbul = require('istanbul');
2 | const MochaSpecReporter = require('mocha/lib/reporters/spec');
3 |
4 | module.exports = function (runner) {
5 | const collector = new instanbul.Collector();
6 | const reporter = new instanbul.Reporter();
7 | reporter.addAll(['lcov', 'json']);
8 | new MochaSpecReporter(runner);
9 |
10 | runner.on('end', function () {
11 | collector.add(global.__coverage__);
12 |
13 | reporter.write(collector, true, function () {
14 | process.stdout.write('report generated');
15 | });
16 | });
17 | };
--------------------------------------------------------------------------------
/.github/workflows/build-publish.yml:
--------------------------------------------------------------------------------
1 | name: Build-Publish
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | test-and-lint:
9 | runs-on: [ubuntu-latest]
10 |
11 | steps:
12 | - name: Checkout 🛎
13 | uses: actions/checkout@v2
14 |
15 | - name: Setup node env 🏗
16 | uses: actions/setup-node@v1
17 | with:
18 | node-version: '14'
19 | registry-url: https://registry.npmjs.org/
20 |
21 | - name: Install dependencies 👨🏻💻
22 | uses: bahmutov/npm-install@v1
23 |
24 | - name: Build Application 🛠
25 | run: npm run prod
26 |
27 | - name: Publish package
28 | run: npm publish
29 | env:
30 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
31 |
--------------------------------------------------------------------------------
/src/Editor.svelte.d.ts:
--------------------------------------------------------------------------------
1 | declare class Editor {
2 | constructor(options: {
3 | target: Element,
4 | props?: {
5 | actions?: ({name: string, title?: string, icon?: string, result?: Function} | string)[],
6 | height?: string,
7 | html?: string,
8 | removeFormatTags?: string[]
9 | }
10 | });
11 |
12 | $destroy(detach?: boolean);
13 |
14 | $on(event: 'change' | 'blur', cb: (event?: any) => void);
15 |
16 | exec(cmd: string, value?: string): void
17 |
18 | getHtml(sanitize?: boolean): string
19 |
20 | getText(): string
21 |
22 | setHtml(html: string, sanitize?: boolean): void
23 |
24 | saveRange(element: Element): void
25 |
26 | restoreRange(element: Element): void
27 |
28 | refs: {
29 | colorPicker: HTMLDivElement,
30 | editor: HTMLDivElement,
31 | modal: HTMLDivElement,
32 | raw: HTMLTextAreaElement
33 | }
34 |
35 | }
36 |
37 | export default Editor;
38 |
--------------------------------------------------------------------------------
/assets/js/scale.fix.js:
--------------------------------------------------------------------------------
1 | (function(document) {
2 | var metas = document.getElementsByTagName('meta'),
3 | changeViewportContent = function(content) {
4 | for (var i = 0; i < metas.length; i++) {
5 | if (metas[i].name == "viewport") {
6 | metas[i].content = content;
7 | }
8 | }
9 | },
10 | initialize = function() {
11 | changeViewportContent("width=device-width, minimum-scale=1.0, maximum-scale=1.0");
12 | },
13 | gestureStart = function() {
14 | changeViewportContent("width=device-width, minimum-scale=0.25, maximum-scale=1.6");
15 | },
16 | gestureEnd = function() {
17 | initialize();
18 | };
19 |
20 |
21 | if (navigator.userAgent.match(/iPhone/i)) {
22 | initialize();
23 |
24 | document.addEventListener("touchstart", gestureStart, false);
25 | document.addEventListener("touchend", gestureEnd, false);
26 | }
27 | })(document);
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Nenad Panić
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import svelte from 'rollup-plugin-svelte';
2 | import {terser} from 'rollup-plugin-terser';
3 | import babel from 'rollup-plugin-babel';
4 | import filesize from 'rollup-plugin-filesize';
5 | import resolve from 'rollup-plugin-node-resolve';
6 | import commonjs from 'rollup-plugin-commonjs';
7 |
8 | const plugins = [
9 | svelte({
10 | extensions: ['.svelte'],
11 | emitCss: false,
12 | exclude:'src/**/*.ts',
13 | compilerOptions: {
14 | dev: false
15 | }
16 | }),
17 | resolve({
18 | brower: true,
19 | dedupe: importee => importee === 'svelte' || importee.startsWith('svelte/')
20 | }),
21 | commonjs(),
22 | babel({ include: 'node_modules/svelte/**' })
23 | ];
24 |
25 | export default [
26 | {
27 | input: 'src/Editor.svelte',
28 | output: {
29 | file: 'dist/index.min.js',
30 | format: 'umd',
31 | name: 'clEditor'
32 | },
33 | plugins: [...plugins, terser(), filesize()]
34 | },
35 | {
36 | input: 'src/Editor.svelte',
37 | output: {
38 | file: 'dist/index.js',
39 | format: 'umd',
40 | name: 'clEditor',
41 | },
42 | plugins
43 | },
44 | {
45 | input: 'src/app.js',
46 | output: {
47 | file: 'dist/index.dev.js',
48 | format: 'iife',
49 | },
50 | plugins
51 | }
52 | ];
53 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | import Editor from './Editor.svelte';
2 |
3 | let inlineEditor;
4 | const inlineEdit = document.getElementById('inlineEdit');
5 | inlineEdit.addEventListener('click', showEditor);
6 |
7 | const editor = new Editor({
8 | target: document.getElementById('editor1')
9 | });
10 |
11 | const editor2 = new Editor({
12 | target: document.getElementById('editor2'),
13 | props: {
14 | actions: [
15 | 'b', 'i', 'u', 'strike', 'h1', 'h2', 'p',
16 | {
17 | name: 'copy',
18 | icon: '📋',
19 | title: 'Copy',
20 | result: () => {
21 | const selection = window.getSelection();
22 | if (!selection.toString().length) {
23 | const range = document.createRange();
24 | range.selectNodeContents(editor2.refs.editor);
25 | selection.removeAllRanges();
26 | selection.addRange(range);
27 | }
28 | editor2.exec('copy');
29 | }
30 | }
31 | ],
32 | html: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean a odio neque. Duis ac laoreet lacus.',
33 | height: '150px'
34 | }
35 | });
36 |
37 | editor2.$on('change', (event) => {
38 | console.log(event.detail);
39 | })
40 |
41 | function showEditor() {
42 | let html = inlineEdit.innerHTML;
43 | inlineEdit.innerHTML = '';
44 | inlineEditor = new Editor({
45 | target: inlineEdit,
46 | props: {
47 | actions: ['b', 'i', 'u', 'strike', 'removeFormat'],
48 | height: 'auto',
49 | html: html
50 | }
51 | });
52 |
53 | inlineEdit.removeEventListener('click', showEditor);
54 |
55 | inlineEditor.$on('blur', () => {
56 | html = inlineEditor.getHtml();
57 | inlineEditor.$destroy();
58 | inlineEdit.innerHTML = html;
59 | inlineEdit.addEventListener('click', showEditor);
60 | });
61 | }
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cl-editor",
3 | "version": "2.3.0",
4 | "description": "Lightweight text editor built with svelte + typescript",
5 | "scripts": {
6 | "dev": "concurrently \"rollup -c rollup.config.js \" \"live-server ./dist/ --port=3000\"",
7 | "prod": "rollup -c rollup.config.js",
8 | "test": "mocha",
9 | "prepublishOnly": "npm run prod"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/nenadpnc/cl-editor"
14 | },
15 | "keywords": [
16 | "html text editor",
17 | "wysiwyg",
18 | "wysiwyg-html-editor",
19 | "svelte",
20 | "typescript"
21 | ],
22 | "author": "nenadpnc",
23 | "license": "MIT",
24 | "homepage": "https://nenadpnc.github.io/cl-editor/",
25 | "devDependencies": {
26 | "@babel/core": "^7.16.0",
27 | "babel-plugin-external-helpers": "^6.22.0",
28 | "babel-preset-env": "^1.7.0",
29 | "babel-runtime": "^6.26.0",
30 | "browser-env": "^3.3.0",
31 | "bundlesize": "^0.18.1",
32 | "chai": "^4.3.4",
33 | "concurrently": "^6.4.0",
34 | "copyfiles": "^2.4.1",
35 | "cross-env": "^7.0.3",
36 | "install": "^0.13.0",
37 | "live-server": "^1.2.1",
38 | "mocha": "^9.1.3",
39 | "rollup": "^2.60.2",
40 | "rollup-plugin-babel": "^4.4.0",
41 | "rollup-plugin-commonjs": "^10.1.0",
42 | "rollup-plugin-filesize": "^9.1.1",
43 | "rollup-plugin-node-resolve": "^5.2.0",
44 | "rollup-plugin-svelte": "^7.1.0",
45 | "rollup-plugin-terser": "^7.0.2",
46 | "sinon": "^12.0.1",
47 | "svelte": "^3.44.2",
48 | "typescript": "^4.5.2"
49 | },
50 | "main": "./dist/index.js",
51 | "svelte": "./src/Editor.svelte",
52 | "types": "./src/Editor.svelte.d.ts",
53 | "files": [
54 | "dist/*.js",
55 | "dist/*.map",
56 | "src/**/*"
57 | ],
58 | "bundlesize": [
59 | {
60 | "path": "./dist/index.min.js",
61 | "maxSize": "10 kB"
62 | }
63 | ]
64 | }
65 |
--------------------------------------------------------------------------------
/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | ---
4 |
5 | ## [](#header-2)Basic Example
6 |
7 | Includes all available actions and default height.
8 |
9 | ```js
10 | const editor = new Editor({
11 | target: document.getElementById('editor1')
12 | })
13 | ```
14 |
15 |
16 |
17 |
18 | ## [](#header-2)Custom action example
19 |
20 | Example of custom _**copy**_ action.
21 |
22 | ```js
23 | const editor2 = new Editor({
24 | target: document.getElementById('editor2'),
25 | data: {
26 | actions: [
27 | 'b', 'i', 'u', 'strike', 'h1', 'h2', 'p',
28 | {
29 | name: 'copy',
30 | icon: '📋',
31 | title: 'Copy',
32 | result: () => {
33 | const selection = window.getSelection();
34 | if (!selection.toString().length) {
35 | const range = document.createRange();
36 | range.selectNodeContents(editor2.refs.editor);
37 | selection.removeAllRanges();
38 | selection.addRange(range);
39 | }
40 | editor2.exec('copy');
41 | }
42 | }
43 | ],
44 | html: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean a odio neque. Duis ac laoreet lacus.',
45 | height: '150px'
46 | }
47 | })
48 | ```
49 |
50 |
51 |
52 |
53 |
54 | ## [](#header-2)Example using _**blur**_ event
55 |
56 | You can use editor _blur_ event to inline edit text.
57 |
58 | ```html
59 |
60 | Edit this line of text
61 |
62 | ```
63 |
64 | ```js
65 | let inlineEditor;
66 | const inlineEdit = document.getElementById('inlineEdit');
67 | inlineEdit.addEventListener('click', showEditor);
68 |
69 | function showEditor() {
70 | let html = inlineEdit.innerHTML;
71 | inlineEdit.innerHTML = '';
72 | inlineEditor = new Editor({
73 | target: inlineEdit,
74 | data: {
75 | actions: ['b', 'i', 'u', 'strike', 'removeFormat'],
76 | height: 'auto',
77 | html: html
78 | }
79 | });
80 |
81 | inlineEdit.removeEventListener('click', showEditor);
82 |
83 | inlineEditor.on('blur', () => {
84 | html = inlineEditor.getHtml();
85 | inlineEditor.destroy();
86 | inlineEdit.innerHTML = html;
87 | inlineEdit.addEventListener('click', showEditor);
88 | });
89 | }
90 | ```
91 |
92 |
93 |
94 | Edit this line of text
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/src/helpers/EditorColorPicker.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {#each btns as btn}
5 |
6 | {/each}
7 |
8 |
9 |
10 |
34 |
35 |
96 |
--------------------------------------------------------------------------------
/_sass/fonts.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Noto Sans';
3 | font-weight: 400;
4 | font-style: normal;
5 | src: url('../fonts/Noto-Sans-regular/Noto-Sans-regular.eot');
6 | src: url('../fonts/Noto-Sans-regular/Noto-Sans-regular.eot?#iefix') format('embedded-opentype'),
7 | local('Noto Sans'),
8 | local('Noto-Sans-regular'),
9 | url('../fonts/Noto-Sans-regular/Noto-Sans-regular.woff2') format('woff2'),
10 | url('../fonts/Noto-Sans-regular/Noto-Sans-regular.woff') format('woff'),
11 | url('../fonts/Noto-Sans-regular/Noto-Sans-regular.ttf') format('truetype'),
12 | url('../fonts/Noto-Sans-regular/Noto-Sans-regular.svg#NotoSans') format('svg');
13 | }
14 |
15 | @font-face {
16 | font-family: 'Noto Sans';
17 | font-weight: 700;
18 | font-style: normal;
19 | src: url('../fonts/Noto-Sans-700/Noto-Sans-700.eot');
20 | src: url('../fonts/Noto-Sans-700/Noto-Sans-700.eot?#iefix') format('embedded-opentype'),
21 | local('Noto Sans Bold'),
22 | local('Noto-Sans-700'),
23 | url('../fonts/Noto-Sans-700/Noto-Sans-700.woff2') format('woff2'),
24 | url('../fonts/Noto-Sans-700/Noto-Sans-700.woff') format('woff'),
25 | url('../fonts/Noto-Sans-700/Noto-Sans-700.ttf') format('truetype'),
26 | url('../fonts/Noto-Sans-700/Noto-Sans-700.svg#NotoSans') format('svg');
27 | }
28 |
29 | @font-face {
30 | font-family: 'Noto Sans';
31 | font-weight: 400;
32 | font-style: italic;
33 | src: url('../fonts/Noto-Sans-italic/Noto-Sans-italic.eot');
34 | src: url('../fonts/Noto-Sans-italic/Noto-Sans-italic.eot?#iefix') format('embedded-opentype'),
35 | local('Noto Sans Italic'),
36 | local('Noto-Sans-italic'),
37 | url('../fonts/Noto-Sans-italic/Noto-Sans-italic.woff2') format('woff2'),
38 | url('../fonts/Noto-Sans-italic/Noto-Sans-italic.woff') format('woff'),
39 | url('../fonts/Noto-Sans-italic/Noto-Sans-italic.ttf') format('truetype'),
40 | url('../fonts/Noto-Sans-italic/Noto-Sans-italic.svg#NotoSans') format('svg');
41 | }
42 |
43 | @font-face {
44 | font-family: 'Noto Sans';
45 | font-weight: 700;
46 | font-style: italic;
47 | src: url('../fonts/Noto-Sans-700italic/Noto-Sans-700italic.eot');
48 | src: url('../fonts/Noto-Sans-700italic/Noto-Sans-700italic.eot?#iefix') format('embedded-opentype'),
49 | local('Noto Sans Bold Italic'),
50 | local('Noto-Sans-700italic'),
51 | url('../fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff2') format('woff2'),
52 | url('../fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff') format('woff'),
53 | url('../fonts/Noto-Sans-700italic/Noto-Sans-700italic.ttf') format('truetype'),
54 | url('../fonts/Noto-Sans-700italic/Noto-Sans-700italic.svg#NotoSans') format('svg');
55 | }
56 |
--------------------------------------------------------------------------------
/_layouts/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {% seo %}
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
37 |
38 |
39 | {{ content }}
40 |
41 |
42 |
48 |
49 |
50 |
51 |
52 |
53 | {% if site.google_analytics %}
54 |
63 | {% endif %}
64 |
65 |
--------------------------------------------------------------------------------
/_sass/rouge-github.scss:
--------------------------------------------------------------------------------
1 | .highlight table td { padding: 5px; }
2 | .highlight table pre { margin: 0; }
3 | .highlight .cm {
4 | color: #999988;
5 | font-style: italic;
6 | }
7 | .highlight .cp {
8 | color: #999999;
9 | font-weight: bold;
10 | }
11 | .highlight .c1 {
12 | color: #999988;
13 | font-style: italic;
14 | }
15 | .highlight .cs {
16 | color: #999999;
17 | font-weight: bold;
18 | font-style: italic;
19 | }
20 | .highlight .c, .highlight .cd {
21 | color: #999988;
22 | font-style: italic;
23 | }
24 | .highlight .err {
25 | color: #a61717;
26 | background-color: #e3d2d2;
27 | }
28 | .highlight .gd {
29 | color: #000000;
30 | background-color: #ffdddd;
31 | }
32 | .highlight .ge {
33 | color: #000000;
34 | font-style: italic;
35 | }
36 | .highlight .gr {
37 | color: #aa0000;
38 | }
39 | .highlight .gh {
40 | color: #999999;
41 | }
42 | .highlight .gi {
43 | color: #000000;
44 | background-color: #ddffdd;
45 | }
46 | .highlight .go {
47 | color: #888888;
48 | }
49 | .highlight .gp {
50 | color: #555555;
51 | }
52 | .highlight .gs {
53 | font-weight: bold;
54 | }
55 | .highlight .gu {
56 | color: #aaaaaa;
57 | }
58 | .highlight .gt {
59 | color: #aa0000;
60 | }
61 | .highlight .kc {
62 | color: #000000;
63 | font-weight: bold;
64 | }
65 | .highlight .kd {
66 | color: #000000;
67 | font-weight: bold;
68 | }
69 | .highlight .kn {
70 | color: #000000;
71 | font-weight: bold;
72 | }
73 | .highlight .kp {
74 | color: #000000;
75 | font-weight: bold;
76 | }
77 | .highlight .kr {
78 | color: #000000;
79 | font-weight: bold;
80 | }
81 | .highlight .kt {
82 | color: #445588;
83 | font-weight: bold;
84 | }
85 | .highlight .k, .highlight .kv {
86 | color: #000000;
87 | font-weight: bold;
88 | }
89 | .highlight .mf {
90 | color: #009999;
91 | }
92 | .highlight .mh {
93 | color: #009999;
94 | }
95 | .highlight .il {
96 | color: #009999;
97 | }
98 | .highlight .mi {
99 | color: #009999;
100 | }
101 | .highlight .mo {
102 | color: #009999;
103 | }
104 | .highlight .m, .highlight .mb, .highlight .mx {
105 | color: #009999;
106 | }
107 | .highlight .sb {
108 | color: #d14;
109 | }
110 | .highlight .sc {
111 | color: #d14;
112 | }
113 | .highlight .sd {
114 | color: #d14;
115 | }
116 | .highlight .s2 {
117 | color: #d14;
118 | }
119 | .highlight .se {
120 | color: #d14;
121 | }
122 | .highlight .sh {
123 | color: #d14;
124 | }
125 | .highlight .si {
126 | color: #d14;
127 | }
128 | .highlight .sx {
129 | color: #d14;
130 | }
131 | .highlight .sr {
132 | color: #009926;
133 | }
134 | .highlight .s1 {
135 | color: #d14;
136 | }
137 | .highlight .ss {
138 | color: #990073;
139 | }
140 | .highlight .s {
141 | color: #d14;
142 | }
143 | .highlight .na {
144 | color: #008080;
145 | }
146 | .highlight .bp {
147 | color: #999999;
148 | }
149 | .highlight .nb {
150 | color: #0086B3;
151 | }
152 | .highlight .nc {
153 | color: #445588;
154 | font-weight: bold;
155 | }
156 | .highlight .no {
157 | color: #008080;
158 | }
159 | .highlight .nd {
160 | color: #3c5d5d;
161 | font-weight: bold;
162 | }
163 | .highlight .ni {
164 | color: #800080;
165 | }
166 | .highlight .ne {
167 | color: #990000;
168 | font-weight: bold;
169 | }
170 | .highlight .nf {
171 | color: #990000;
172 | font-weight: bold;
173 | }
174 | .highlight .nl {
175 | color: #990000;
176 | font-weight: bold;
177 | }
178 | .highlight .nn {
179 | color: #555555;
180 | }
181 | .highlight .nt {
182 | color: #000080;
183 | }
184 | .highlight .vc {
185 | color: #008080;
186 | }
187 | .highlight .vg {
188 | color: #008080;
189 | }
190 | .highlight .vi {
191 | color: #008080;
192 | }
193 | .highlight .nv {
194 | color: #008080;
195 | }
196 | .highlight .ow {
197 | color: #000000;
198 | font-weight: bold;
199 | }
200 | .highlight .o {
201 | color: #000000;
202 | font-weight: bold;
203 | }
204 | .highlight .w {
205 | color: #bbbbbb;
206 | }
207 | .highlight {
208 | background-color: #f8f8f8;
209 | }
210 |
--------------------------------------------------------------------------------
/_sass/jekyll-theme-minimal.scss:
--------------------------------------------------------------------------------
1 | @import "fonts";
2 | @import "rouge-github";
3 |
4 | body {
5 | background-color: #fff;
6 | padding:50px;
7 | font: 14px/1.5 "Noto Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
8 | color:#727272;
9 | font-weight:400;
10 | }
11 |
12 | h1, h2, h3, h4, h5, h6 {
13 | color:#222;
14 | margin:0 0 20px;
15 | }
16 |
17 | p, ul, ol, table, pre, dl {
18 | margin:0 0 20px;
19 | }
20 |
21 | h1, h2, h3 {
22 | line-height:1.1;
23 | }
24 |
25 | h1 {
26 | font-size:28px;
27 | }
28 |
29 | h2 {
30 | color:#393939;
31 | }
32 |
33 | h3, h4, h5, h6 {
34 | color:#494949;
35 | }
36 |
37 | a {
38 | color:#267CB9;
39 | text-decoration:none;
40 | }
41 |
42 | a:hover, a:focus {
43 | color:#069;
44 | font-weight: bold;
45 | }
46 |
47 | a small {
48 | font-size:11px;
49 | color:#777;
50 | margin-top:-0.3em;
51 | display:block;
52 | }
53 |
54 | a:hover small {
55 | color:#777;
56 | }
57 |
58 | .wrapper {
59 | width:860px;
60 | margin:0 auto;
61 | }
62 |
63 | blockquote {
64 | border-left:1px solid #e5e5e5;
65 | margin:0;
66 | padding:0 0 0 20px;
67 | font-style:italic;
68 | }
69 |
70 | code, pre {
71 | font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, Consolas, Liberation Mono, DejaVu Sans Mono, Courier New, monospace;
72 | color:#333;
73 | font-size:12px;
74 | }
75 |
76 | pre {
77 | padding:8px 15px;
78 | background: #f8f8f8;
79 | border-radius:5px;
80 | border:1px solid #e5e5e5;
81 | overflow-x: auto;
82 | }
83 |
84 | table {
85 | width:100%;
86 | border-collapse:collapse;
87 | }
88 |
89 | th, td {
90 | text-align:left;
91 | padding:5px 10px;
92 | border-bottom:1px solid #e5e5e5;
93 | }
94 |
95 | dt {
96 | color:#444;
97 | font-weight:700;
98 | }
99 |
100 | th {
101 | color:#444;
102 | }
103 |
104 | img {
105 | max-width:100%;
106 | }
107 |
108 | header {
109 | width:270px;
110 | float:left;
111 | position:fixed;
112 | -webkit-font-smoothing:subpixel-antialiased;
113 | }
114 |
115 | header ul {
116 | list-style:none;
117 | height:40px;
118 | padding:0;
119 | background: #f4f4f4;
120 | border-radius:5px;
121 | border:1px solid #e0e0e0;
122 | width:270px;
123 | }
124 |
125 | header li {
126 | width:89px;
127 | float:left;
128 | border-right:1px solid #e0e0e0;
129 | height:40px;
130 | }
131 |
132 | header li:first-child a {
133 | border-radius:5px 0 0 5px;
134 | }
135 |
136 | header li:last-child a {
137 | border-radius:0 5px 5px 0;
138 | }
139 |
140 | header ul a {
141 | line-height:1;
142 | font-size:11px;
143 | color:#676767;
144 | display:block;
145 | text-align:center;
146 | padding-top:6px;
147 | height:34px;
148 | }
149 |
150 | header ul a:hover, header ul a:focus {
151 | color:#675C5C;
152 | font-weight:bold;
153 | }
154 |
155 | header ul a:active {
156 | background-color:#f0f0f0;
157 | }
158 |
159 | strong {
160 | color:#222;
161 | font-weight:700;
162 | }
163 |
164 | header ul li + li + li {
165 | border-right:none;
166 | width:89px;
167 | }
168 |
169 | header ul a strong {
170 | font-size:14px;
171 | display:block;
172 | color:#222;
173 | }
174 |
175 | section {
176 | width:500px;
177 | float:right;
178 | padding-bottom:50px;
179 | }
180 |
181 | small {
182 | font-size:11px;
183 | }
184 |
185 | hr {
186 | border:0;
187 | background:#e5e5e5;
188 | height:1px;
189 | margin:0 0 20px;
190 | }
191 |
192 | footer {
193 | width:270px;
194 | float:left;
195 | position:fixed;
196 | bottom:50px;
197 | -webkit-font-smoothing:subpixel-antialiased;
198 | }
199 |
200 | @media print, screen and (max-width: 960px) {
201 |
202 | div.wrapper {
203 | width:auto;
204 | margin:0;
205 | }
206 |
207 | header, section, footer {
208 | float:none;
209 | position:static;
210 | width:auto;
211 | }
212 |
213 | header {
214 | padding-right:320px;
215 | }
216 |
217 | section {
218 | border:1px solid #e5e5e5;
219 | border-width:1px 0;
220 | padding:20px 0;
221 | margin:0 0 20px;
222 | }
223 |
224 | header a small {
225 | display:inline;
226 | }
227 |
228 | header ul {
229 | position:absolute;
230 | right:50px;
231 | top:52px;
232 | }
233 | }
234 |
235 | @media print, screen and (max-width: 720px) {
236 | body {
237 | word-wrap:break-word;
238 | }
239 |
240 | header {
241 | padding:0;
242 | }
243 |
244 | header ul, header p.view {
245 | position:static;
246 | }
247 |
248 | pre, code {
249 | word-wrap:normal;
250 | }
251 | }
252 |
253 | @media print, screen and (max-width: 480px) {
254 | body {
255 | padding:15px;
256 | }
257 |
258 | header ul {
259 | width:99%;
260 | }
261 |
262 | header li, header ul li + li + li {
263 | width:33%;
264 | }
265 | }
266 |
267 | @media print {
268 | body {
269 | padding:0.4in;
270 | font-size:12pt;
271 | color:#444;
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/src/helpers/EditorModal.svelte:
--------------------------------------------------------------------------------
1 |
2 | {#if show}
3 |
4 |
5 |
6 | {title}
7 |
20 |
21 |
22 | {/if}
23 |
24 |
70 |
71 |
210 |
--------------------------------------------------------------------------------
/src/helpers/util.js:
--------------------------------------------------------------------------------
1 | let t = {};
2 |
3 | export const exec = (command, value = null) => {
4 | document.execCommand(command, false, value)
5 | }
6 |
7 | export const getTagsRecursive = (element, tags) => {
8 | tags = tags || (element && element.tagName ? [element.tagName] : []);
9 |
10 | if (element && element.parentNode) {
11 | element = element.parentNode;
12 | } else {
13 | return tags;
14 | }
15 |
16 | const tag = element.tagName;
17 | if (element.style && element.getAttribute) {
18 | [element.style.textAlign || element.getAttribute('align'), element.style.color || tag === 'FONT' && 'forecolor', element.style.backgroundColor && 'backcolor']
19 | .filter((item) => item)
20 | .forEach((item) => tags.push(item));
21 | }
22 |
23 | if (tag === 'DIV') {
24 | return tags;
25 | }
26 |
27 | tags.push(tag);
28 |
29 | return getTagsRecursive(element, tags).filter((_tag) => _tag != null);
30 | }
31 |
32 | export const saveRange = (editor) => {
33 | const documentSelection = document.getSelection();
34 |
35 | t.range = null;
36 |
37 | if (documentSelection.rangeCount) {
38 | let savedRange = t.range = documentSelection.getRangeAt(0);
39 | let range = document.createRange();
40 | let rangeStart;
41 | range.selectNodeContents(editor);
42 | range.setEnd(savedRange.startContainer, savedRange.startOffset);
43 | rangeStart = (range + '').length;
44 | t.metaRange = {
45 | start: rangeStart,
46 | end: rangeStart + (savedRange + '').length
47 | };
48 | }
49 | }
50 | export const restoreRange = (editor) => {
51 | let metaRange = t.metaRange;
52 | let savedRange = t.range;
53 | let documentSelection = document.getSelection();
54 | let range;
55 |
56 | if (!savedRange) {
57 | return;
58 | }
59 |
60 | if (metaRange && metaRange.start !== metaRange.end) { // Algorithm from http://jsfiddle.net/WeWy7/3/
61 | let charIndex = 0,
62 | nodeStack = [editor],
63 | node,
64 | foundStart = false,
65 | stop = false;
66 |
67 | range = document.createRange();
68 |
69 | while (!stop && (node = nodeStack.pop())) {
70 | if (node.nodeType === 3) {
71 | let nextCharIndex = charIndex + node.length;
72 | if (!foundStart && metaRange.start >= charIndex && metaRange.start <= nextCharIndex) {
73 | range.setStart(node, metaRange.start - charIndex);
74 | foundStart = true;
75 | }
76 | if (foundStart && metaRange.end >= charIndex && metaRange.end <= nextCharIndex) {
77 | range.setEnd(node, metaRange.end - charIndex);
78 | stop = true;
79 | }
80 | charIndex = nextCharIndex;
81 | } else {
82 | let cn = node.childNodes;
83 | let i = cn.length;
84 |
85 | while (i > 0) {
86 | i -= 1;
87 | nodeStack.push(cn[i]);
88 | }
89 | }
90 | }
91 | }
92 |
93 | documentSelection.removeAllRanges();
94 | documentSelection.addRange(range || savedRange);
95 | }
96 |
97 | export const cleanHtml = (input) => {
98 | const html = input.match(/(.*?)/);
99 | let output = html && html[1] || input;
100 | output = output
101 | .replace(/\r?\n|\r/g, ' ')
102 | .replace(//g, '')
103 | .replace(new RegExp('<(/)*(meta|link|span|\\?xml:|st1:|o:|font|w:sdt)(.*?)>', 'gi'), '')
104 | .replace(/(.*?)/gi, '')
105 | .replace(/style="[^"]*"/gi, '')
106 | .replace(/style='[^']*'/gi, '')
107 | .replace(/ /gi, ' ')
108 | .replace(/>(\s+)<')
109 | .replace(/class="[^"]*"/gi, '')
110 | .replace(/class='[^']*'/gi, '')
111 | .replace(/<[^/].*?>/g, i => i.split(/[ >]/g)[0] + '>')
112 | .trim()
113 |
114 | output = removeBadTags(output);
115 | return output;
116 | }
117 |
118 | export const unwrap = (wrapper) => {
119 | const docFrag = document.createDocumentFragment();
120 | while (wrapper.firstChild) {
121 | const child = wrapper.removeChild(wrapper.firstChild);
122 | docFrag.appendChild(child);
123 | }
124 |
125 | // replace wrapper with document fragment
126 | wrapper.parentNode.replaceChild(docFrag, wrapper);
127 | }
128 |
129 | export const removeBlockTagsRecursive = (elements, tagsToRemove) => {
130 | Array.from(elements).forEach((item) => {
131 | if (tagsToRemove.some((tag) => tag === item.tagName.toLowerCase())) {
132 | if (item.children.length) {
133 | removeBlockTagsRecursive(item.children, tagsToRemove);
134 | }
135 | unwrap(item);
136 | }
137 | });
138 | }
139 |
140 | export const getActionBtns = (actions) => {
141 | return Object.keys(actions).map((action) => actions[action]);
142 | }
143 |
144 | export const getNewActionObj = (actions, userActions = []) => {
145 | if (userActions && userActions.length) {
146 | const newActions = {};
147 | userActions.forEach((action) => {
148 | if (typeof action === 'string') {
149 | newActions[action] = Object.assign({}, actions[action]);
150 | } else if (actions[action.name]) {
151 | newActions[action.name] = Object.assign(actions[action.name], action);
152 | } else {
153 | newActions[action.name] = Object.assign({}, action);
154 | }
155 | });
156 |
157 | return newActions;
158 | } else {
159 | return actions;
160 | }
161 | }
162 |
163 | export const removeBadTags = (html) => {
164 | ['style', 'script', 'applet', 'embed', 'noframes', 'noscript'].forEach((badTag) => {
165 | html = html.replace(new RegExp(`<${badTag}.*?${badTag}(.*?)>`, 'gi'), '')
166 | });
167 |
168 | return html;
169 | }
170 |
171 | export const isEditorClick = (target, editorWrapper) => {
172 | if (target === editorWrapper) {
173 | return true;
174 | }
175 | if (target.parentElement) {
176 | return isEditorClick(target.parentElement, editorWrapper);
177 | }
178 | return false;
179 | }
180 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Lightweight text editor
2 |
3 | Built with svelte (no external dependencies)
4 |
5 | #### File size (bundle includes css, html and js)
6 | * min: 30kb
7 | * gzip: 10kb
8 |
9 | ## Installation
10 |
11 | #### npm:
12 |
13 | ```bash
14 | npm install --save cl-editor
15 | ```
16 |
17 | #### HTML:
18 |
19 | ```html
20 |
21 | ...
22 |
23 |
24 | ...
25 |
26 | ...
27 |
28 | ```
29 |
30 | ## Usage
31 | ```js
32 | import Editor from 'cl-editor';
33 | // or
34 | const Editor = require('cl-editor');
35 | ```
36 | ```js
37 | // Initialize editor
38 | const editor = new Editor({
39 | // required
40 | target: document.getElementById('editor'),
41 | // optional
42 | props: {
43 | // string if overwriting, object if customizing/creating
44 | // available actions:
45 | // 'viewHtml', 'undo', 'redo', 'b', 'i', 'u', 'strike', 'sup', 'sub', 'h1', 'h2', 'p', 'blockquote',
46 | // 'ol', 'ul', 'hr', 'left', 'right', 'center', 'justify', 'a', 'image', 'forecolor', 'backcolor', 'removeFormat'
47 | actions: [
48 | 'b', 'i', 'u', 'strike', 'ul', 'ol',
49 | {
50 | name: 'copy', // required
51 | icon: 'C', // string or html string (ex. )
52 | title: 'Copy',
53 | result: () => {
54 | // copy current selection or whole editor content
55 | const selection = window.getSelection();
56 | if (!selection.toString().length) {
57 | const range = document.createRange();
58 | range.selectNodeContents(editor.refs.editor);
59 | selection.removeAllRanges();
60 | selection.addRange(range);
61 | }
62 | editor.exec('copy');
63 | }
64 | },
65 | 'h1', 'h2', 'p'
66 | ],
67 | // default 300px
68 | height: '300px',
69 | // initial html
70 | html: '',
71 | // remove format action clears formatting, but also removes some html tags.
72 | // you can specify which tags you want to be removed.
73 | removeFormatTags: ['h1', 'h2', 'blackquote'] // default
74 | }
75 | })
76 | ```
77 |
78 | ### API
79 | ```js
80 | // Methods
81 | editor.exec(cmd: string, value?: string) // execute document command (document.executeCommand(cmd, false, value))
82 | editor.getHtml(sanitize?: boolean) // returns html string from editor. if passed true as argument, html will be sanitized before return
83 | editor.getText() // returns text string from editor
84 | editor.setHtml(html: string, sanitize?: boolean) // sets html for editor. if second argument is true, html will be sanitized
85 | editor.saveRange() // saves current editor cursor position or user selection
86 | editor.restoreRange() // restores cursor position or user selection
87 | // saveRange and restoreRange are useful when making custom actions
88 | // that demands that focus is shifted from editor to, for example, modal window.
89 | ```
90 | * For list of available _**exec**_ command visit [https://codepen.io/netsi1964/pen/QbLLG](https://codepen.io/netsi1964/pen/QbLLGW)
91 | ```js
92 | // Events
93 | editor.$on('change', (event) => console.log(event)) // on every keyup event
94 | editor.$on('blur', (event) => console.log(event)) // on editor blur event
95 | ```
96 | ```js
97 | // Props
98 | editor.refs. // references to editor, raw (textarea), modal and colorPicker HTMLElements
99 | ```
100 |
101 | #### Actions
102 |
103 | The `actions` prop lists predefined actions (and/or adds new actions) to be shown in the toolbar.
104 | If the prop is not set, all `actions` defined and exported in [actions.js](src/helpers/actions.js) are made available, in the order in which they are defined.
105 | To limit or change the order of predefined actions shown, set it by passing an array of names of actions defined, eg.:
106 | ```js
107 | actions={["b", "i", "u", "h2", "ul", "left", "center", "justify", "forecolor"]}
108 | ```
109 | The editor looks up to see if name is already defined, and adds it to the toolbar if it is.
110 |
111 | You can add a custom action by inserting it in the array, like how "copy" is defined in example above. Take a look at `actions.js` for more examples.
112 |
113 |
114 | ### Usage in Svelte
115 |
116 | It is easier to import and work directly from the source if you are using Svelte. You can handle `change` events via `on:change`.
117 |
118 | ```jsx
119 |
126 |
127 | {@html html}
128 | html = evt.detail}/>
129 | ```
130 |
131 | ### Example of customising the color picker palette
132 |
133 | ```jsx
134 |
146 |
147 | {@html html}
148 | html = evt.detail}/>
149 | ```
150 |
151 | To limit or define the tools shown in the toolbar, pass in an `actions` prop.
152 |
153 | To easily get the editor content DOM element, pass an `contentId` prop, eg. `contentId='notes-content'`.
154 |
155 | This is useful if you want to listen to resize of the editor and respond accordingly.
156 |
157 | To do so, first enable resize on the editor:
158 |
159 | ```css
160 | .cl-content {
161 | resize: both;
162 | }
163 | ```
164 |
165 | Now observe the resize:
166 |
167 | ```jsx
168 |
176 |
177 |
178 | ```
179 |
180 | ### Run demo
181 | ```bash
182 | git clone https://github.com/nenadpnc/cl-text-editor.git cl-editor
183 | cd cl-editor
184 | npm i
185 | npm run dev
186 | ```
187 |
188 | ## References
189 | This library is inspired by these open source repos:
190 | - [Alex-D/Trumbowyg](https://github.com/Alex-D/Trumbowyg)
191 | - [jaredreich/pell](https://github.com/jaredreich/pell)
192 |
193 | ## Licence
194 |
195 | MIT License
196 |
--------------------------------------------------------------------------------
/src/Editor.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | {#each $state.actionBtns as action}
9 |
16 | {/each}
17 |
18 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
158 |
159 |
219 |
--------------------------------------------------------------------------------
/src/helpers/actions.js:
--------------------------------------------------------------------------------
1 | import {
2 | exec,
3 | removeBlockTagsRecursive,
4 | getActionBtns,
5 | saveRange,
6 | restoreRange
7 | } from "./util";
8 |
9 | import { get } from "svelte/store";
10 |
11 | const linkSvg =
12 | '';
13 | const unlinkSvg =
14 | '';
15 |
16 | export default {
17 | viewHtml: {
18 | icon:
19 | '',
20 | title: "View HTML",
21 | result: function() {
22 | let refs = get(this.references);
23 | let actionObj = get(this.state).actionObj;
24 | let helper = get(this.helper);
25 |
26 | helper.showEditor = !helper.showEditor;
27 | refs.editor.style.display = helper.showEditor ? "block" : "none";
28 | refs.raw.style.display = helper.showEditor ? "none" : "block";
29 | if (helper.showEditor) {
30 | refs.editor.innerHTML = refs.raw.value;
31 | } else {
32 | refs.raw.value = refs.editor.innerHTML;
33 | }
34 | setTimeout(() => {
35 | Object.keys(actionObj).forEach(
36 | action => (actionObj[action].disabled = !helper.showEditor)
37 | );
38 | actionObj.viewHtml.disabled = false;
39 | actionObj.viewHtml.active = !helper.showEditor;
40 |
41 | this.state.update(state => {
42 | state.actionBtns = getActionBtns(actionObj);
43 | state.actionObj = actionObj;
44 | return state;
45 | });
46 | });
47 | }
48 | },
49 | undo: {
50 | icon:
51 | '',
52 | title: "Undo",
53 | result: () => exec("undo")
54 | },
55 | redo: {
56 | icon:
57 | '',
58 | title: "Redo",
59 | result: () => exec("redo")
60 | },
61 | b: {
62 | icon: "B",
63 | title: "Bold",
64 | result: () => exec("bold")
65 | },
66 | i: {
67 | icon: "I",
68 | title: "Italic",
69 | result: () => exec("italic")
70 | },
71 | u: {
72 | icon: "U",
73 | title: "Underline",
74 | result: () => exec("underline")
75 | },
76 | strike: {
77 | icon: "S",
78 | title: "Strike-through",
79 | result: () => exec("strikeThrough")
80 | },
81 | sup: {
82 | icon: "A2",
83 | title: "Superscript",
84 | result: () => exec("superscript")
85 | },
86 | sub: {
87 | icon: "A2",
88 | title: "Subscript",
89 | result: () => exec("subscript")
90 | },
91 | h1: {
92 | icon: "H1",
93 | title: "Heading 1",
94 | result: () => exec("formatBlock", "")
95 | },
96 | h2: {
97 | icon: "H2",
98 | title: "Heading 2",
99 | result: () => exec("formatBlock", "")
100 | },
101 | p: {
102 | icon: "¶",
103 | title: "Paragraph",
104 | result: () => exec("formatBlock", "
")
105 | },
106 | blockquote: {
107 | icon: "“ ”",
108 | title: "Quote",
109 | result: () => exec("formatBlock", "
")
110 | },
111 | ol: {
112 | icon:
113 | '',
114 | title: "Ordered List",
115 | result: () => exec("insertOrderedList")
116 | },
117 | ul: {
118 | icon:
119 | '',
120 | title: "Unordered List",
121 | result: () => exec("insertUnorderedList")
122 | },
123 | hr: {
124 | icon: "―",
125 | title: "Horizontal Line",
126 | result: () => exec("insertHorizontalRule")
127 | },
128 | left: {
129 | icon:
130 | '',
131 | title: "Justify left",
132 | result: () => exec("justifyLeft")
133 | },
134 | right: {
135 | icon:
136 | '',
137 | title: "Justify right",
138 | result: () => exec("justifyRight")
139 | },
140 | center: {
141 | icon:
142 | '',
143 | title: "Justify center",
144 | result: () => exec("justifyCenter")
145 | },
146 | justify: {
147 | icon:
148 | '',
149 | title: "Justify full",
150 | result: () => exec("justifyFull")
151 | },
152 | a: {
153 | icon: linkSvg,
154 | title: "Insert link",
155 | result: function() {
156 | const actionObj = get(this.state).actionObj;
157 | const refs = get(this.references);
158 |
159 | if (actionObj.a.active) {
160 | const selection = window.getSelection();
161 | const range = document.createRange();
162 | range.selectNodeContents(document.getSelection().focusNode);
163 | selection.removeAllRanges();
164 | selection.addRange(range);
165 | exec("unlink");
166 | actionObj.a.title = "Insert link";
167 | actionObj.a.icon = linkSvg;
168 | this.state.update(state => {
169 | state.actionBtn = getActionBtns(actionObj);
170 | state.actionObj = actionObj;
171 | return state;
172 | });
173 | } else {
174 | saveRange(refs.editor);
175 | refs.modal.$set({
176 | show: true,
177 | event: "linkUrl",
178 | title: "Insert link",
179 | label: "Url"
180 | });
181 | if (!get(this.helper).link) {
182 | this.helper.update(state => {
183 | state.link = true;
184 | return state;
185 | });
186 | refs.modal.$on("linkUrl", event => {
187 | restoreRange(refs.editor);
188 | exec("createLink", event.detail);
189 | actionObj.a.title = "Unlink";
190 | actionObj.a.icon = unlinkSvg;
191 |
192 | this.state.update(state => {
193 | state.actionBtn = getActionBtns(actionObj);
194 | state.actionObj = actionObj;
195 | return state;
196 | });
197 | });
198 | }
199 | }
200 | }
201 | },
202 | image: {
203 | icon:
204 | '',
205 | title: "Image",
206 | result: function() {
207 | const refs = get(this.references);
208 | saveRange(refs.editor);
209 | refs.modal.$set({
210 | show: true,
211 | event: "imageUrl",
212 | title: "Insert image",
213 | label: "Url"
214 | });
215 | if (!get(this.helper).image) {
216 | this.helper.update(state => {
217 | state.image = true;
218 | return state;
219 | });
220 | refs.modal.$on("imageUrl", event => {
221 | restoreRange(refs.editor);
222 | exec("insertImage", event.detail);
223 | });
224 | }
225 | }
226 | },
227 | forecolor: {
228 | icon:
229 | '',
230 | title: "Text color",
231 | colorPicker: true,
232 | result: function() {
233 | showColorPicker.call(this, "foreColor");
234 | }
235 | },
236 | backcolor: {
237 | icon:
238 | '',
239 | title: "Background color",
240 | colorPicker: true,
241 | result: function() {
242 | showColorPicker.call(this, "backColor");
243 | }
244 | },
245 | removeFormat: {
246 | icon:
247 | '',
248 | title: "Remove format",
249 | result: function() {
250 | const refs = get(this.references);
251 | const selection = window.getSelection();
252 | if (!selection.toString().length) {
253 | removeBlockTagsRecursive(
254 | refs.editor.children,
255 | this.removeFormatTags
256 | );
257 | const range = document.createRange();
258 | range.selectNodeContents(refs.editor);
259 | selection.removeAllRanges();
260 | selection.addRange(range);
261 | }
262 | exec("removeFormat");
263 | selection.removeAllRanges();
264 | }
265 | }
266 | };
267 |
268 | const showColorPicker = function(cmd) {
269 | const refs = get(this.references);
270 | saveRange(refs.editor);
271 | refs.colorPicker.$set({show: true, event: cmd});
272 | if (!get(this.helper)[cmd]) {
273 | this.helper.update(state => {
274 | state[cmd] = true;
275 | return state;
276 | });
277 | refs.colorPicker.$on(cmd, event => {
278 | let item = event.detail;
279 | if (item.modal) {
280 | refs.modal.$set({
281 | show: true,
282 | event: `${cmd}Changed`,
283 | title: "Text color",
284 | label:
285 | cmd === "foreColor" ? "Text color" : "Background color"
286 | });
287 | const command = cmd;
288 | if (!get(this.helper)[`${command}Modal`]) {
289 | get(this.helper)[`${command}Modal`] = true;
290 | refs.modal.$on(`${command}Changed`, event => {
291 | let color = event.detail;
292 | restoreRange(refs.editor);
293 | exec(command, color);
294 | });
295 | }
296 | } else {
297 | restoreRange(refs.editor);
298 | exec(cmd, item.color);
299 | }
300 | });
301 | }
302 | };
303 |
--------------------------------------------------------------------------------
/dist/index.min.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).clEditor=e()}(this,(function(){"use strict";function t(){}function e(t){return t()}function n(){return Object.create(null)}function o(t){t.forEach(e)}function i(t){return"function"==typeof t}function l(t,e){return t!=t?e==e:t!==e||t&&"object"==typeof t||"function"==typeof t}function r(e,...n){if(null==e)return t;const o=e.subscribe(...n);return o.unsubscribe?()=>o.unsubscribe():o}function s(t){let e;return r(t,(t=>e=t))(),e}function c(t,e,n){t.$$.on_destroy.push(r(e,n))}function a(t,e,n){return t.set(n),e}function d(t,e){t.appendChild(e)}function h(t,e,n){const o=function(t){if(!t)return document;const e=t.getRootNode?t.getRootNode():t.ownerDocument;if(e&&e.host)return e;return t.ownerDocument}(t);if(!o.getElementById(e)){const t=g("style");t.id=e,t.textContent=n,function(t,e){d(t.head||t,e)}(o,t)}}function u(t,e,n){t.insertBefore(e,n||null)}function f(t){t.parentNode.removeChild(t)}function p(t,e){for(let n=0;nt.removeEventListener(e,n,o)}function x(t,e,n){null==n?t.removeAttribute(e):t.getAttribute(e)!==n&&t.setAttribute(e,n)}function y(t,e){e=""+e,t.wholeText!==e&&(t.data=e)}function $(t,e){t.value=null==e?"":e}function w(t,e,n,o){t.style.setProperty(e,n,o?"important":"")}function k(t,e,n){t.classList[n?"add":"remove"](e)}class j{constructor(){this.e=this.n=null}c(t){this.h(t)}m(t,e,n=null){this.e||(this.e=g(e.nodeName),this.t=e,this.c(t)),this.i(n)}h(t){this.e.innerHTML=t,this.n=Array.from(this.e.childNodes)}i(t){for(let e=0;e{const o=t.$$.callbacks[e];if(o){const i=function(t,e,n=!1){const o=document.createEvent("CustomEvent");return o.initCustomEvent(t,n,!1,e),o}(e,n);o.slice().forEach((e=>{e.call(t,i)}))}}}const L=[],C=[],T=[],B=[],F=Promise.resolve();let R=!1;function O(t){T.push(t)}let N=!1;const _=new Set;function A(){if(!N){N=!0;do{for(let t=0;t{I.delete(t),o&&(n&&t.d(1),o())})),t.o(e)}}function q(t){t&&t.c()}function V(t,n,l,r){const{fragment:s,on_mount:c,on_destroy:a,after_update:d}=t.$$;s&&s.m(n,l),r||O((()=>{const n=c.map(e).filter(i);a?a.push(...n):o(n),t.$$.on_mount=[]})),d.forEach(O)}function P(t,e){const n=t.$$;null!==n.fragment&&(o(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function X(t,e){-1===t.$$.dirty[0]&&(L.push(t),R||(R=!0,F.then(A)),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<{const i=o.length?o[0]:n;return u.ctx&&s(u.ctx[t],u.ctx[t]=i)&&(!u.skip_bound&&u.bound[t]&&u.bound[t](i),p&&X(e,t)),n})):[],u.update(),p=!0,o(u.before_update),u.fragment=!!r&&r(u.ctx),i.target){if(i.hydrate){const t=function(t){return Array.from(t.childNodes)}(i.target);u.fragment&&u.fragment.l(t),t.forEach(f)}else u.fragment&&u.fragment.c();i.intro&&S(e.$$.fragment),V(e,i.target,i.anchor,i.customElement),A()}H(h)}class Q{$destroy(){P(this,1),this.$destroy=t}$on(t,e){const n=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return n.push(e),()=>{const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}$set(t){var e;this.$$set&&(e=t,0!==Object.keys(e).length)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}let W={};const K=(t,e=null)=>{document.execCommand(t,!1,e)},G=(t,e)=>{if(e=e||(t&&t.tagName?[t.tagName]:[]),!t||!t.parentNode)return e;const n=(t=t.parentNode).tagName;return t.style&&t.getAttribute&&[t.style.textAlign||t.getAttribute("align"),t.style.color||"FONT"===n&&"forecolor",t.style.backgroundColor&&"backcolor"].filter((t=>t)).forEach((t=>e.push(t))),"DIV"===n?e:(e.push(n),G(t,e).filter((t=>null!=t)))},Y=t=>{const e=document.getSelection();if(W.range=null,e.rangeCount){let n,o=W.range=e.getRangeAt(0),i=document.createRange();i.selectNodeContents(t),i.setEnd(o.startContainer,o.startOffset),n=(i+"").length,W.metaRange={start:n,end:n+(o+"").length}}},Z=t=>{let e,n=W.metaRange,o=W.range,i=document.getSelection();if(o){if(n&&n.start!==n.end){let o,i=0,l=[t],r=!1,s=!1;for(e=document.createRange();!s&&(o=l.pop());)if(3===o.nodeType){let t=i+o.length;!r&&n.start>=i&&n.start<=t&&(e.setStart(o,n.start-i),r=!0),r&&n.end>=i&&n.end<=t&&(e.setEnd(o,n.end-i),s=!0),i=t}else{let t=o.childNodes,e=t.length;for(;e>0;)e-=1,l.push(t[e])}}i.removeAllRanges(),i.addRange(e||o)}},tt=(t,e)=>{Array.from(t).forEach((t=>{e.some((e=>e===t.tagName.toLowerCase()))&&(t.children.length&&tt(t.children,e),(t=>{const e=document.createDocumentFragment();for(;t.firstChild;){const n=t.removeChild(t.firstChild);e.appendChild(n)}t.parentNode.replaceChild(e,t)})(t))}))},et=t=>Object.keys(t).map((e=>t[e])),nt=t=>(["style","script","applet","embed","noframes","noscript"].forEach((e=>{t=t.replace(new RegExp(`<${e}.*?${e}(.*?)>`,"gi"),"")})),t),ot=(t,e)=>t===e||!!t.parentElement&&ot(t.parentElement,e),it=[];function lt(e,n=t){let o;const i=new Set;function r(t){if(l(e,t)&&(e=t,o)){const t=!it.length;for(const t of i)t[1](),it.push(t,e);if(t){for(let t=0;t{i.delete(c),0===i.size&&(o(),o=null)}}}}const rt='';var st={viewHtml:{icon:'',title:"View HTML",result:function(){let t=s(this.references),e=s(this.state).actionObj,n=s(this.helper);n.showEditor=!n.showEditor,t.editor.style.display=n.showEditor?"block":"none",t.raw.style.display=n.showEditor?"none":"block",n.showEditor?t.editor.innerHTML=t.raw.value:t.raw.value=t.editor.innerHTML,setTimeout((()=>{Object.keys(e).forEach((t=>e[t].disabled=!n.showEditor)),e.viewHtml.disabled=!1,e.viewHtml.active=!n.showEditor,this.state.update((t=>(t.actionBtns=et(e),t.actionObj=e,t)))}))}},undo:{icon:'',title:"Undo",result:()=>K("undo")},redo:{icon:'',title:"Redo",result:()=>K("redo")},b:{icon:"B",title:"Bold",result:()=>K("bold")},i:{icon:"I",title:"Italic",result:()=>K("italic")},u:{icon:"U",title:"Underline",result:()=>K("underline")},strike:{icon:"S",title:"Strike-through",result:()=>K("strikeThrough")},sup:{icon:"A2",title:"Superscript",result:()=>K("superscript")},sub:{icon:"A2",title:"Subscript",result:()=>K("subscript")},h1:{icon:"H1",title:"Heading 1",result:()=>K("formatBlock","")},h2:{icon:"H2",title:"Heading 2",result:()=>K("formatBlock","")},p:{icon:"¶",title:"Paragraph",result:()=>K("formatBlock","
")},blockquote:{icon:"“ ”",title:"Quote",result:()=>K("formatBlock","
")},ol:{icon:'',title:"Ordered List",result:()=>K("insertOrderedList")},ul:{icon:'',title:"Unordered List",result:()=>K("insertUnorderedList")},hr:{icon:"―",title:"Horizontal Line",result:()=>K("insertHorizontalRule")},left:{icon:'',title:"Justify left",result:()=>K("justifyLeft")},right:{icon:'',title:"Justify right",result:()=>K("justifyRight")},center:{icon:'',title:"Justify center",result:()=>K("justifyCenter")},justify:{icon:'',title:"Justify full",result:()=>K("justifyFull")},a:{icon:rt,title:"Insert link",result:function(){const t=s(this.state).actionObj,e=s(this.references);if(t.a.active){const e=window.getSelection(),n=document.createRange();n.selectNodeContents(document.getSelection().focusNode),e.removeAllRanges(),e.addRange(n),K("unlink"),t.a.title="Insert link",t.a.icon=rt,this.state.update((e=>(e.actionBtn=et(t),e.actionObj=t,e)))}else Y(e.editor),e.modal.$set({show:!0,event:"linkUrl",title:"Insert link",label:"Url"}),s(this.helper).link||(this.helper.update((t=>(t.link=!0,t))),e.modal.$on("linkUrl",(n=>{Z(e.editor),K("createLink",n.detail),t.a.title="Unlink",t.a.icon='',this.state.update((e=>(e.actionBtn=et(t),e.actionObj=t,e)))})))}},image:{icon:'',title:"Image",result:function(){const t=s(this.references);Y(t.editor),t.modal.$set({show:!0,event:"imageUrl",title:"Insert image",label:"Url"}),s(this.helper).image||(this.helper.update((t=>(t.image=!0,t))),t.modal.$on("imageUrl",(e=>{Z(t.editor),K("insertImage",e.detail)})))}},forecolor:{icon:'',title:"Text color",colorPicker:!0,result:function(){ct.call(this,"foreColor")}},backcolor:{icon:'',title:"Background color",colorPicker:!0,result:function(){ct.call(this,"backColor")}},removeFormat:{icon:'',title:"Remove format",result:function(){const t=s(this.references),e=window.getSelection();if(!e.toString().length){tt(t.editor.children,this.removeFormatTags);const n=document.createRange();n.selectNodeContents(t.editor),e.removeAllRanges(),e.addRange(n)}K("removeFormat"),e.removeAllRanges()}}};const ct=function(t){const e=s(this.references);Y(e.editor),e.colorPicker.$set({show:!0,event:t}),s(this.helper)[t]||(this.helper.update((e=>(e[t]=!0,e))),e.colorPicker.$on(t,(n=>{let o=n.detail;if(o.modal){e.modal.$set({show:!0,event:`${t}Changed`,title:"Text color",label:"foreColor"===t?"Text color":"Background color"});const n=t;s(this.helper)[`${n}Modal`]||(s(this.helper)[`${n}Modal`]=!0,e.modal.$on(`${n}Changed`,(t=>{let o=t.detail;Z(e.editor),K(n,o)})))}else Z(e.editor),K(t,o.color)})))};function at(t){h(t,"svelte-42yfje",'.cl-editor-modal.svelte-42yfje.svelte-42yfje{position:absolute;top:37px;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);max-width:520px;width:100%;height:140px;backface-visibility:hidden;z-index:11}.cl-editor-overlay.svelte-42yfje.svelte-42yfje{position:absolute;background-color:rgba(255,255,255,.5);height:100%;width:100%;left:0;top:0;z-index:10}.modal-box.svelte-42yfje.svelte-42yfje{position:absolute;top:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);max-width:500px;width:calc(100% - 20px);padding-bottom:36px;z-index:1;background-color:#FFF;text-align:center;font-size:14px;box-shadow:rgba(0,0,0,.2) 0 2px 3px;-webkit-backface-visibility:hidden;backface-visibility:hidden}.modal-title.svelte-42yfje.svelte-42yfje{font-size:24px;font-weight:700;margin:0 0 20px;padding:2px 0 4px;display:block;border-bottom:1px solid #EEE;color:#333;background:#fbfcfc}.modal-label.svelte-42yfje.svelte-42yfje{display:block;position:relative;margin:15px 12px;height:29px;line-height:29px;overflow:hidden}.modal-label.svelte-42yfje input.svelte-42yfje{position:absolute;top:0;right:0;height:27px;line-height:25px;border:1px solid #DEDEDE;background:#fff;font-size:14px;max-width:330px;width:70%;padding:0 7px;transition:all 150ms}.modal-label.svelte-42yfje input.svelte-42yfje:focus{outline:none}.input-error.svelte-42yfje input.svelte-42yfje{border:1px solid #e74c3c}.input-info.svelte-42yfje.svelte-42yfje{display:block;text-align:left;height:25px;line-height:25px;transition:all 150ms}.input-info.svelte-42yfje span.svelte-42yfje{display:block;color:#69878f;background-color:#fbfcfc;border:1px solid #DEDEDE;padding:1px 7px;width:150px}.input-error.svelte-42yfje .input-info.svelte-42yfje{margin-top:-29px}.input-error.svelte-42yfje .msg-error.svelte-42yfje{color:#e74c3c}.modal-button.svelte-42yfje.svelte-42yfje{position:absolute;bottom:10px;right:0;text-decoration:none;color:#FFF;display:block;width:100px;height:35px;line-height:33px;margin:0 10px;background-color:#333;border:none;cursor:pointer;font-family:"Lato",Helvetica,Verdana,sans-serif;font-size:16px;transition:all 150ms}.modal-submit.svelte-42yfje.svelte-42yfje{right:110px;background:#2bc06a}.modal-reset.svelte-42yfje.svelte-42yfje{color:#555;background:#e6e6e6}')}function dt(e){let n,l,r,s,c,a,h,p,w,j,z,H,M,E,L,C,T,B,F,R,O,N,_=e[2]&&ht();return{c(){n=g("div"),l=b(),r=g("div"),s=g("div"),c=g("span"),a=v(e[3]),h=b(),p=g("form"),w=g("label"),j=g("input"),H=b(),M=g("span"),E=g("span"),L=v(e[4]),C=b(),_&&_.c(),T=b(),B=g("button"),B.textContent="Confirm",F=b(),R=g("button"),R.textContent="Cancel",x(n,"class","cl-editor-overlay svelte-42yfje"),x(c,"class","modal-title svelte-42yfje"),x(j,"name","text"),x(j,"class","svelte-42yfje"),x(E,"class","svelte-42yfje"),x(M,"class","input-info svelte-42yfje"),x(w,"class","modal-label svelte-42yfje"),k(w,"input-error",e[2]),x(B,"class","modal-button modal-submit svelte-42yfje"),x(B,"type","submit"),x(R,"class","modal-button modal-reset svelte-42yfje"),x(R,"type","reset"),x(s,"class","modal-box svelte-42yfje"),x(r,"class","cl-editor-modal svelte-42yfje")},m(o,f){var g,v;u(o,n,f),u(o,l,f),u(o,r,f),d(r,s),d(s,c),d(c,a),d(s,h),d(s,p),d(p,w),d(w,j),e[11](j),$(j,e[1]),d(w,H),d(w,M),d(M,E),d(E,L),d(M,C),_&&_.m(M,null),d(p,T),d(p,B),d(p,F),d(p,R),O||(N=[m(n,"click",e[8]),m(j,"keyup",e[9]),(v=z=e[6].call(null,j),v&&i(v.destroy)?v.destroy:t),m(j,"input",e[12]),m(R,"click",e[8]),m(p,"submit",(g=e[13],function(t){return t.preventDefault(),g.call(this,t)}))],O=!0)},p(t,e){8&e&&y(a,t[3]),2&e&&j.value!==t[1]&&$(j,t[1]),16&e&&y(L,t[4]),t[2]?_||(_=ht(),_.c(),_.m(M,null)):_&&(_.d(1),_=null),4&e&&k(w,"input-error",t[2])},d(t){t&&f(n),t&&f(l),t&&f(r),e[11](null),_&&_.d(),O=!1,o(N)}}}function ht(t){let e;return{c(){e=g("span"),e.textContent="Required",x(e,"class","msg-error svelte-42yfje")},m(t,n){u(t,e,n)},d(t){t&&f(e)}}}function ut(e){let n,o=e[0]&&dt(e);return{c(){o&&o.c(),n=v("")},m(t,e){o&&o.m(t,e),u(t,n,e)},p(t,[e]){t[0]?o?o.p(t,e):(o=dt(t),o.c(),o.m(n.parentNode,n)):o&&(o.d(1),o=null)},i:t,o:t,d(t){o&&o.d(t),t&&f(n)}}}function ft(t,e,n){let o=new E,{show:i=!1}=e,{text:l=""}=e,{event:r=""}=e,{title:s=""}=e,{label:c=""}=e,{error:a=!1}=e,d={};function h(){l?(o(r,l),u()):(n(2,a=!0),d.text.focus())}function u(){n(0,i=!1),n(1,l=""),n(2,a=!1)}return t.$$set=t=>{"show"in t&&n(0,i=t.show),"text"in t&&n(1,l=t.text),"event"in t&&n(10,r=t.event),"title"in t&&n(3,s=t.title),"label"in t&&n(4,c=t.label),"error"in t&&n(2,a=t.error)},t.$$.update=()=>{33&t.$$.dirty&&i&&setTimeout((()=>{d.text.focus()}))},[i,l,a,s,c,d,t=>{t.type=r.includes("Color")?"color":"text"},h,u,function(){n(2,a=!1)},r,function(t){C[t?"unshift":"push"]((()=>{d.text=t,n(5,d)}))},function(){l=this.value,n(1,l)},t=>h()]}class pt extends Q{constructor(t){super(),J(this,t,ft,ut,l,{show:0,text:1,event:10,title:3,label:4,error:2},at)}get show(){return this.$$.ctx[0]}set show(t){this.$$set({show:t}),A()}get text(){return this.$$.ctx[1]}set text(t){this.$$set({text:t}),A()}get event(){return this.$$.ctx[10]}set event(t){this.$$set({event:t}),A()}get title(){return this.$$.ctx[3]}set title(t){this.$$set({title:t}),A()}get label(){return this.$$.ctx[4]}set label(t){this.$$set({label:t}),A()}get error(){return this.$$.ctx[2]}set error(t){this.$$set({error:t}),A()}}function gt(t){h(t,"svelte-njq4pk",'.color-picker-wrapper.svelte-njq4pk{border:1px solid #ecf0f1;border-top:none;background:#FFF;box-shadow:rgba(0,0,0,.1) 0 2px 3px;width:290px;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);padding:0;position:absolute;top:37px;z-index:11}.color-picker-overlay.svelte-njq4pk{position:absolute;background-color:rgba(255,255,255,.5);height:100%;width:100%;left:0;top:0;z-index:10}.color-picker-btn.svelte-njq4pk{display:block;position:relative;float:left;height:20px;width:20px;border:1px solid #333;padding:0;margin:2px;line-height:35px;text-decoration:none;background:#FFF;color:#333!important;cursor:pointer;text-align:left;font-size:15px;transition:all 150ms;line-height:20px;padding:0px 5px}.color-picker-btn.svelte-njq4pk:hover::after{content:" ";display:block;position:absolute;top:-5px;left:-5px;height:27px;width:27px;background:inherit;border:1px solid #FFF;box-shadow:#000 0 0 2px;z-index:10}')}function vt(t,e,n){const o=t.slice();return o[8]=e[n],o}function bt(t){let e,n,o,i,l=(t[8].text||"")+"";function r(...e){return t[6](t[8],...e)}return{c(){e=g("button"),n=v(l),x(e,"type","button"),x(e,"class","color-picker-btn svelte-njq4pk"),w(e,"background-color",t[8].color)},m(t,l){u(t,e,l),d(e,n),o||(i=m(e,"click",r),o=!0)},p(o,i){t=o,2&i&&l!==(l=(t[8].text||"")+"")&&y(n,l),2&i&&w(e,"background-color",t[8].color)},d(t){t&&f(e),o=!1,i()}}}function mt(e){let n,o,i,l,r,s,c=e[1],a=[];for(let t=0;t{"show"in t&&n(0,i=t.show),"btns"in t&&n(1,l=t.btns),"event"in t&&n(4,r=t.event),"colors"in t&&n(5,s=t.colors)},t.$$.update=()=>{32&t.$$.dirty&&n(1,l=s.map((t=>({color:t}))).concat([{text:"#",modal:!0}]))},[i,l,c,a,r,s,(t,e)=>a(t)]}class yt extends Q{constructor(t){super(),J(this,t,xt,mt,l,{show:0,btns:1,event:4,colors:5},gt)}}const $t=function(t){const{subscribe:e,set:n,update:o}=lt({actionBtns:[],actionObj:{}});return{name:t,set:n,update:o,subscribe:e}};function wt(t){h(t,"svelte-1a534py",".cl.svelte-1a534py .svelte-1a534py{box-sizing:border-box}.cl.svelte-1a534py.svelte-1a534py{box-shadow:0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);box-sizing:border-box;width:100%;position:relative}.cl-content.svelte-1a534py.svelte-1a534py{height:300px;outline:0;overflow-y:auto;padding:10px;width:100%;background-color:white}.cl-actionbar.svelte-1a534py.svelte-1a534py{background-color:#ecf0f1;border-bottom:1px solid rgba(10, 10, 10, 0.1);width:100%}.cl-button.svelte-1a534py.svelte-1a534py{background-color:transparent;border:none;cursor:pointer;height:35px;outline:0;width:35px;vertical-align:top;position:relative}.cl-button.svelte-1a534py.svelte-1a534py:hover,.cl-button.active.svelte-1a534py.svelte-1a534py{background-color:#fff}.cl-button.svelte-1a534py.svelte-1a534py:disabled{opacity:.5;pointer-events:none}.cl-textarea.svelte-1a534py.svelte-1a534py{display:none;max-width:100%;min-width:100%;border:none;padding:10px}.cl-textarea.svelte-1a534py.svelte-1a534py:focus{outline:none}")}function kt(t,e,n){const o=t.slice();return o[38]=e[n],o}function jt(t){let e,n,o,i,l,r,s,c,a=t[38].icon+"";function h(...e){return t[24](t[38],...e)}return{c(){e=g("button"),n=new j,o=b(),n.a=o,x(e,"type","button"),x(e,"class",i="cl-button "+(t[38].active?"active":"")+" svelte-1a534py"),x(e,"title",l=t[38].title),e.disabled=r=t[38].disabled},m(t,i){u(t,e,i),n.m(a,e),d(e,o),s||(c=m(e,"click",h),s=!0)},p(o,s){t=o,16&s[0]&&a!==(a=t[38].icon+"")&&n.p(a),16&s[0]&&i!==(i="cl-button "+(t[38].active?"active":"")+" svelte-1a534py")&&x(e,"class",i),16&s[0]&&l!==(l=t[38].title)&&x(e,"title",l),16&s[0]&&r!==(r=t[38].disabled)&&(e.disabled=r)},d(t){t&&f(e),s=!1,c()}}}function zt(t){let e,n,i,l,r,s,c,a,h,v,y,$,k,j=t[4].actionBtns,z=[];for(let e=0;en(34,i=t))),Ht.push({});let v="editor_"+Ht.length,b=$t(v);c(t,b,(t=>n(4,l=t)));let m=lt({});c(t,m,(t=>n(3,o=t))),a(b,l.actionObj=((t,e=[])=>{if(e&&e.length){const n={};return e.forEach((e=>{"string"==typeof e?n[e]=Object.assign({},t[e]):t[e.name]?n[e.name]=Object.assign(t[e.name],e):n[e.name]=Object.assign({},e)})),n}return t})(st,s),l);let x={exec:H,getHtml:L,getText:T,setHtml:B,saveRange:F,restoreRange:R,helper:g,references:m,state:b,removeFormatTags:p};var y;function $(t){o.editor.focus(),F(o.editor),R(o.editor),t.result.call(x),w()}function w(t){const e=t?[]:G(document.getSelection().focusNode);Object.keys(l.actionObj).forEach((t=>a(b,l.actionObj[t].active=!1,l))),e.forEach((t=>(l.actionObj[t.toLowerCase()]||{}).active=!0)),a(b,l.actionBtns=et(l.actionObj),l),b.set(l)}function k(t){t.preventDefault(),H("insertHTML",t.clipboardData.getData("text/html")?(t=>{const e=t.match(/(.*?)/);let n=e&&e[1]||t;return n=n.replace(/\r?\n|\r/g," ").replace(//g,"").replace(new RegExp("<(/)*(meta|link|span|\\?xml:|st1:|o:|font|w:sdt)(.*?)>","gi"),"").replace(/(.*?)/gi,"").replace(/style="[^"]*"/gi,"").replace(/style='[^']*'/gi,"").replace(/ /gi," ").replace(/>(\s+)<").replace(/class="[^"]*"/gi,"").replace(/class='[^']*'/gi,"").replace(/<[^/].*?>/g,(t=>t.split(/[ >]/g)[0]+">")).trim(),n=nt(n),n})(t.clipboardData.getData("text/html")):t.clipboardData.getData("text"))}function j(t){r("change",t)}function z(t){!ot(t.target,o.editorWrapper)&&i.blurActive&&r("blur",t),a(g,i.blurActive=!0,i)}function H(t,e){K(t,e)}function L(t){return t?nt(o.editor.innerHTML):o.editor.innerHTML}function T(){return o.editor.innerText}function B(t,e){const n=e?nt(t):t||"";a(m,o.editor.innerHTML=n,o),a(m,o.raw.value=n,o)}function F(){Y(o.editor)}function R(){Z(o.editor)}!function(t,e){M().$$.context.set(t,e)}(v,x),y=()=>{a(b,l.actionBtns=et(l.actionObj),l),B(h)},M().$$.on_mount.push(y);const O=o;return t.$$set=t=>{"actions"in t&&n(13,s=t.actions),"height"in t&&n(0,d=t.height),"html"in t&&n(14,h=t.html),"contentId"in t&&n(1,u=t.contentId),"colors"in t&&n(2,f=t.colors),"removeFormatTags"in t&&n(15,p=t.removeFormatTags)},[d,u,f,o,l,g,b,m,$,w,k,j,z,s,h,p,H,L,T,B,F,R,O,t=>z(t),(t,e)=>$(t),function(t){C[t?"unshift":"push"]((()=>{o.editor=t,m.set(o)}))},t=>j(t.target.innerHTML),()=>w(),()=>w(),t=>k(t),function(t){C[t?"unshift":"push"]((()=>{o.raw=t,m.set(o)}))},function(t){C[t?"unshift":"push"]((()=>{o.modal=t,m.set(o)}))},function(t){C[t?"unshift":"push"]((()=>{o.colorPicker=t,m.set(o)}))},function(t){C[t?"unshift":"push"]((()=>{o.editorWrapper=t,m.set(o)}))}]}return class extends Q{constructor(t){super(),J(this,t,Mt,zt,l,{actions:13,height:0,html:14,contentId:1,colors:2,removeFormatTags:15,exec:16,getHtml:17,getText:18,setHtml:19,saveRange:20,restoreRange:21,refs:22},wt,[-1,-1])}get actions(){return this.$$.ctx[13]}set actions(t){this.$$set({actions:t}),A()}get height(){return this.$$.ctx[0]}set height(t){this.$$set({height:t}),A()}get html(){return this.$$.ctx[14]}set html(t){this.$$set({html:t}),A()}get contentId(){return this.$$.ctx[1]}set contentId(t){this.$$set({contentId:t}),A()}get colors(){return this.$$.ctx[2]}set colors(t){this.$$set({colors:t}),A()}get removeFormatTags(){return this.$$.ctx[15]}set removeFormatTags(t){this.$$set({removeFormatTags:t}),A()}get exec(){return this.$$.ctx[16]}get getHtml(){return this.$$.ctx[17]}get getText(){return this.$$.ctx[18]}get setHtml(){return this.$$.ctx[19]}get saveRange(){return this.$$.ctx[20]}get restoreRange(){return this.$$.ctx[21]}get refs(){return this.$$.ctx[22]}}}));
2 |
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-700/Noto-Sans-700.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
337 |
--------------------------------------------------------------------------------
/assets/fonts/Noto-Sans-700italic/Noto-Sans-700italic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
335 |
--------------------------------------------------------------------------------