├── .github
└── workflows
│ ├── demos.yml
│ └── deploy.yml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── demos
├── build.sh
├── demos-index.html
├── header-text.js
├── javascript
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ └── index.js
│ └── webpack.config.js
├── package.json
├── react
│ ├── .babelrc
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── app.jsx
│ │ └── index.jsx
│ └── webpack.config.js
├── script-tags
│ ├── demo-quill-1.x.html
│ └── demo-quill-2.x.html
├── typescript
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── index.ts
│ │ └── setup.js
│ ├── tsconfig.json
│ └── webpack.config.js
└── vue
│ ├── package.json
│ ├── public
│ └── index.html
│ ├── src
│ ├── app.vue
│ └── index.js
│ └── webpack.config.js
├── jest.config.js
├── package.json
├── src
├── html-formatter.ts
├── html-parser.spec.ts
├── html-parser.ts
├── index.ts
├── logger.ts
├── options.ts
├── quill.htmlEditButton.ts
└── styles.css
├── tsconfig.json
└── webpack.config.js
/.github/workflows/demos.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - master
5 |
6 | name: Build and Publish Demos
7 | jobs:
8 | demo:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@master
12 | - uses: actions/setup-node@v1
13 | with:
14 | node-version: 20.12.1
15 | - name: Cache node_modules
16 | id: cache-modules
17 | uses: actions/cache@v1
18 | with:
19 | path: node_modules
20 | key: ${{ runner.OS }}-build-${{ hashFiles('package.json') }}
21 | - name: Install Dependencies
22 | if: steps.cache-modules.outputs.cache-hit != 'true'
23 | run: yarn
24 | - name: Build lib
25 | run: yarn build
26 | - name: Build demo folder
27 | run: |
28 | cd demos
29 | yarn
30 | ./build.sh
31 | find ./dist
32 | - name: Deploy 🚀
33 | uses: JamesIves/github-pages-deploy-action@releases/v3
34 | with:
35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
36 | BRANCH: gh-pages
37 | FOLDER: demos/dist
38 | CLEAN: true
39 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - master
5 |
6 | name: Build and Publish Library
7 | jobs:
8 | build:
9 | if: "!contains(github.event.head_commit.message, 'no-ci')"
10 | name: Deploy Library
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@master
14 | - uses: actions/setup-node@v1
15 | with:
16 | node-version: 20.12.1
17 | - name: Cache node_modules
18 | id: cache-modules
19 | uses: actions/cache@v1
20 | with:
21 | path: node_modules
22 | key: ${{ runner.OS }}-build-${{ hashFiles('package.json') }}
23 | - name: Install Dependencies
24 | if: steps.cache-modules.outputs.cache-hit != 'true'
25 | run: yarn
26 | - name: Test Library
27 | run: yarn test
28 | - name: Build Library
29 | run: yarn build
30 | - name: Publish
31 | uses: Github-Actions-Community/merge-release@22d66d3f7750d57b2b8c05db6077205332527df8
32 | env:
33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | yarn.lock
3 | dist
4 | types
5 | *.DS_STORE
6 | *.tgz
7 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .babelrc
2 | .github
3 | node_modules
4 | yarn.lock
5 | webpack.config.js
6 | tsconfig.json
7 | jest.config.js
8 | demos
9 | *.tgz
10 |
11 | !dist
12 | !types
13 | !src
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2020 Ben Winding
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # quill-html-edit-button
2 |
3 |
4 |
5 | [](https://www.npmjs.com/package/quill-html-edit-button)
6 | [](https://github.com/benwinding/quill-html-edit-button/blob/master/LICENSE)
7 | [](https://www.npmjs.com/package/quill-html-edit-button)
8 | [](https://github.com/benwinding/quill-html-edit-button)
9 | 
10 |
11 |
12 |
13 | Quill.js Module which allows you to quickly view/edit the HTML in the editor
14 |
15 | 
16 |
17 | - [Live Demo (webpack javascript)](https://benwinding.github.io/quill-html-edit-button/javascript/) - also > [Source Code](https://github.com/benwinding/quill-html-edit-button/tree/master/demos/javascript)
18 | - [Live Demo (webpack typescript)](https://benwinding.github.io/quill-html-edit-button/typescript/) - also > [Source Code](https://github.com/benwinding/quill-html-edit-button/tree/master/demos/typescript)
19 | - [Live Demo (webpack vue)](https://benwinding.github.io/quill-html-edit-button/vue/) - also > [Source Code](https://github.com/benwinding/quill-html-edit-button/tree/master/demos/vue)
20 | - [Live Demo (script tags quill-1.x)](https://benwinding.github.io/quill-html-edit-button/script-tags/demo-quill-1.x.html) - also > [Source Code](https://github.com/benwinding/quill-html-edit-button/tree/master/demos/script-tags/demo-quill-1.x.html)
21 | - [Live Demo (script tags quill-2.x) With Tables!](https://benwinding.github.io/quill-html-edit-button/script-tags/demo-quill-2.x.html) - also > [Source Code](https://github.com/benwinding/quill-html-edit-button/tree/master/demos/script-tags/demo-quill-2.x.html)
22 |
23 | ## Install
24 |
25 | `yarn add quill-html-edit-button`
26 |
27 | ## Quickstart (Javascript)
28 |
29 | ``` js
30 | import Quill from 'quill/core';
31 | import Toolbar from 'quill/modules/toolbar';
32 | import Snow from 'quill/themes/snow';
33 |
34 | import htmlEditButton from "quill-html-edit-button";
35 |
36 | Quill.register({
37 | 'modules/toolbar': Toolbar,
38 | 'themes/snow': Snow,
39 | "modules/htmlEditButton": htmlEditButton
40 | })
41 |
42 | const quill = new Quill(editor, {
43 | // ...
44 | modules: {
45 | // ...
46 | htmlEditButton: {}
47 | }
48 | });
49 | ```
50 |
51 | ## Quickstart (typescript)
52 |
53 | Due to Quill's implementation, typescript integration is not trivial:
54 |
55 | - Follow the demo example here [`demos/typescript/src/index.ts`](https://github.com/benwinding/quill-html-edit-button/blob/master/demos/typescript/src/index.ts)
56 | - The file [`setup.js`](https://github.com/benwinding/quill-html-edit-button/blob/master/demos/typescript/src/setup.js`) is to use the library without types (as they aren't implemented with quill modules).
57 | - Your `tsconfig.json` needs the following properties, to prevent errors:
58 | ``` json
59 | "compilerOptions": {
60 | "allowJs": true,
61 | "checkJs": false
62 | }
63 | ```
64 |
65 |
66 | ## Quickstart (script tag)
67 |
68 | ``` html
69 |
70 |
71 |
81 | ```
82 |
83 | ## Options
84 |
85 | ``` js
86 | modules: {
87 | // ...
88 | htmlEditButton: {
89 | debug: true, // logging, default:false
90 | msg: "Edit the content in HTML format", //Custom message to display in the editor, default: Edit HTML here, when you click "OK" the quill editor's contents will be replaced
91 | okText: "Ok", // Text to display in the OK button, default: Ok,
92 | cancelText: "Cancel", // Text to display in the cancel button, default: Cancel
93 | buttonHTML: "<>", // Text to display in the toolbar button, default: <>
94 | buttonTitle: "Show HTML source", // Text to display as the tooltip for the toolbar button, default: Show HTML source
95 | syntax: false, // Show the HTML with syntax highlighting. Requires highlightjs on window.hljs (similar to Quill itself), default: false
96 | prependSelector: 'div#myelement', // a string used to select where you want to insert the overlayContainer, default: null (appends to body),
97 | editorModules: {} // The default mod
98 | }
99 | }
100 | ```
101 |
102 | ### Syntax Highlighting
103 |
104 | By default syntax highlighting is off, if you want to enable it use `syntax: true` in the options (as shown above) and make sure you add the following script tags:
105 |
106 | ``` html
107 |
111 |
112 |
116 | ```
117 |
118 | Alternatively, include these scripts in your package bundler, as long as highlightjs is available in the global space at `window.hljs`.
119 |
120 | ## Customising The HTML Editor
121 | The editor itself is actually a Quill Editor instance too! So you can pass in custom modules like this:
122 |
123 | ``` ts
124 | // options
125 | htmlEditButton: {
126 | // Flags
127 | debug?: boolean; // default: false
128 | syntax?: boolean; // default: false
129 | // Overlay
130 | closeOnClickOverlay: boolean; // default: true
131 | prependSelector: string; // default: null
132 | // Labels
133 | buttonHTML?: string; // default: "<>"
134 | buttonTitle?: string; // default: "Show HTML source"
135 | msg: string; // default: 'Edit HTML here, when you click "OK" the quill editor\'s contents will be replaced'
136 | okText: string; // default: "Ok"
137 | cancelText: string; // default: "Cancel"
138 | // Quill Modules (for the HTML editor)
139 | editorModules?: { // default: null
140 | // Any modules here will be declared in HTML quill editor instance
141 | keyboard: {
142 | bindings: {
143 | custom: {
144 | key: 'a',
145 | handler: function(range, context) {
146 | console.log('A KEY PRESSED!');
147 | }
148 | },
149 | },
150 | },
151 | },
152 | },
153 | ```
154 |
155 | ## Thanks
156 |
157 | This project is based on [quill-image-uploader](https://github.com/NoelOConnell/quill-image-uploader), thanks mate!
158 |
--------------------------------------------------------------------------------
/demos/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | rm -rf ./dist
4 | mkdir dist
5 |
6 | # Common Header
7 | cp ./header-text.js ./dist
8 | # Demo list
9 | cp ./demos-index.html ./dist/index.html
10 |
11 | # Build typescript
12 | rm -rf ./typescript/dist
13 | yarn build:typescript
14 | cp -r ./typescript/dist ./dist/typescript
15 |
16 | # Build javascript
17 | rm -rf ./javascript/dist
18 | yarn build:javascript
19 | cp -r ./javascript/dist ./dist/javascript
20 |
21 | # Copy script tags
22 | mkdir ./dist/script-tags
23 | cp ./script-tags/* ./dist/script-tags
24 |
25 | # Build vue
26 | rm -rf ./vue/dist
27 | yarn build:vue
28 | cp -r ./vue/dist ./dist/vue
29 |
30 | # Build react
31 | rm -rf ./react/dist
32 | yarn build:react
33 | cp -r ./react/dist ./dist/react
34 |
--------------------------------------------------------------------------------
/demos/demos-index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | quill-html-edit-button (Demos)
7 |
8 |
9 |
10 |
11 |
12 | This is a quill.js module
13 | which allows you to quickly view/edit the HTML in the editor.
14 |
15 |
16 |
22 |
23 | Demos
24 |
25 | Below are a few demos for the
26 | quill-html-edit-button
29 | library.
30 |
31 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/demos/header-text.js:
--------------------------------------------------------------------------------
1 | const demoTitle = document.title;
2 | const demoSourceCodeLink = document
3 | .getElementById("src-code")
4 | .getAttribute("data");
5 |
6 | // HEADER TEXT
7 | const headerHtml = `
13 | `;
14 | const header = document.createElement("div");
15 | header.innerHTML = headerHtml;
16 |
17 | // GITHUB CORNER
18 | const cornerHTML = `
19 |
22 |
23 | `;
26 | const corner = document.createElement("div");
27 | corner.innerHTML = cornerHTML;
28 |
29 | // Add to DOM after first JS frame
30 | window.addEventListener("load", function () {
31 | document.body.prepend(header);
32 | document.body.prepend(corner);
33 | });
34 |
--------------------------------------------------------------------------------
/demos/javascript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "javascript",
3 | "private": true,
4 | "version": "1.0.0",
5 | "main": "index.js",
6 | "license": "MIT",
7 | "scripts": {
8 | "start": "webpack serve --mode development --hot",
9 | "build": "webpack --mode production"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/demos/javascript/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Quill Demo (javascript)
7 |
11 |
12 |
13 |
14 |
15 |
19 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
Select the HTML button from the toolbar
34 |
35 |
Try editing this sentence!
36 |
37 |
38 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/demos/javascript/src/index.js:
--------------------------------------------------------------------------------
1 | import htmlEditButton from "../../../src/quill.htmlEditButton";
2 |
3 | import Quill from "quill/core";
4 |
5 | import Toolbar from "quill/modules/toolbar";
6 | import Snow from "quill/themes/snow";
7 |
8 | Quill.register({
9 | "modules/toolbar": Toolbar,
10 | "themes/snow": Snow,
11 | "modules/htmlEditButton": htmlEditButton,
12 | });
13 |
14 | const fullToolbarOptions = [
15 | [{ header: [1, 2, 3, false] }],
16 | ["bold", "italic"],
17 | ["clean"],
18 | ["image"],
19 | [{ list: "ordered" }, { list: "bullet" }],
20 | ];
21 |
22 | console.log("Demo loaded...");
23 |
24 | var quill = new Quill("#editor", {
25 | theme: "snow",
26 | modules: {
27 | toolbar: {
28 | container: fullToolbarOptions,
29 | },
30 | htmlEditButton: { debug: true, syntax: true },
31 | },
32 | });
33 |
--------------------------------------------------------------------------------
/demos/javascript/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const TerserPlugin = require("terser-webpack-plugin");
3 | const CopyWebpackPlugin = require("copy-webpack-plugin");
4 |
5 | const isProd = process.argv.includes("production");
6 |
7 | /**
8 | * @type {import('webpack').Configuration}
9 | */
10 | module.exports = {
11 | entry: {
12 | index: "./src/index.js",
13 | },
14 | output: {
15 | filename: "[name].min.js",
16 | libraryTarget: "umd",
17 | path: path.resolve(__dirname, "dist"),
18 | publicPath: "/dist",
19 | },
20 | devServer: {
21 | port: 8001,
22 | static: {
23 | directory: path.resolve(__dirname, "dist"),
24 | },
25 | devMiddleware: {
26 | writeToDisk: true,
27 | },
28 | },
29 | plugins: [
30 | new CopyWebpackPlugin({
31 | patterns: [
32 | { from: "public", to: "." },
33 | { from: "../header-text.js", to: "." },
34 | ],
35 | }),
36 | ],
37 | devtool: isProd ? "source-map" : "inline-source-map",
38 | optimization: {
39 | minimize: true,
40 | minimizer: [
41 | new TerserPlugin({
42 | extractComments: true,
43 | parallel: true,
44 | terserOptions: {
45 | compress: {
46 | drop_console: false,
47 | },
48 | },
49 | }),
50 | ],
51 | },
52 | resolve: {
53 | extensions: [".ts", ".js"],
54 | },
55 | module: {
56 | rules: [
57 | { test: /\.ts$/, use: ["ts-loader"], exclude: /node_modules/ },
58 | {
59 | test: /\.css$/,
60 | use: ["style-loader", "css-loader"],
61 | },
62 | {
63 | test: /\.js$/,
64 | exclude: /node_modules/,
65 | use: {
66 | loader: "babel-loader",
67 | },
68 | },
69 | {
70 | test: /\.svg$/,
71 | use: [
72 | {
73 | loader: "html-loader",
74 | options: {
75 | minimize: true,
76 | },
77 | },
78 | ],
79 | },
80 | ],
81 | },
82 | };
83 |
--------------------------------------------------------------------------------
/demos/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quill-html-edit-button-demos",
3 | "version": "0.0.1",
4 | "private": true,
5 | "workspaces": [
6 | "javascript",
7 | "react",
8 | "typescript",
9 | "vue"
10 | ],
11 | "scripts": {
12 | "start:javascript": "yarn workspace javascript start",
13 | "start:react": "yarn workspace react start",
14 | "start:typescript": "yarn workspace typescript start",
15 | "start:vue": "yarn workspace vue start",
16 | "build:javascript": "yarn workspace javascript build",
17 | "build:react": "yarn workspace react build",
18 | "build:typescript": "yarn workspace typescript build",
19 | "build:vue": "yarn workspace vue build",
20 | "build": "./build.sh"
21 | },
22 | "devDependencies": {
23 | "babel-core": "^6.26.0",
24 | "babel-loader": "^7.1.3",
25 | "copy-webpack-plugin": "^12.0.2",
26 | "webpack": "5.89.0",
27 | "webpack-cli": "4.10.0",
28 | "webpack-dev-server": "4.15.1"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/demos/react/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-react"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/demos/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react",
3 | "private": true,
4 | "version": "1.0.0",
5 | "main": "index.js",
6 | "license": "MIT",
7 | "scripts": {
8 | "start": "webpack serve --mode development --hot",
9 | "build": "webpack --mode production"
10 | },
11 | "devDependencies": {
12 | "react-quill": "2.0.0",
13 | "react": "18.2.0",
14 | "react-dom": "18.2.0",
15 | "babel-core": "^6.26.0",
16 | "babel-loader": "9.1.3",
17 | "@babel/preset-react": "7.23.3"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/demos/react/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Quill Demo (react)
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/demos/react/src/app.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 |
3 | import htmlEditButton from "../../../src/quill.htmlEditButton";
4 | // import htmlEditButton from "quill-html-edit-button";
5 | // ^ In production use this
6 | import ReactQuill, { Quill } from "react-quill";
7 | import "react-quill/dist/quill.snow.css";
8 |
9 | import Toolbar from "quill/modules/toolbar";
10 | import Snow from "quill/themes/snow";
11 |
12 | Quill.register({
13 | "modules/toolbar": Toolbar,
14 | "themes/snow": Snow,
15 | "modules/htmlEditButton": htmlEditButton,
16 | });
17 |
18 | const modules = {
19 | htmlEditButton: {
20 | debug: true,
21 | },
22 | };
23 |
24 | export function MyApp() {
25 | const [value, setValue] = useState("Here is some text!");
26 |
27 | return (
28 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/demos/react/src/index.jsx:
--------------------------------------------------------------------------------
1 | /* global React */
2 | /* global ReactQuill */
3 | "use strict";
4 |
5 | import { MyApp } from "./app";
6 | import ReactDOM from "react-dom/client";
7 | import React from "react";
8 |
9 | const root = ReactDOM.createRoot(document.getElementById("app"));
10 |
11 | root.render();
12 |
--------------------------------------------------------------------------------
/demos/react/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const TerserPlugin = require("terser-webpack-plugin");
3 | const CopyWebpackPlugin = require("copy-webpack-plugin");
4 |
5 | const isProd = process.argv.includes("production");
6 |
7 | /**
8 | * @type {import('webpack').Configuration}
9 | */
10 | module.exports = {
11 | entry: {
12 | index: "./src/index.jsx",
13 | },
14 | output: {
15 | filename: "[name].min.js",
16 | libraryTarget: "umd",
17 | path: path.resolve(__dirname, "dist"),
18 | },
19 | devServer: {
20 | port: 8001,
21 | hot: true,
22 | devMiddleware: {
23 | writeToDisk: true,
24 | },
25 | },
26 | plugins: [
27 | new CopyWebpackPlugin({
28 | patterns: [
29 | { from: "public", to: "." },
30 | { from: "../header-text.js", to: "." },
31 | ],
32 | }),
33 | ],
34 | devtool: isProd ? "source-map" : "inline-source-map",
35 | optimization: {
36 | minimize: true,
37 | minimizer: [
38 | new TerserPlugin({
39 | extractComments: true,
40 | parallel: true,
41 | }),
42 | ],
43 | },
44 | resolve: {
45 | extensions: [".js", ".jsx", ".ts"],
46 | },
47 | module: {
48 | rules: [
49 | { test: /\.ts$/, use: ["ts-loader"], exclude: /node_modules/ },
50 | {
51 | test: /\.css$/,
52 | use: ["style-loader", "css-loader"],
53 | },
54 | {
55 | test: /\.jsx$/,
56 | exclude: /node_modules/,
57 | use: {
58 | loader: "babel-loader",
59 | },
60 | },
61 | {
62 | test: /\.svg$/,
63 | use: [
64 | {
65 | loader: "html-loader",
66 | options: {
67 | minimize: true,
68 | },
69 | },
70 | ],
71 | },
72 | ],
73 | },
74 | };
75 |
--------------------------------------------------------------------------------
/demos/script-tags/demo-quill-1.x.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Quill Demo (script tag) Quill 1.x
7 |
11 |
12 |
13 |
14 |
15 |
19 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
Select the HTML button from the toolbar
35 |
36 |
Try editing this sentence!
37 |
38 |
39 |
43 |
44 |
45 |
46 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/demos/script-tags/demo-quill-2.x.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Quill Demo (script tag) Quill 2.x
7 |
11 |
12 |
13 |
14 |
15 |
19 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
Select the HTML button from the toolbar
35 |
36 |
Try editing this sentence!
37 |
Wow a table!
38 |
39 |
40 |
41 | Australia |
42 | America |
43 | Americalia |
44 |
45 |
46 |
47 |
48 |
49 |
53 |
54 |
55 |
56 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/demos/typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "typescript",
3 | "private": true,
4 | "version": "1.0.0",
5 | "main": "index.js",
6 | "license": "MIT",
7 | "scripts": {
8 | "start": "webpack serve --mode development --hot",
9 | "build": "webpack --mode production"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/demos/typescript/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Quill Demo (typescript)
7 |
11 |
12 |
13 |
14 |
15 |
19 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
Select the HTML button from the toolbar
34 |
35 |
Try editing this sentence!
36 |
37 |
38 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/demos/typescript/src/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Currently there's no types for quill modules,
3 | so you need to either make types, or use a js
4 | file to import them (which is what setup.js is).
5 | */
6 | import Quill from "./setup";
7 |
8 | const fullToolbarOptions = [
9 | [{ header: [1, 2, 3, false] }],
10 | ["bold", "italic"],
11 | ["clean"],
12 | ["image"],
13 | [{ list: "ordered" }, { list: "bullet" }],
14 | ];
15 |
16 | console.log("Demo loaded...");
17 |
18 | var quill = new Quill("#editor", {
19 | theme: "snow",
20 | modules: {
21 | toolbar: {
22 | container: fullToolbarOptions,
23 | },
24 | htmlEditButton: { debug: true, syntax: true },
25 | },
26 | });
27 |
--------------------------------------------------------------------------------
/demos/typescript/src/setup.js:
--------------------------------------------------------------------------------
1 | import Quill from "quill/core";
2 |
3 | import htmlEditButton from "../../../src/quill.htmlEditButton";
4 | /*
5 | import htmlEditButton from "quill-html-edit-button";
6 | ^ In production use this
7 | */
8 | import Toolbar from "quill/modules/toolbar";
9 | import Snow from "quill/themes/snow";
10 |
11 | Quill.register({
12 | "modules/toolbar": Toolbar,
13 | "themes/snow": Snow,
14 | });
15 | Quill.register("modules/htmlEditButton", htmlEditButton);
16 |
17 | export default Quill;
18 |
--------------------------------------------------------------------------------
/demos/typescript/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./src",
5 | "strict": true,
6 | "module": "commonjs",
7 | "target": "es5",
8 | "allowJs": true,
9 | "checkJs": false,
10 | "moduleResolution": "node",
11 | "allowSyntheticDefaultImports": true,
12 | "esModuleInterop": true,
13 | "skipLibCheck": true,
14 | "outDir": "dist",
15 | "lib": ["es2017", "dom"]
16 | },
17 | "include": ["src"]
18 | }
19 |
--------------------------------------------------------------------------------
/demos/typescript/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const TerserPlugin = require("terser-webpack-plugin");
3 | const CopyWebpackPlugin = require("copy-webpack-plugin");
4 |
5 | const isProd = process.argv.includes("production");
6 |
7 | /**
8 | * @type {import('webpack').Configuration}
9 | */
10 | module.exports = {
11 | entry: {
12 | index: "./src/index.ts",
13 | },
14 | output: {
15 | filename: "[name].min.js",
16 | libraryTarget: "umd",
17 | path: path.resolve(__dirname, "dist"),
18 | },
19 | devServer: {
20 | port: 8001,
21 | hot: true,
22 | devMiddleware: {
23 | writeToDisk: true,
24 | },
25 | },
26 | plugins: [
27 | new CopyWebpackPlugin({
28 | patterns: [
29 | { from: "public", to: "." },
30 | { from: "../header-text.js", to: "." },
31 | ],
32 | }),
33 | ],
34 | devtool: isProd ? "source-map" : "inline-source-map",
35 | optimization: {
36 | minimize: true,
37 | minimizer: [
38 | new TerserPlugin({
39 | extractComments: true,
40 | parallel: true,
41 | terserOptions: {
42 | compress: {
43 | drop_console: false,
44 | },
45 | },
46 | }),
47 | ],
48 | },
49 | resolve: {
50 | extensions: [".ts", ".js"],
51 | },
52 | module: {
53 | rules: [
54 | { test: /\.ts$/, use: ["ts-loader"], exclude: /node_modules/ },
55 | {
56 | test: /\.css$/,
57 | use: ["style-loader", "css-loader"],
58 | },
59 | {
60 | test: /\.svg$/,
61 | use: [
62 | {
63 | loader: "html-loader",
64 | options: {
65 | minimize: true,
66 | },
67 | },
68 | ],
69 | },
70 | ],
71 | },
72 | };
73 |
--------------------------------------------------------------------------------
/demos/vue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue",
3 | "private": true,
4 | "version": "1.0.0",
5 | "main": "index.js",
6 | "license": "MIT",
7 | "scripts": {
8 | "start": "webpack serve --mode development --hot",
9 | "build": "webpack --mode production"
10 | },
11 | "devDependencies": {
12 | "@vueup/vue-quill": "1.0.0-beta.8",
13 | "vue": "3.3.7",
14 | "vue-loader": "17.3.1",
15 | "vue-template-compiler": "2.6.14"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/demos/vue/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Quill Demo (vue)
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/demos/vue/src/app.vue:
--------------------------------------------------------------------------------
1 |
2 | Basic Example
3 |
4 |
5 |
6 |
37 |
--------------------------------------------------------------------------------
/demos/vue/src/index.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "vue";
2 | import App from "./app.vue";
3 |
4 | import "@vueup/vue-quill/dist/vue-quill.bubble.css";
5 | import "@vueup/vue-quill/dist/vue-quill.snow.css";
6 |
7 | createApp(App).mount("#app");
8 |
--------------------------------------------------------------------------------
/demos/vue/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const TerserPlugin = require("terser-webpack-plugin");
3 | const CopyWebpackPlugin = require("copy-webpack-plugin");
4 | const { VueLoaderPlugin } = require("vue-loader");
5 |
6 | const isProd = process.argv.includes("production");
7 |
8 | /**
9 | * @type {import('webpack').Configuration}
10 | */
11 | module.exports = {
12 | entry: {
13 | index: "./src/index.js",
14 | },
15 | output: {
16 | filename: "[name].min.js",
17 | libraryTarget: "umd",
18 | path: path.resolve(__dirname, "dist"),
19 | },
20 | devServer: {
21 | port: 8001,
22 | hot: true,
23 | devMiddleware: {
24 | writeToDisk: true,
25 | },
26 | },
27 | plugins: [
28 | new CopyWebpackPlugin({
29 | patterns: [
30 | { from: "public", to: "." },
31 | { from: "../header-text.js", to: "." },
32 | ],
33 | }),
34 | new VueLoaderPlugin(),
35 | ],
36 | devtool: isProd ? "source-map" : "inline-source-map",
37 | optimization: {
38 | minimize: true,
39 | minimizer: [
40 | new TerserPlugin({
41 | extractComments: true,
42 | parallel: true,
43 | }),
44 | ],
45 | },
46 | resolve: {
47 | extensions: [".ts", ".js", ".vue"],
48 | alias: {
49 | vue: "vue/dist/vue.esm-bundler.js",
50 | },
51 | },
52 | module: {
53 | rules: [
54 | { test: /\.ts$/, use: ["ts-loader"], exclude: /node_modules/ },
55 | {
56 | test: /\.css$/,
57 | use: ["style-loader", "css-loader"],
58 | },
59 | {
60 | test: /\.js$/,
61 | exclude: /node_modules/,
62 | use: {
63 | loader: "babel-loader",
64 | },
65 | },
66 | {
67 | test: /\.svg$/,
68 | use: [
69 | {
70 | loader: "html-loader",
71 | options: {
72 | minimize: true,
73 | },
74 | },
75 | ],
76 | },
77 | {
78 | test: /\.vue$/,
79 | exclude: /node_modules/,
80 | loader: "vue-loader",
81 | },
82 | ],
83 | },
84 | };
85 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: "ts-jest",
3 | testEnvironment: "node",
4 | testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.ts$"
5 | };
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quill-html-edit-button",
3 | "description": "A Quill rich text editor Module which adds an html edit button to the quill editor",
4 | "version": "0.0.0",
5 | "main": "dist/quill.htmlEditButton.min.js",
6 | "types": "types/index.d.ts",
7 | "repository": "https://github.com/benwinding/quill-html-edit-button",
8 | "author": "Ben Winding ",
9 | "license": "MIT",
10 | "scripts": {
11 | "watch": "webpack --watch --progress",
12 | "build": "yarn build-lib && yarn build-types",
13 | "build-lib": "webpack --mode production",
14 | "build-types": "tsc --declaration --emitDeclarationOnly",
15 | "test": "jest",
16 | "test-watch": "jest --watch-all",
17 | "lint": "prettier --write src/**/*.ts",
18 | "clean": "rm -rf dist/"
19 | },
20 | "keywords": [
21 | "quill",
22 | "quilljs",
23 | "html",
24 | "button",
25 | "edit"
26 | ],
27 | "peerDependencies": {
28 | "quill": "^2.x"
29 | },
30 | "devDependencies": {
31 | "@types/jest": "^26.0.15",
32 | "@types/node": "^14.14.22",
33 | "@types/quill": "^2.0.1",
34 | "css-loader": "^0.28.8",
35 | "highlight.js": "^10.1.2",
36 | "html-loader": "^1.3.2",
37 | "jest": "^26.6.3",
38 | "prettier": "^3.0.3",
39 | "quill": "^2.0.2",
40 | "style-loader": "^0.19.1",
41 | "ts-jest": "^26.4.4",
42 | "ts-loader": "^9.5.1",
43 | "typescript": "^5.5.3",
44 | "webpack": "5.93.0",
45 | "webpack-cli": "5.1.4",
46 | "webpack-dev-server": "5.0.4"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/html-formatter.ts:
--------------------------------------------------------------------------------
1 | import { QuillHtmlLogger } from "./logger";
2 |
3 | // Adapted FROM jsfiddle here: https://jsfiddle.net/buksy/rxucg1gd/
4 | export function FormatHTMLStringIndentation(
5 | code: string,
6 | logger: QuillHtmlLogger,
7 | ) {
8 | "use strict";
9 | let stripWhiteSpaces = true;
10 | let stripEmptyLines = true;
11 | const whitespace = " ".repeat(2); // Default indenting 4 whitespaces
12 | let currentIndent = 0;
13 | const newlineChar = "\n";
14 | let prevChar = null;
15 | let char = null;
16 | let nextChar = null;
17 |
18 | let result = "";
19 | for (let pos = 0; pos <= code.length; pos++) {
20 | prevChar = char;
21 | char = code.substr(pos, 1);
22 | nextChar = code.substr(pos + 1, 1);
23 |
24 | const isBrTag = code.substr(pos, 4) === "
";
25 | const isOpeningTag = char === "<" && nextChar !== "/" && !isBrTag;
26 | const isClosingTag = char === "<" && nextChar === "/" && !isBrTag;
27 | const isTagEnd = prevChar === ">" && char !== "<" && currentIndent > 0;
28 | const isTagNext =
29 | !isBrTag &&
30 | !isOpeningTag &&
31 | !isClosingTag &&
32 | isTagEnd &&
33 | code.substr(pos, code.substr(pos).indexOf("<")).trim() === "";
34 | if (isBrTag) {
35 | // If opening tag, add newline character and indention
36 | result += newlineChar;
37 | currentIndent--;
38 | pos += 4;
39 | }
40 | if (isOpeningTag) {
41 | // If opening tag, add newline character and indention
42 | result += newlineChar + whitespace.repeat(currentIndent);
43 | currentIndent++;
44 | }
45 | // if Closing tag, add newline and indention
46 | else if (isClosingTag) {
47 | // If there're more closing tags than opening
48 | if (--currentIndent < 0) currentIndent = 0;
49 | result += newlineChar + whitespace.repeat(currentIndent);
50 | }
51 | // remove multiple whitespaces
52 | else if (stripWhiteSpaces === true && char === " " && nextChar === " ")
53 | char = "";
54 | // remove empty lines
55 | else if (stripEmptyLines === true && char === newlineChar) {
56 | //debugger;
57 | if (code.substr(pos, code.substr(pos).indexOf("<")).trim() === "")
58 | char = "";
59 | }
60 | if (isTagEnd && !isTagNext) {
61 | result += newlineChar + whitespace.repeat(currentIndent);
62 | }
63 |
64 | result += char;
65 | }
66 | logger.log("formatHTML", {
67 | before: code,
68 | after: result,
69 | });
70 | return result;
71 | }
72 |
--------------------------------------------------------------------------------
/src/html-parser.spec.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ConvertMultipleSpacesToSingle,
3 | FixTagSpaceOpenTag,
4 | FixTagSpaceCloseTag,
5 | PreserveNewlinesBr,
6 | PreserveNewlinesPTags,
7 | } from "./html-parser";
8 |
9 | describe("html parser multiple spaces converted to single", () => {
10 | test("space on right of tag", () => {
11 | expect(ConvertMultipleSpacesToSingle("OKAY ")).toBe(
12 | "OKAY",
13 | );
14 | });
15 | test("new lines on right", () => {
16 | expect(
17 | ConvertMultipleSpacesToSingle(`OKAY
18 | `),
19 | ).toBe("OKAY");
20 | });
21 | test(" spaces between", () => {
22 | expect(ConvertMultipleSpacesToSingle("11 22")).toBe(
23 | "11 22",
24 | );
25 | });
26 | });
27 |
28 | describe("html parser convert br tags", () => {
29 | test("
is converted to
", () => {
30 | expect(PreserveNewlinesBr("
")).toBe("
");
31 | });
32 | test("
is converted to
", () => {
33 | expect(PreserveNewlinesBr("
")).toBe("
");
34 | });
35 | test("
is converted to
", () => {
36 | expect(PreserveNewlinesBr("
")).toBe("
");
37 | });
38 | });
39 |
40 | describe("html parser convert p tags", () => {
41 | test(" is converted to
", () => {
42 | expect(PreserveNewlinesPTags("")).toBe("
");
43 | });
44 | });
45 |
46 | describe("html parser spacing around open tags", () => {
47 | test("<> space right of > stripped", () => {
48 | expect(FixTagSpaceOpenTag(" test")).toBe("test");
49 | });
50 | test("<> spaces between left NOT stripped", () => {
51 | expect(FixTagSpaceOpenTag(" ")).toBe(" ");
52 | });
53 | });
54 |
55 | describe("html parser spacing around close tags", () => {
56 | test("<> space left of < stripped", () => {
57 | expect(FixTagSpaceCloseTag("test ")).toBe("test ");
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/src/html-parser.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Formats the the output from the popup so that the quill editor can properly display it
3 | */
4 |
5 | export function OutputHTMLParser(inputHtmlFromQuillPopup: string): string {
6 | return Compose(
7 | [
8 | ConvertMultipleSpacesToSingle,
9 | FixTagSpaceOpenTag,
10 | FixTagSpaceCloseTag,
11 | PreserveNewlinesBr,
12 | PreserveNewlinesPTags,
13 | ],
14 | inputHtmlFromQuillPopup,
15 | );
16 | }
17 |
18 | export function ConvertMultipleSpacesToSingle(input: string): string {
19 | return input.replace(/\s+/g, " ").trim();
20 | }
21 |
22 | export function PreserveNewlinesBr(input: string): string {
23 | return input.replace(/
)/g, "
");
24 | }
25 |
26 | export function PreserveNewlinesPTags(input: string): string {
27 | return input.replace(/<\/p>/g, "
");
28 | }
29 |
30 | export function FixTagSpaceOpenTag(input: string): string {
31 | // Open tag remove space on inside
32 | return input.replace(/(<(?!\/)[\w=\."'\s]*>) /g, "$1");
33 | }
34 |
35 | export function FixTagSpaceCloseTag(input: string): string {
36 | // Close tag remove space on inside
37 | return input.replace(/ (<\/[\w]+>)/g, "$1");
38 | }
39 |
40 | export function Compose(functions: Array<(input: T) => T>, input: T): T {
41 | return functions.reduce((acc, cur) => cur(acc), input);
42 | }
43 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { QuillHtmlEditButtonOptions } from "./options";
2 | import { htmlEditButton } from "./quill.htmlEditButton";
3 |
4 | export { QuillHtmlEditButtonOptions };
5 | export { htmlEditButton };
6 |
--------------------------------------------------------------------------------
/src/logger.ts:
--------------------------------------------------------------------------------
1 | export class QuillHtmlLogger {
2 | private debug = false;
3 |
4 | setDebug(debug: boolean) {
5 | this.debug = debug;
6 | }
7 |
8 | prefixString() {
9 | return `> quill-html-edit-button: `;
10 | }
11 | get log() {
12 | if (!this.debug) {
13 | return (...args: any) => {};
14 | }
15 | const boundLogFn = console.log.bind(console, this.prefixString());
16 | return boundLogFn;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/options.ts:
--------------------------------------------------------------------------------
1 | // prettier-ignore
2 | export interface QuillHtmlEditButtonOptions {
3 | // Flags
4 | debug?: boolean; // default: false
5 | syntax?: boolean; // default: false
6 | // Overlay
7 | closeOnClickOverlay: boolean; // default: true
8 | prependSelector: string; // default: null
9 | // Labels
10 | buttonHTML?: string; // default: "<>"
11 | buttonTitle?: string; // default: "Show HTML source"
12 | msg: string; // default: 'Edit HTML here, when you click "OK" the quill editor\'s contents will be replaced'
13 | okText: string; // default: "Ok"
14 | cancelText: string; // default: "Cancel"
15 | // Quill Modules (for the HTML editor)
16 | editorModules?: {
17 | // Any modules here will be declared in HTML quill editor instance
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/src/quill.htmlEditButton.ts:
--------------------------------------------------------------------------------
1 | import { QuillHtmlLogger } from "./logger";
2 | import "./styles.css";
3 | import Quill from "quill";
4 | import { QuillHtmlEditButtonOptions } from "./options";
5 | import { OutputHTMLParser } from "./html-parser";
6 | import { FormatHTMLStringIndentation } from "./html-formatter";
7 | import { default as Toolbar } from "quill/modules/toolbar";
8 |
9 | function $create(elName: string) {
10 | return document.createElement(elName);
11 | }
12 | function $setAttr(el: HTMLElement, key: string, value: string) {
13 | return el.setAttribute(key, value);
14 | }
15 |
16 | const Logger = new QuillHtmlLogger();
17 |
18 | class htmlEditButton {
19 | constructor(quill: Quill, optionsInput: QuillHtmlEditButtonOptions) {
20 | const options = optionsInput || ({} as QuillHtmlEditButtonOptions);
21 | const debug = !!(options && options.debug);
22 | Logger.setDebug(debug);
23 | Logger.log("logging enabled");
24 | // Add button to all quill toolbar instances
25 | const toolbarModule = quill.getModule("toolbar") as Toolbar;
26 | if (!toolbarModule) {
27 | throw new Error(
28 | 'quill.htmlEditButton requires the "toolbar" module to be included too',
29 | );
30 | }
31 | // this.registerDivModule();
32 | let toolbarEl = toolbarModule.container;
33 | const buttonContainer = $create("span");
34 | $setAttr(buttonContainer, "class", "ql-formats");
35 | const button = $create("button") as HTMLButtonElement;
36 | button.innerHTML = options.buttonHTML || "<>";
37 | button.title = options.buttonTitle || "Show HTML source";
38 | button.type = "button";
39 | const onSave = (html: string) => {
40 | quill.clipboard.dangerouslyPasteHTML(html);
41 | };
42 | button.onclick = function (e) {
43 | e.preventDefault();
44 | launchPopupEditor(quill, options, onSave);
45 | };
46 | buttonContainer.appendChild(button);
47 | toolbarEl?.appendChild(buttonContainer);
48 | }
49 | }
50 |
51 | function launchPopupEditor(
52 | quill: Quill & any,
53 | options: QuillHtmlEditButtonOptions,
54 | saveCallback: (html: string) => void,
55 | ) {
56 | const htmlFromEditor = quill.container.querySelector(".ql-editor").innerHTML;
57 | const popupContainer = $create("div");
58 | const overlayContainer = $create("div");
59 | const msg =
60 | options.msg ||
61 | 'Edit HTML here, when you click "OK" the quill editor\'s contents will be replaced';
62 | const cancelText = options.cancelText || "Cancel";
63 | const okText = options.okText || "Ok";
64 | const closeOnClickOverlay = options.closeOnClickOverlay !== false;
65 |
66 | $setAttr(overlayContainer, "class", "ql-html-overlayContainer");
67 | $setAttr(popupContainer, "class", "ql-html-popupContainer");
68 | const popupTitle = $create("span");
69 | $setAttr(popupTitle, "class", "ql-html-popupTitle");
70 | popupTitle.innerText = msg;
71 | const textContainer = $create("div");
72 | textContainer.appendChild(popupTitle);
73 | $setAttr(textContainer, "class", "ql-html-textContainer");
74 | const codeBlock = $create("pre");
75 | $setAttr(codeBlock, "data-language", "xml");
76 | codeBlock.innerText = FormatHTMLStringIndentation(htmlFromEditor, Logger);
77 | const htmlEditor = $create("div");
78 | $setAttr(htmlEditor, "class", "ql-html-textArea");
79 | const buttonCancel = $create("button");
80 | buttonCancel.innerHTML = cancelText;
81 | $setAttr(buttonCancel, "class", "ql-html-buttonCancel");
82 | const buttonOk = $create("button");
83 | buttonOk.innerHTML = okText;
84 | $setAttr(buttonOk, "class", "ql-html-buttonOk");
85 | const buttonGroup = $create("div");
86 | $setAttr(buttonGroup, "class", "ql-html-buttonGroup");
87 | const prependSelector = document.querySelector(options.prependSelector);
88 |
89 | buttonGroup.appendChild(buttonCancel);
90 | buttonGroup.appendChild(buttonOk);
91 | htmlEditor.appendChild(codeBlock);
92 | textContainer.appendChild(htmlEditor);
93 | textContainer.appendChild(buttonGroup);
94 | popupContainer.appendChild(textContainer);
95 | overlayContainer.appendChild(popupContainer);
96 |
97 | if (prependSelector) {
98 | prependSelector.prepend(overlayContainer);
99 | } else {
100 | document.body.appendChild(overlayContainer);
101 | }
102 |
103 | const modules = options && options.editorModules;
104 | const hasModules = !!modules && !!Object.keys(modules).length;
105 | const modulesSafe = hasModules ? modules : {};
106 | // console.time('new Quill')
107 | const editor = new Quill(htmlEditor, {
108 | modules: {
109 | syntax: options.syntax,
110 | ...modulesSafe,
111 | },
112 | });
113 | // console.timeEnd('new Quill')
114 |
115 | buttonCancel.onclick = function () {
116 | if (prependSelector) {
117 | prependSelector.removeChild(overlayContainer);
118 | } else {
119 | document.body.removeChild(overlayContainer);
120 | }
121 | };
122 |
123 | if (closeOnClickOverlay) {
124 | overlayContainer.onclick = buttonCancel.onclick;
125 | }
126 |
127 | popupContainer.onclick = function (e) {
128 | e.preventDefault();
129 | e.stopPropagation();
130 | };
131 | buttonOk.onclick = function () {
132 | const container = (editor as any).container as HTMLElement;
133 | const qlElement = container.querySelector(".ql-editor") as HTMLDivElement;
134 | const htmlInputFromPopup = qlElement.innerText;
135 | const htmlOutputFormatted = OutputHTMLParser(htmlInputFromPopup);
136 | Logger.log("OutputHTMLParser", { htmlInputFromPopup, htmlOutputFormatted });
137 | saveCallback(htmlOutputFormatted);
138 | if (prependSelector) {
139 | prependSelector.removeChild(overlayContainer);
140 | } else {
141 | document.body.removeChild(overlayContainer);
142 | }
143 | };
144 | }
145 |
146 | (window as any)["htmlEditButton"] = htmlEditButton;
147 | export default htmlEditButton;
148 | export { htmlEditButton };
149 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | .ql-html-overlayContainer {
2 | background: #0000007d;
3 | position: fixed;
4 | top: 0;
5 | left: 0;
6 | right: 0;
7 | bottom: 0;
8 | z-index: 9999;
9 | }
10 |
11 | .ql-html-popupContainer {
12 | background: #ddd;
13 | position: absolute;
14 | top: 5%;
15 | left: 5%;
16 | right: 5%;
17 | bottom: 5%;
18 | border-radius: 10px;
19 | }
20 |
21 | .ql-html-textContainer {
22 | position: relative;
23 | height: calc(100% - 40px);
24 | padding: 20px;
25 | }
26 |
27 | .ql-html-textArea {
28 | background: #fff;
29 | position: absolute;
30 | left: 15px;
31 | width: calc(100% - 30px);
32 | height: calc(100% - 60px) !important;
33 | }
34 |
35 | .ql-html-textArea .ql-syntax {
36 | word-break: break-all;
37 | white-space: pre-wrap;
38 | }
39 |
40 | .ql-html-buttonCancel {
41 | margin-right: 20px;
42 | }
43 |
44 | .ql-html-popupTitle {
45 | margin: 0;
46 | display: block;
47 | font-style: italic;
48 | }
49 |
50 | .ql-html-buttonGroup {
51 | position: absolute;
52 | bottom: 20px;
53 | transform: scale(1.5);
54 | left: calc(50% - 60px);
55 | }
56 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./src",
5 | "strict": true,
6 | "declaration": true,
7 | "module": "commonjs",
8 | "target": "es5",
9 | "allowJs": true,
10 | "checkJs": false,
11 | "moduleResolution": "node",
12 | "allowSyntheticDefaultImports": true,
13 | "esModuleInterop": true,
14 | "skipLibCheck": true,
15 | "outDir": "types",
16 | "lib": ["es2017", "dom"]
17 | },
18 | "include": ["src/index.ts"]
19 | }
20 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const TerserPlugin = require("terser-webpack-plugin");
3 |
4 | const isProd = process.argv.includes("production");
5 |
6 | /**
7 | * @type {import('webpack').Configuration}
8 | */
9 | module.exports = [
10 | {
11 | entry: {
12 | "quill.htmlEditButton": "./src/quill.htmlEditButton.ts",
13 | },
14 | output: {
15 | filename: "[name].min.js",
16 | path: path.resolve(__dirname, "dist"),
17 | libraryTarget: "umd",
18 | publicPath: "/dist/",
19 | },
20 | devServer: {
21 | port: 8001,
22 | hot: true,
23 | static: {
24 | directory: path.join(__dirname, 'src'),
25 | serveIndex: true,
26 | },
27 | devMiddleware: {
28 | writeToDisk: true,
29 | },
30 | },
31 | devtool: isProd ? "source-map" : "inline-source-map",
32 | optimization: {
33 | minimize: true,
34 | minimizer: [
35 | new TerserPlugin({
36 | extractComments: true,
37 | parallel: true,
38 | terserOptions: {
39 | compress: {
40 | drop_console: false,
41 | },
42 | },
43 | }),
44 | ],
45 | },
46 | resolve: {
47 | extensions: [".ts", ".js"],
48 | },
49 | module: {
50 | rules: [
51 | { test: /\.ts$/, use: ["ts-loader"], exclude: /node_modules/ },
52 | {
53 | test: /\.css$/,
54 | use: ["style-loader", "css-loader"],
55 | },
56 | {
57 | test: /\.svg$/,
58 | use: [
59 | {
60 | loader: "html-loader",
61 | options: {
62 | minimize: true,
63 | },
64 | },
65 | ],
66 | },
67 | ],
68 | },
69 | },
70 | ];
71 |
--------------------------------------------------------------------------------