├── .gitignore ├── .vscode └── settings.json ├── .watchmanconfig ├── WebViewQuillJS ├── ActivityOverlay.tsx ├── DebugMessageBox.tsx ├── WebViewQuillJS.tsx ├── WebViewQuillJS.view.tsx ├── assets │ └── index.html ├── index.d.ts ├── index.ts ├── models.ts ├── package.json └── yarn.lock ├── demoApp ├── .expo-shared │ └── assets.json ├── .gitignore ├── App.tsx ├── app.json ├── assets │ ├── icon.png │ ├── postTSC.282b31d6.css │ ├── postTSC.282b31d6.css.map │ ├── postTSC.a740dd4b.js │ ├── postTSC.a740dd4b.js.map │ └── splash.png ├── babel.config.js ├── gulpfile.js ├── package.json ├── tsconfig.json ├── yarn-error.log └── yarn.lock ├── expo-qr-code.png ├── html ├── .gitignore ├── README.md ├── dist │ └── index.html ├── gulpfile.js ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.test.tsx │ ├── QuillComponent.tsx │ ├── QuillComponent.view.tsx │ ├── index.tsx │ ├── logo.svg │ ├── models.ts │ ├── react-app-env.d.ts │ └── setupTeststjs ├── tsconfig.json └── yarn.lock └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.ppt* 5 | build/**/* 6 | dist/**/* 7 | 8 | /app/node_modules 9 | /app/.expo 10 | /html/node_modules 11 | /HTML/src/node_modules/**/* 12 | /WebViewQuillJS/node_modules 13 | /WebViewQuillJS/.jest/ 14 | /.vscode 15 | /ejected-html 16 | /BasicJavascriptWebviewPackage 17 | /BasicReactNativePackage 18 | /BasicReactNativeTypescriptPackage 19 | /Deprecated-SampleReactNativePackage 20 | /Deprecated-WebViewQuillJS 21 | /WebViewQuillJSNoTypescript 22 | /htmlUnpkg/node_modules -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /WebViewQuillJS/ActivityOverlay.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, ActivityIndicator, StyleSheet } from "react-native"; 3 | 4 | interface Props {} 5 | 6 | export const ActivityOverlay: React.FC = () => { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | }; 15 | 16 | const styles = StyleSheet.create({ 17 | activityOverlayStyle: { 18 | ...StyleSheet.absoluteFillObject, 19 | backgroundColor: "rgba(255, 255, 255, .5)", 20 | display: "flex", 21 | justifyContent: "center", 22 | alignContent: "center", 23 | borderRadius: 5 24 | }, 25 | activityIndicatorContainer: { 26 | backgroundColor: "lightgray", 27 | padding: 10, 28 | borderRadius: 50, 29 | alignSelf: "center", 30 | shadowColor: "#000000", 31 | shadowOffset: { 32 | width: 0, 33 | height: 3 34 | }, 35 | shadowRadius: 5, 36 | shadowOpacity: 1.0 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /WebViewQuillJS/DebugMessageBox.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ScrollView, Text, View } from "react-native"; 3 | 4 | export interface Props { 5 | debugMessages: string[]; 6 | doShowDebugMessages: boolean; 7 | } 8 | 9 | const DebugMessageBox = ({ 10 | debugMessages = [], 11 | doShowDebugMessages = false 12 | }: Props) => { 13 | if (doShowDebugMessages) { 14 | return ( 15 | 27 | 28 | {debugMessages.map((msg, idx) => { 29 | if (typeof msg === "object") { 30 | return ( 31 | {`- ${JSON.stringify( 32 | msg 33 | )}`} 34 | ); 35 | } 36 | return {`- ${msg}`}; 37 | })} 38 | 39 | 40 | ); 41 | } 42 | return null; 43 | }; 44 | 45 | export default DebugMessageBox; 46 | -------------------------------------------------------------------------------- /WebViewQuillJS/WebViewQuillJS.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { WebView } from "react-native-webview"; 3 | import AssetUtils from "expo-asset-utils"; 4 | import { Asset } from "expo-asset"; 5 | import WebViewQuillJSView from "./WebViewQuillJS.view"; 6 | 7 | import { ActivityOverlay } from "./ActivityOverlay"; 8 | import * as FileSystem from "expo-file-system"; 9 | import { 10 | WebviewQuillJSMessage, 11 | WebviewQuillJSEvents, 12 | StartupMessage, 13 | WebViewQuillJSProps 14 | } from "./models"; 15 | 16 | // @ts-ignore node types 17 | const INDEX_FILE_PATH = require(`./assets/index.html`); 18 | 19 | interface State { 20 | debugMessages: string[]; 21 | isLoading: boolean; 22 | webviewContent: string; 23 | } 24 | 25 | class WebViewQuillJS extends React.Component { 26 | private webViewRef: any; 27 | static defaultProps = { 28 | backgroundColor: "#FAEBD7", 29 | doShowDebugMessages: false, 30 | loadingIndicator: () => { 31 | return null; 32 | }, 33 | onError: (syntheticEvent: any) => {}, 34 | onMessageReceived: (message: WebviewQuillJSMessage) => {}, 35 | onLoadEnd: () => {}, 36 | onLoadStart: () => {} 37 | }; 38 | 39 | constructor(props: any) { 40 | super(props); 41 | this.state = { 42 | debugMessages: [], 43 | isLoading: null, 44 | webviewContent: null 45 | }; 46 | this.webViewRef = null; 47 | } 48 | 49 | componentDidMount = () => { 50 | this.loadHTMLFile(); 51 | }; 52 | 53 | private loadHTMLFile = async () => { 54 | try { 55 | let asset: Asset = await AssetUtils.resolveAsync(INDEX_FILE_PATH); 56 | let fileString: string = await FileSystem.readAsStringAsync( 57 | asset.localUri 58 | ); 59 | 60 | this.setState({ webviewContent: fileString }); 61 | } catch (error) { 62 | console.warn(error); 63 | console.warn("Unable to resolve index file"); 64 | } 65 | }; 66 | 67 | componentDidUpdate = (prevProps: WebViewQuillJSProps, prevState: State) => { 68 | const { webviewContent } = this.state; 69 | const { content } = this.props; 70 | if (!prevState.webviewContent && webviewContent) { 71 | this.updateDebugMessages("file loaded"); 72 | } 73 | if (content !== prevProps.content) { 74 | this.sendMessage({ content }); 75 | } 76 | }; 77 | 78 | // Handle messages received from webview contents 79 | private handleMessage = (data: string) => { 80 | const { onMessageReceived } = this.props; 81 | 82 | if (onMessageReceived) { 83 | let message: WebviewQuillJSMessage = JSON.parse(data); 84 | this.updateDebugMessages(`received: ${JSON.stringify(message)}`); 85 | if (message.msg === WebviewQuillJSEvents.QUILLJS_COMPONENT_MOUNTED) { 86 | this.sendStartupMessage(); 87 | } 88 | 89 | onMessageReceived(message); 90 | } 91 | }; 92 | 93 | // Send message to webview 94 | private sendMessage = (payload: object) => { 95 | this.updateDebugMessages(`sending: ${payload}`); 96 | 97 | this.webViewRef?.injectJavaScript( 98 | `window.postMessage(${JSON.stringify(payload)}, '*');` 99 | ); 100 | }; 101 | 102 | // Send a startup message with initalizing values to the map 103 | private sendStartupMessage = () => { 104 | const { 105 | backgroundColor, 106 | content, 107 | doShowQuillComponentDebugMessages, 108 | isReadOnly 109 | } = this.props; 110 | 111 | const startupMessage: StartupMessage = { 112 | backgroundColor, 113 | content, 114 | doShowQuillComponentDebugMessages, 115 | isReadOnly 116 | }; 117 | 118 | this.setState({ isLoading: false }); 119 | this.updateDebugMessages("sending startup message"); 120 | 121 | this.webViewRef.injectJavaScript( 122 | `window.postMessage(${JSON.stringify(startupMessage)}, '*');` 123 | ); 124 | }; 125 | 126 | // Add a new debug message to the debug message array 127 | private updateDebugMessages = (debugMessage: string) => { 128 | this.setState({ 129 | debugMessages: [...this.state.debugMessages, debugMessage] 130 | }); 131 | }; 132 | 133 | private onError = (syntheticEvent: any) => { 134 | this.props.onError(syntheticEvent); 135 | }; 136 | private onLoadEnd = () => { 137 | this.setState({ isLoading: false }); 138 | this.props.onLoadEnd(); 139 | }; 140 | private onLoadStart = () => { 141 | this.setState({ isLoading: true }); 142 | this.props.onLoadStart(); 143 | }; 144 | 145 | // Output rendered item to screen 146 | render() { 147 | const { debugMessages, webviewContent } = this.state; 148 | const { 149 | backgroundColor, 150 | doShowDebugMessages, 151 | loadingIndicator 152 | } = this.props; 153 | 154 | if (webviewContent) { 155 | return ( 156 | { 167 | this.webViewRef = ref; 168 | }} 169 | /> 170 | ); 171 | } else { 172 | return null; 173 | } 174 | } 175 | } 176 | 177 | export default WebViewQuillJS; 178 | -------------------------------------------------------------------------------- /WebViewQuillJS/WebViewQuillJS.view.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from "react"; 2 | // @ts-ignore 3 | import { StyleSheet, View, NativeSyntheticEvent } from "react-native"; 4 | import { WebView } from "react-native-webview"; 5 | import DebugMessageBox from "./DebugMessageBox"; 6 | import { WebViewError } from "react-native-webview/lib/WebViewTypes"; 7 | 8 | export interface Props { 9 | backgroundColor: string; 10 | debugMessages: string[]; 11 | doShowDebugMessages: boolean; 12 | handleMessage: (data: string) => void; 13 | webviewContent: string; 14 | loadingIndicator: () => ReactElement; 15 | onError: (syntheticEvent: NativeSyntheticEvent) => void; 16 | onLoadEnd: () => void; 17 | onLoadStart: () => void; 18 | setWebViewRef: (ref: WebView) => void; 19 | } 20 | 21 | class WebViewQuillJSView extends React.Component { 22 | constructor(props) { 23 | super(props); 24 | } 25 | 26 | render() { 27 | const { 28 | backgroundColor, 29 | debugMessages, 30 | doShowDebugMessages, 31 | handleMessage, 32 | webviewContent, 33 | loadingIndicator, 34 | onError, 35 | onLoadEnd, 36 | onLoadStart, 37 | setWebViewRef 38 | } = this.props; 39 | return ( 40 | 47 | {webviewContent ? ( 48 | { 55 | setWebViewRef(component); 56 | }} 57 | javaScriptEnabled={true} 58 | onLoadEnd={onLoadEnd} 59 | onLoadStart={onLoadStart} 60 | onMessage={event => { 61 | if (event && event.nativeEvent && event.nativeEvent.data) { 62 | handleMessage(event.nativeEvent.data); 63 | } 64 | }} 65 | domStorageEnabled={true} 66 | startInLoadingState={true} 67 | onError={onError} 68 | originWhitelist={["*"]} 69 | renderLoading={loadingIndicator || null} 70 | source={{ 71 | html: webviewContent 72 | }} 73 | allowFileAccess={true} 74 | allowUniversalAccessFromFileURLs={true} 75 | allowFileAccessFromFileURLs={true} 76 | /> 77 | ) : null} 78 | 82 | 83 | ); 84 | } 85 | } 86 | 87 | export default WebViewQuillJSView; 88 | -------------------------------------------------------------------------------- /WebViewQuillJS/index.d.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export as namespace ReactNativeWebViewQuillJS; 4 | 5 | export { default as WebViewQuillJS } from "./WebViewQuillJS"; 6 | export { 7 | WebviewQuillJSEvents, 8 | WebviewQuillJSMessage, 9 | WebViewQuillJSProps 10 | } from "./models"; 11 | 12 | import WebViewQuillJS from "./WebViewQuillJS"; 13 | export default WebViewQuillJS; 14 | -------------------------------------------------------------------------------- /WebViewQuillJS/index.ts: -------------------------------------------------------------------------------- 1 | export { default as WebViewQuillJS } from "./WebViewQuillJS"; 2 | export { 3 | WebviewQuillJSEvents, 4 | WebviewQuillJSMessage, 5 | WebViewQuillJSProps 6 | } from "./models"; 7 | 8 | import WebViewQuillJS from "./WebViewQuillJS"; 9 | export default WebViewQuillJS; 10 | -------------------------------------------------------------------------------- /WebViewQuillJS/models.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as Quill from "quill"; 3 | 4 | export interface WebViewQuillJSProps { 5 | backgroundColor?: string; 6 | content?: Quill.Delta | string | object; 7 | defaultContent?: Quill.Delta | string | object; 8 | doShowDebugMessages?: boolean; 9 | doShowQuillComponentDebugMessages?: boolean; 10 | isReadOnly?: boolean; 11 | loadingIndicator?: () => React.ReactElement; 12 | onError?: (syntheticEvent: any) => void; 13 | onLoadEnd?: () => void; 14 | onLoadStart?: () => void; 15 | onMessageReceived?: (message: any) => void; 16 | } 17 | export enum WebviewQuillJSEvents { 18 | QUILLJS_COMPONENT_MOUNTED = "QUILLJS_COMPONENT_MOUNTED", 19 | DOCUMENT_EVENT_LISTENER_ADDED = "DOCUMENT_EVENT_LISTENER_ADDED", 20 | WINDOW_EVENT_LISTENER_ADDED = "WINDOW_EVENT_LISTENER_ADDED", 21 | UNABLE_TO_ADD_EVENT_LISTENER = "UNABLE_TO_ADD_EVENT_LISTENER", 22 | DOCUMENT_EVENT_LISTENER_REMOVED = "DOCUMENT_EVENT_LISTENER_REMOVED", 23 | WINDOW_EVENT_LISTENER_REMOVED = "WINDOW_EVENT_LISTENER_REMOVED", 24 | ON_CHANGE = "ON_CHANGE", 25 | ON_CHANGE_SELECTION = "ON_CHANGE_SELECTION", 26 | ON_FOCUS = "ON_FOCUS", 27 | ON_BLUR = "ON_BLUR", 28 | ON_KEY_PRESS = "ON_KEY_PRESS", 29 | ON_KEY_DOWN = "ON_KEY_DOWN", 30 | ON_KEY_UP = "ON_KEY_UP" 31 | } 32 | 33 | export type WebviewQuillJSMessage = { 34 | event?: WebviewQuillJSEvents; 35 | msg?: string; 36 | error?: string; 37 | payload?: any; 38 | }; 39 | 40 | export type StartupMessage = { 41 | backgroundColor?: string; 42 | content?: Quill.Delta | string | object; 43 | doShowQuillComponentDebugMessages?: boolean; 44 | isReadOnly?: boolean; 45 | }; 46 | -------------------------------------------------------------------------------- /WebViewQuillJS/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-webview-quilljs", 3 | "version": "2.0.0", 4 | "description": "A React Native component that uses a React Native WebView to provide a Quill JS viewer and editor.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/reggie3/react-native-webview-quilljs.git" 8 | }, 9 | "keywords": [ 10 | "text editor", 11 | "react-native", 12 | "quill", 13 | "quilljs", 14 | "expo", 15 | "webview" 16 | ], 17 | "main": "index.ts", 18 | "typings": "index.d.ts", 19 | "scripts": { 20 | "test": "echo \"Error: no test specified\" && exit 1" 21 | }, 22 | "author": "", 23 | "license": "ISC", 24 | "dependencies": { 25 | "expo-asset": "^7.0.0", 26 | "expo-asset-utils": "^1.2.0", 27 | "expo-file-system": "^8.0.0", 28 | "lodash.isequal": "^4.5.0", 29 | "react-dom": "^16.10.2", 30 | "react-native-webview": "^8.0.3", 31 | "react-quill": "^1.3.3" 32 | }, 33 | "peerDependencies": { 34 | "react": "^16.9.0" 35 | }, 36 | "devDependencies": { 37 | "@babel/core": "^7.0.0-0", 38 | "@babel/plugin-proposal-class-properties": "^7.5.5", 39 | "@babel/preset-react": "^7.6.3", 40 | "@types/node": "^13.1.6", 41 | "babel-plugin-transform-class-properties": "^6.24.1", 42 | "gulp": "4", 43 | "gulp-run": "^1.7.1", 44 | "parcel-bundler": "^1.12.4", 45 | "parcel-plugin-inliner": "^1.0.10", 46 | "typescript": "^3.6.4" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /demoApp/.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "f9155ac790fd02fadcdeca367b02581c04a353aa6d5aa84409a59f6804c87acd": true, 3 | "89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44": true 4 | } -------------------------------------------------------------------------------- /demoApp/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | web-report/ 12 | 13 | # macOS 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /demoApp/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | StyleSheet, 4 | View, 5 | Text, 6 | KeyboardAvoidingView, 7 | Platform, 8 | SafeAreaView 9 | } from "react-native"; 10 | import WebViewQuillJS from "react-native-webview-quilljs"; 11 | 12 | interface State { 13 | content: any; 14 | defaultContent: any; 15 | } 16 | 17 | export default class App extends React.Component { 18 | constructor(props: any) { 19 | super(props); 20 | this.state = { 21 | content: null, 22 | defaultContent: { 23 | ops: [ 24 | { insert: "This", attributes: { bold: true } }, 25 | { insert: " is " }, 26 | { 27 | insert: "react-native-webview-quill-js", 28 | attributes: { color: "#fcc" } 29 | } 30 | ] 31 | } 32 | }; 33 | } 34 | 35 | onMessageReceived = (message: any) => { 36 | const { instruction, payload } = message; 37 | if (payload?.delta) { 38 | this.setState({ content: payload.delta }); 39 | } 40 | }; 41 | 42 | render() { 43 | return ( 44 | 45 | 46 | 47 | React Native Webview Quill JS Demo 48 | 49 | 50 | 55 | 56 | 61 | 62 | 63 | 68 | 69 | 70 | 71 | ); 72 | } 73 | } 74 | 75 | const styles = StyleSheet.create({ 76 | container: { 77 | flex: 1, 78 | backgroundColor: "aliceblue" 79 | }, 80 | header: { 81 | height: 60, 82 | backgroundColor: "dodgerblue", 83 | paddingHorizontal: 10, 84 | paddingTop: 30, 85 | width: "100%" 86 | }, 87 | headerText: { 88 | color: "white", 89 | fontSize: 18, 90 | fontWeight: "600" 91 | } 92 | }); 93 | -------------------------------------------------------------------------------- /demoApp/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "description": "Demonstration of the react-native-webview-quilljs component.", 4 | "name": "react-native-webview-quilljs-demo", 5 | "slug": "react-native-webview-quilljs-demo", 6 | "privacy": "public", 7 | "sdkVersion": "36.0.0", 8 | "platforms": ["ios", "android", "web"], 9 | "version": "1.0.0", 10 | "orientation": "portrait", 11 | "icon": "./assets/icon.png", 12 | "splash": { 13 | "image": "./assets/splash.png", 14 | "resizeMode": "contain", 15 | "backgroundColor": "#ffffff" 16 | }, 17 | "updates": { 18 | "fallbackToCacheTimeout": 0 19 | }, 20 | "assetBundlePatterns": ["**/*"], 21 | "ios": { 22 | "supportsTablet": true 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /demoApp/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reggie3/react-native-webview-quilljs/73405ca82f90b6acd432dabd659f5e5b2f653c17/demoApp/assets/icon.png -------------------------------------------------------------------------------- /demoApp/assets/postTSC.282b31d6.css: -------------------------------------------------------------------------------- 1 | body{margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace} 2 | /*! 3 | * Quill Editor v1.3.0 4 | * https://quilljs.com/ 5 | * Copyright (c) 2014, Jason Chen 6 | * Copyright (c) 2013, salesforce.com 7 | */.ql-container{box-sizing:border-box;font-family:Helvetica,Arial,sans-serif;font-size:13px;height:100%;margin:0;position:relative}.ql-container.ql-disabled .ql-tooltip{visibility:hidden}.ql-container.ql-disabled .ql-editor ul[data-checked]>li:before{pointer-events:none}.ql-clipboard{left:-100000px;height:1px;overflow-y:hidden;position:absolute;top:50%}.ql-clipboard p{margin:0;padding:0}.ql-editor{box-sizing:border-box;line-height:1.42;height:100%;outline:none;overflow-y:auto;padding:12px 15px;tab-size:4;-moz-tab-size:4;text-align:left;white-space:pre-wrap;word-wrap:break-word}.ql-editor>*{cursor:text}.ql-editor blockquote,.ql-editor h1,.ql-editor h2,.ql-editor h3,.ql-editor h4,.ql-editor h5,.ql-editor h6,.ql-editor ol,.ql-editor p,.ql-editor pre,.ql-editor ul{margin:0;padding:0;counter-reset:list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol,.ql-editor ul{padding-left:1.5em}.ql-editor ol>li,.ql-editor ul>li{list-style-type:none}.ql-editor ul>li:before{content:"\2022"}.ql-editor ul[data-checked=false],.ql-editor ul[data-checked=true]{pointer-events:none}.ql-editor ul[data-checked=false]>li *,.ql-editor ul[data-checked=true]>li *{pointer-events:all}.ql-editor ul[data-checked=false]>li:before,.ql-editor ul[data-checked=true]>li:before{color:#777;cursor:pointer;pointer-events:all}.ql-editor ul[data-checked=true]>li:before{content:"\2611"}.ql-editor ul[data-checked=false]>li:before{content:"\2610"}.ql-editor li:before{display:inline-block;white-space:nowrap;width:1.2em}.ql-editor li:not(.ql-direction-rtl):before{margin-left:-1.5em;margin-right:.3em;text-align:right}.ql-editor li.ql-direction-rtl:before{margin-left:.3em;margin-right:-1.5em}.ql-editor ol li:not(.ql-direction-rtl),.ql-editor ul li:not(.ql-direction-rtl){padding-left:1.5em}.ql-editor ol li.ql-direction-rtl,.ql-editor ul li.ql-direction-rtl{padding-right:1.5em}.ql-editor ol li{counter-reset:list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;counter-increment:list-0}.ql-editor ol li:before{content:counter(list-0,decimal) ". "}.ql-editor ol li.ql-indent-1{counter-increment:list-1}.ql-editor ol li.ql-indent-1:before{content:counter(list-1,lower-alpha) ". "}.ql-editor ol li.ql-indent-1{counter-reset:list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-2{counter-increment:list-2}.ql-editor ol li.ql-indent-2:before{content:counter(list-2,lower-roman) ". "}.ql-editor ol li.ql-indent-2{counter-reset:list-3 list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-3{counter-increment:list-3}.ql-editor ol li.ql-indent-3:before{content:counter(list-3,decimal) ". "}.ql-editor ol li.ql-indent-3{counter-reset:list-4 list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-4{counter-increment:list-4}.ql-editor ol li.ql-indent-4:before{content:counter(list-4,lower-alpha) ". "}.ql-editor ol li.ql-indent-4{counter-reset:list-5 list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-5{counter-increment:list-5}.ql-editor ol li.ql-indent-5:before{content:counter(list-5,lower-roman) ". "}.ql-editor ol li.ql-indent-5{counter-reset:list-6 list-7 list-8 list-9}.ql-editor ol li.ql-indent-6{counter-increment:list-6}.ql-editor ol li.ql-indent-6:before{content:counter(list-6,decimal) ". "}.ql-editor ol li.ql-indent-6{counter-reset:list-7 list-8 list-9}.ql-editor ol li.ql-indent-7{counter-increment:list-7}.ql-editor ol li.ql-indent-7:before{content:counter(list-7,lower-alpha) ". "}.ql-editor ol li.ql-indent-7{counter-reset:list-8 list-9}.ql-editor ol li.ql-indent-8{counter-increment:list-8}.ql-editor ol li.ql-indent-8:before{content:counter(list-8,lower-roman) ". "}.ql-editor ol li.ql-indent-8{counter-reset:list-9}.ql-editor ol li.ql-indent-9{counter-increment:list-9}.ql-editor ol li.ql-indent-9:before{content:counter(list-9,decimal) ". "}.ql-editor .ql-indent-1:not(.ql-direction-rtl){padding-left:3em}.ql-editor li.ql-indent-1:not(.ql-direction-rtl){padding-left:4.5em}.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right{padding-right:3em}.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right{padding-right:4.5em}.ql-editor .ql-indent-2:not(.ql-direction-rtl){padding-left:6em}.ql-editor li.ql-indent-2:not(.ql-direction-rtl){padding-left:7.5em}.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right{padding-right:6em}.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right{padding-right:7.5em}.ql-editor .ql-indent-3:not(.ql-direction-rtl){padding-left:9em}.ql-editor li.ql-indent-3:not(.ql-direction-rtl){padding-left:10.5em}.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right{padding-right:9em}.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right{padding-right:10.5em}.ql-editor .ql-indent-4:not(.ql-direction-rtl){padding-left:12em}.ql-editor li.ql-indent-4:not(.ql-direction-rtl){padding-left:13.5em}.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right{padding-right:12em}.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right{padding-right:13.5em}.ql-editor .ql-indent-5:not(.ql-direction-rtl){padding-left:15em}.ql-editor li.ql-indent-5:not(.ql-direction-rtl){padding-left:16.5em}.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right{padding-right:15em}.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right{padding-right:16.5em}.ql-editor .ql-indent-6:not(.ql-direction-rtl){padding-left:18em}.ql-editor li.ql-indent-6:not(.ql-direction-rtl){padding-left:19.5em}.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right{padding-right:18em}.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right{padding-right:19.5em}.ql-editor .ql-indent-7:not(.ql-direction-rtl){padding-left:21em}.ql-editor li.ql-indent-7:not(.ql-direction-rtl){padding-left:22.5em}.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right{padding-right:21em}.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right{padding-right:22.5em}.ql-editor .ql-indent-8:not(.ql-direction-rtl){padding-left:24em}.ql-editor li.ql-indent-8:not(.ql-direction-rtl){padding-left:25.5em}.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right{padding-right:24em}.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right{padding-right:25.5em}.ql-editor .ql-indent-9:not(.ql-direction-rtl){padding-left:27em}.ql-editor li.ql-indent-9:not(.ql-direction-rtl){padding-left:28.5em}.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right{padding-right:27em}.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right{padding-right:28.5em}.ql-editor .ql-video{display:block;max-width:100%}.ql-editor .ql-video.ql-align-center{margin:0 auto}.ql-editor .ql-video.ql-align-right{margin:0 0 0 auto}.ql-editor .ql-bg-black{background-color:#000}.ql-editor .ql-bg-red{background-color:#e60000}.ql-editor .ql-bg-orange{background-color:#f90}.ql-editor .ql-bg-yellow{background-color:#ff0}.ql-editor .ql-bg-green{background-color:#008a00}.ql-editor .ql-bg-blue{background-color:#06c}.ql-editor .ql-bg-purple{background-color:#93f}.ql-editor .ql-color-white{color:#fff}.ql-editor .ql-color-red{color:#e60000}.ql-editor .ql-color-orange{color:#f90}.ql-editor .ql-color-yellow{color:#ff0}.ql-editor .ql-color-green{color:#008a00}.ql-editor .ql-color-blue{color:#06c}.ql-editor .ql-color-purple{color:#93f}.ql-editor .ql-font-serif{font-family:Georgia,Times New Roman,serif}.ql-editor .ql-font-monospace{font-family:Monaco,Courier New,monospace}.ql-editor .ql-size-small{font-size:.75em}.ql-editor .ql-size-large{font-size:1.5em}.ql-editor .ql-size-huge{font-size:2.5em}.ql-editor .ql-direction-rtl{direction:rtl;text-align:inherit}.ql-editor .ql-align-center{text-align:center}.ql-editor .ql-align-justify{text-align:justify}.ql-editor .ql-align-right{text-align:right}.ql-editor .ql-embed-selected{border:1px solid #777;user-select:none}.ql-editor.ql-blank:before{color:rgba(0,0,0,.6);content:attr(data-placeholder);font-style:italic;pointer-events:none;position:absolute}.ql-snow.ql-toolbar:after,.ql-snow .ql-toolbar:after{clear:both;content:"";display:table}.ql-snow.ql-toolbar button,.ql-snow .ql-toolbar button{background:none;border:none;cursor:pointer;display:inline-block;float:left;height:24px;padding:3px 5px;width:28px}.ql-snow.ql-toolbar button svg,.ql-snow .ql-toolbar button svg{float:left;height:100%}.ql-snow.ql-toolbar button:active:hover,.ql-snow .ql-toolbar button:active:hover{outline:none}.ql-snow.ql-toolbar input.ql-image[type=file],.ql-snow .ql-toolbar input.ql-image[type=file]{display:none}.ql-snow.ql-toolbar .ql-picker-item.ql-selected,.ql-snow .ql-toolbar .ql-picker-item.ql-selected,.ql-snow.ql-toolbar .ql-picker-item:hover,.ql-snow .ql-toolbar .ql-picker-item:hover,.ql-snow.ql-toolbar .ql-picker-label.ql-active,.ql-snow .ql-toolbar .ql-picker-label.ql-active,.ql-snow.ql-toolbar .ql-picker-label:hover,.ql-snow .ql-toolbar .ql-picker-label:hover,.ql-snow.ql-toolbar button.ql-active,.ql-snow .ql-toolbar button.ql-active,.ql-snow.ql-toolbar button:focus,.ql-snow .ql-toolbar button:focus,.ql-snow.ql-toolbar button:hover,.ql-snow .ql-toolbar button:hover{color:#06c}.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill,.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill,.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill,.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,.ql-snow.ql-toolbar button.ql-active .ql-fill,.ql-snow .ql-toolbar button.ql-active .ql-fill,.ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill,.ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill,.ql-snow.ql-toolbar button:focus .ql-fill,.ql-snow .ql-toolbar button:focus .ql-fill,.ql-snow.ql-toolbar button:focus .ql-stroke.ql-fill,.ql-snow .ql-toolbar button:focus .ql-stroke.ql-fill,.ql-snow.ql-toolbar button:hover .ql-fill,.ql-snow .ql-toolbar button:hover .ql-fill,.ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill,.ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill{fill:#06c}.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke,.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke,.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter,.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter,.ql-snow.ql-toolbar button.ql-active .ql-stroke,.ql-snow .ql-toolbar button.ql-active .ql-stroke,.ql-snow.ql-toolbar button.ql-active .ql-stroke-miter,.ql-snow .ql-toolbar button.ql-active .ql-stroke-miter,.ql-snow.ql-toolbar button:focus .ql-stroke,.ql-snow .ql-toolbar button:focus .ql-stroke,.ql-snow.ql-toolbar button:focus .ql-stroke-miter,.ql-snow .ql-toolbar button:focus .ql-stroke-miter,.ql-snow.ql-toolbar button:hover .ql-stroke,.ql-snow .ql-toolbar button:hover .ql-stroke,.ql-snow.ql-toolbar button:hover .ql-stroke-miter,.ql-snow .ql-toolbar button:hover .ql-stroke-miter{stroke:#06c}@media (pointer:coarse){.ql-snow.ql-toolbar button:hover:not(.ql-active),.ql-snow .ql-toolbar button:hover:not(.ql-active){color:#444}.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill,.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-fill,.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill,.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill{fill:#444}.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke,.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke,.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter,.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter{stroke:#444}}.ql-snow,.ql-snow *{box-sizing:border-box}.ql-snow .ql-hidden{display:none}.ql-snow .ql-out-bottom,.ql-snow .ql-out-top{visibility:hidden}.ql-snow .ql-tooltip{position:absolute;transform:translateY(10px)}.ql-snow .ql-tooltip a{cursor:pointer;text-decoration:none}.ql-snow .ql-tooltip.ql-flip{transform:translateY(-10px)}.ql-snow .ql-formats{display:inline-block;vertical-align:middle}.ql-snow .ql-formats:after{clear:both;content:"";display:table}.ql-snow .ql-stroke{fill:none;stroke:#444;stroke-linecap:round;stroke-linejoin:round;stroke-width:2}.ql-snow .ql-stroke-miter{fill:none;stroke:#444;stroke-miterlimit:10;stroke-width:2}.ql-snow .ql-fill,.ql-snow .ql-stroke.ql-fill{fill:#444}.ql-snow .ql-empty{fill:none}.ql-snow .ql-even{fill-rule:evenodd}.ql-snow .ql-stroke.ql-thin,.ql-snow .ql-thin{stroke-width:1}.ql-snow .ql-transparent{opacity:.4}.ql-snow .ql-direction svg:last-child{display:none}.ql-snow .ql-direction.ql-active svg:last-child{display:inline}.ql-snow .ql-direction.ql-active svg:first-child{display:none}.ql-snow .ql-editor h1{font-size:2em}.ql-snow .ql-editor h2{font-size:1.5em}.ql-snow .ql-editor h3{font-size:1.17em}.ql-snow .ql-editor h4{font-size:1em}.ql-snow .ql-editor h5{font-size:.83em}.ql-snow .ql-editor h6{font-size:.67em}.ql-snow .ql-editor a{text-decoration:underline}.ql-snow .ql-editor blockquote{border-left:4px solid #ccc;margin-bottom:5px;margin-top:5px;padding-left:16px}.ql-snow .ql-editor code,.ql-snow .ql-editor pre{background-color:#f0f0f0;border-radius:3px}.ql-snow .ql-editor pre{white-space:pre-wrap;margin-bottom:5px;margin-top:5px;padding:5px 10px}.ql-snow .ql-editor code{font-size:85%;padding-bottom:2px;padding-top:2px}.ql-snow .ql-editor code:after,.ql-snow .ql-editor code:before{content:"\A0";letter-spacing:-2px}.ql-snow .ql-editor pre.ql-syntax{background-color:#23241f;color:#f8f8f2;overflow:visible}.ql-snow .ql-editor img{max-width:100%}.ql-snow .ql-picker{color:#444;display:inline-block;float:left;font-size:14px;font-weight:500;height:24px;position:relative;vertical-align:middle}.ql-snow .ql-picker-label{cursor:pointer;display:inline-block;height:100%;padding-left:8px;padding-right:2px;position:relative;width:100%}.ql-snow .ql-picker-label:before{display:inline-block;line-height:22px}.ql-snow .ql-picker-options{background-color:#fff;display:none;min-width:100%;padding:4px 8px;position:absolute;white-space:nowrap}.ql-snow .ql-picker-options .ql-picker-item{cursor:pointer;display:block;padding-bottom:5px;padding-top:5px}.ql-snow .ql-picker.ql-expanded .ql-picker-label{color:#ccc;z-index:2}.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill{fill:#ccc}.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke{stroke:#ccc}.ql-snow .ql-picker.ql-expanded .ql-picker-options{display:block;margin-top:-1px;top:100%;z-index:1}.ql-snow .ql-color-picker,.ql-snow .ql-icon-picker{width:28px}.ql-snow .ql-color-picker .ql-picker-label,.ql-snow .ql-icon-picker .ql-picker-label{padding:2px 4px}.ql-snow .ql-color-picker .ql-picker-label svg,.ql-snow .ql-icon-picker .ql-picker-label svg{right:4px}.ql-snow .ql-icon-picker .ql-picker-options{padding:4px 0}.ql-snow .ql-icon-picker .ql-picker-item{height:24px;width:24px;padding:2px 4px}.ql-snow .ql-color-picker .ql-picker-options{padding:3px 5px;width:152px}.ql-snow .ql-color-picker .ql-picker-item{border:1px solid transparent;float:left;height:16px;margin:2px;padding:0;width:16px}.ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg{position:absolute;margin-top:-9px;right:0;top:50%;width:18px}.ql-snow .ql-picker.ql-font .ql-picker-item[data-label]:not([data-label=""]):before,.ql-snow .ql-picker.ql-font .ql-picker-label[data-label]:not([data-label=""]):before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-label]:not([data-label=""]):before,.ql-snow .ql-picker.ql-header .ql-picker-label[data-label]:not([data-label=""]):before,.ql-snow .ql-picker.ql-size .ql-picker-item[data-label]:not([data-label=""]):before,.ql-snow .ql-picker.ql-size .ql-picker-label[data-label]:not([data-label=""]):before{content:attr(data-label)}.ql-snow .ql-picker.ql-header{width:98px}.ql-snow .ql-picker.ql-header .ql-picker-item:before,.ql-snow .ql-picker.ql-header .ql-picker-label:before{content:"Normal"}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]:before,.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]:before{content:"Heading 1"}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]:before,.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]:before{content:"Heading 2"}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]:before,.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]:before{content:"Heading 3"}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]:before,.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]:before{content:"Heading 4"}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]:before,.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]:before{content:"Heading 5"}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]:before,.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]:before{content:"Heading 6"}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]:before{font-size:2em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]:before{font-size:1.5em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]:before{font-size:1.17em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]:before{font-size:1em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]:before{font-size:.83em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]:before{font-size:.67em}.ql-snow .ql-picker.ql-font{width:108px}.ql-snow .ql-picker.ql-font .ql-picker-item:before,.ql-snow .ql-picker.ql-font .ql-picker-label:before{content:"Sans Serif"}.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]:before,.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]:before{content:"Serif"}.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]:before,.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]:before{content:"Monospace"}.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]:before{font-family:Georgia,Times New Roman,serif}.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]:before{font-family:Monaco,Courier New,monospace}.ql-snow .ql-picker.ql-size{width:98px}.ql-snow .ql-picker.ql-size .ql-picker-item:before,.ql-snow .ql-picker.ql-size .ql-picker-label:before{content:"Normal"}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]:before,.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]:before{content:"Small"}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]:before,.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]:before{content:"Large"}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]:before,.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]:before{content:"Huge"}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]:before{font-size:10px}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]:before{font-size:18px}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]:before{font-size:32px}.ql-snow .ql-color-picker.ql-background .ql-picker-item{background-color:#fff}.ql-snow .ql-color-picker.ql-color .ql-picker-item{background-color:#000}.ql-toolbar.ql-snow{border:1px solid #ccc;box-sizing:border-box;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;padding:8px}.ql-toolbar.ql-snow .ql-formats{margin-right:15px}.ql-toolbar.ql-snow .ql-picker-label{border:1px solid transparent}.ql-toolbar.ql-snow .ql-picker-options{border:1px solid transparent;box-shadow:0 2px 8px rgba(0,0,0,.2)}.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label,.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options{border-color:#ccc}.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected,.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover{border-color:#000}.ql-toolbar.ql-snow+.ql-container.ql-snow{border-top:0}.ql-snow .ql-tooltip{background-color:#fff;border:1px solid #ccc;box-shadow:0 0 5px #ddd;color:#444;padding:5px 12px;white-space:nowrap}.ql-snow .ql-tooltip:before{content:"Visit URL:";line-height:26px;margin-right:8px}.ql-snow .ql-tooltip input[type=text]{display:none;border:1px solid #ccc;font-size:13px;height:26px;margin:0;padding:3px 5px;width:170px}.ql-snow .ql-tooltip a.ql-preview{display:inline-block;max-width:200px;overflow-x:hidden;text-overflow:ellipsis;vertical-align:top}.ql-snow .ql-tooltip a.ql-action:after{border-right:1px solid #ccc;content:"Edit";margin-left:16px;padding-right:8px}.ql-snow .ql-tooltip a.ql-remove:before{content:"Remove";margin-left:8px}.ql-snow .ql-tooltip a{line-height:26px}.ql-snow .ql-tooltip.ql-editing a.ql-preview,.ql-snow .ql-tooltip.ql-editing a.ql-remove{display:none}.ql-snow .ql-tooltip.ql-editing input[type=text]{display:inline-block}.ql-snow .ql-tooltip.ql-editing a.ql-action:after{border-right:0;content:"Save";padding-right:0}.ql-snow .ql-tooltip[data-mode=link]:before{content:"Enter link:"}.ql-snow .ql-tooltip[data-mode=formula]:before{content:"Enter formula:"}.ql-snow .ql-tooltip[data-mode=video]:before{content:"Enter video:"}.ql-snow a{color:#06c}.ql-container.ql-snow{border:1px solid #ccc} 8 | /*# sourceMappingURL=postTSC.282b31d6.css.map */ -------------------------------------------------------------------------------- /demoApp/assets/postTSC.282b31d6.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["index.css","quill.snow.css"],"names":[],"mappings":"AAAA,KACE,QAAS,CACT,mIAEY,CACZ,kCAAmC,CACnC,iCACF,CAEA,KACE,uEAEF;ACZA;;;;;EAKE,CACF,cACE,qBAAsB,CACtB,sCAAyC,CACzC,cAAe,CACf,WAAY,CACZ,QAAW,CACX,iBACF,CACA,sCACE,iBACF,CACA,gEACE,mBACF,CACA,cACE,cAAe,CACf,UAAW,CACX,iBAAkB,CAClB,iBAAkB,CAClB,OACF,CACA,gBACE,QAAS,CACT,SACF,CACA,WACE,qBAAsB,CACtB,gBAAiB,CACjB,WAAY,CACZ,YAAa,CACb,eAAgB,CAChB,iBAAkB,CAClB,UAAW,CACX,eAAgB,CAChB,eAAgB,CAChB,oBAAqB,CACrB,oBACF,CACA,aACE,WACF,CACA,kKAWE,QAAS,CACT,SAAU,CACV,4EACF,CACA,4BAEE,kBACF,CACA,kCAEE,oBACF,CACA,wBACE,eACF,CACA,mEAEE,mBACF,CACA,6EAEE,kBACF,CACA,uFAEE,UAAW,CACX,cAAe,CACf,kBACF,CACA,2CACE,eACF,CACA,4CACE,eACF,CACA,qBACE,oBAAqB,CACrB,kBAAmB,CACnB,WACF,CACA,4CACE,kBAAmB,CACnB,iBAAmB,CACnB,gBACF,CACA,sCACE,gBAAkB,CAClB,mBACF,CACA,gFAEE,kBACF,CACA,oEAEE,mBACF,CACA,iBACE,4EAA6E,CAC7E,wBACF,CACA,wBACE,oCACF,CACA,6BACE,wBACF,CACA,oCACE,wCACF,CACA,6BACE,qEACF,CACA,6BACE,wBACF,CACA,oCACE,wCACF,CACA,6BACE,8DACF,CACA,6BACE,wBACF,CACA,oCACE,oCACF,CACA,6BACE,uDACF,CACA,6BACE,wBACF,CACA,oCACE,wCACF,CACA,6BACE,gDACF,CACA,6BACE,wBACF,CACA,oCACE,wCACF,CACA,6BACE,yCACF,CACA,6BACE,wBACF,CACA,oCACE,oCACF,CACA,6BACE,kCACF,CACA,6BACE,wBACF,CACA,oCACE,wCACF,CACA,6BACE,2BACF,CACA,6BACE,wBACF,CACA,oCACE,wCACF,CACA,6BACE,oBACF,CACA,6BACE,wBACF,CACA,oCACE,oCACF,CACA,+CACE,gBACF,CACA,iDACE,kBACF,CACA,wDACE,iBACF,CACA,0DACE,mBACF,CACA,+CACE,gBACF,CACA,iDACE,kBACF,CACA,wDACE,iBACF,CACA,0DACE,mBACF,CACA,+CACE,gBACF,CACA,iDACE,mBACF,CACA,wDACE,iBACF,CACA,0DACE,oBACF,CACA,+CACE,iBACF,CACA,iDACE,mBACF,CACA,wDACE,kBACF,CACA,0DACE,oBACF,CACA,+CACE,iBACF,CACA,iDACE,mBACF,CACA,wDACE,kBACF,CACA,0DACE,oBACF,CACA,+CACE,iBACF,CACA,iDACE,mBACF,CACA,wDACE,kBACF,CACA,0DACE,oBACF,CACA,+CACE,iBACF,CACA,iDACE,mBACF,CACA,wDACE,kBACF,CACA,0DACE,oBACF,CACA,+CACE,iBACF,CACA,iDACE,mBACF,CACA,wDACE,kBACF,CACA,0DACE,oBACF,CACA,+CACE,iBACF,CACA,iDACE,mBACF,CACA,wDACE,kBACF,CACA,0DACE,oBACF,CACA,qBACE,aAAc,CACd,cACF,CACA,qCACE,aACF,CACA,oCACE,iBACF,CACA,wBACE,qBACF,CACA,sBACE,wBACF,CACA,yBACE,qBACF,CACA,yBACE,qBACF,CACA,wBACE,wBACF,CACA,uBACE,qBACF,CACA,yBACE,qBACF,CACA,2BACE,UACF,CACA,yBACE,aACF,CACA,4BACE,UACF,CACA,4BACE,UACF,CACA,2BACE,aACF,CACA,0BACE,UACF,CACA,4BACE,UACF,CACA,0BACE,yCACF,CACA,8BACE,wCACF,CACA,0BACE,eACF,CACA,0BACE,eACF,CACA,yBACE,eACF,CACA,6BACE,aAAc,CACd,kBACF,CACA,4BACE,iBACF,CACA,6BACE,kBACF,CACA,2BACE,gBACF,CACA,8BACE,qBAAsB,CACtB,gBACF,CACA,2BACE,oBAAsB,CACtB,8BAA+B,CAC/B,iBAAkB,CAClB,mBAAoB,CACpB,iBACF,CACA,qDAEE,UAAW,CACX,UAAW,CACX,aACF,CACA,uDAEE,eAAgB,CAChB,WAAY,CACZ,cAAe,CACf,oBAAqB,CACrB,UAAW,CACX,WAAY,CACZ,eAAgB,CAChB,UACF,CACA,+DAEE,UAAW,CACX,WACF,CACA,iFAEE,YACF,CACA,6FAEE,YACF,CACA,6jBAcE,UACF,CACA,kgDA4BE,SACF,CACA,kgDA4BE,WACF,CACA,wBACE,mGAEE,UACF,CACA,8PAIE,SACF,CACA,8PAIE,WACF,CACF,CAIA,oBACE,qBACF,CACA,oBACE,YACF,CACA,6CAEE,iBACF,CACA,qBACE,iBAAkB,CAClB,0BACF,CACA,uBACE,cAAe,CACf,oBACF,CACA,6BACE,2BACF,CACA,qBACE,oBAAqB,CACrB,qBACF,CACA,2BACE,UAAW,CACX,UAAW,CACX,aACF,CACA,oBACE,SAAU,CACV,WAAY,CACZ,oBAAqB,CACrB,qBAAsB,CACtB,cACF,CACA,0BACE,SAAU,CACV,WAAY,CACZ,oBAAqB,CACrB,cACF,CACA,8CAEE,SACF,CACA,mBACE,SACF,CACA,kBACE,iBACF,CACA,8CAEE,cACF,CACA,yBACE,UACF,CACA,sCACE,YACF,CACA,gDACE,cACF,CACA,iDACE,YACF,CACA,uBACE,aACF,CACA,uBACE,eACF,CACA,uBACE,gBACF,CACA,uBACE,aACF,CACA,uBACE,eACF,CACA,uBACE,eACF,CACA,sBACE,yBACF,CACA,+BACE,0BAA2B,CAC3B,iBAAkB,CAClB,cAAe,CACf,iBACF,CACA,iDAEE,wBAAyB,CACzB,iBACF,CACA,wBACE,oBAAqB,CACrB,iBAAkB,CAClB,cAAe,CACf,gBACF,CACA,yBACE,aAAc,CACd,kBAAmB,CACnB,eACF,CACA,+DAEE,aAAc,CACd,mBACF,CACA,kCACE,wBAAyB,CACzB,aAAc,CACd,gBACF,CACA,wBACE,cACF,CACA,oBACE,UAAW,CACX,oBAAqB,CACrB,UAAW,CACX,cAAe,CACf,eAAgB,CAChB,WAAY,CACZ,iBAAkB,CAClB,qBACF,CACA,0BACE,cAAe,CACf,oBAAqB,CACrB,WAAY,CACZ,gBAAiB,CACjB,iBAAkB,CAClB,iBAAkB,CAClB,UACF,CACA,iCACE,oBAAqB,CACrB,gBACF,CACA,4BACE,qBAAsB,CACtB,YAAa,CACb,cAAe,CACf,eAAgB,CAChB,iBAAkB,CAClB,kBACF,CACA,4CACE,cAAe,CACf,aAAc,CACd,kBAAmB,CACnB,eACF,CACA,iDACE,UAAW,CACX,SACF,CACA,0DACE,SACF,CACA,4DACE,WACF,CACA,mDACE,aAAc,CACd,eAAgB,CAChB,QAAS,CACT,SACF,CACA,mDAEE,UACF,CACA,qFAEE,eACF,CACA,6FAEE,SACF,CACA,4CACE,aACF,CACA,yCACE,WAAY,CACZ,UAAW,CACX,eACF,CACA,6CACE,eAAgB,CAChB,WACF,CACA,0CACE,4BAA6B,CAC7B,UAAW,CACX,WAAY,CACZ,UAAW,CACX,SAAY,CACZ,UACF,CACA,mEACE,iBAAkB,CAClB,eAAgB,CAChB,OAAQ,CACR,OAAQ,CACR,UACF,CACA,+fAME,wBACF,CACA,8BACE,UACF,CACA,2GAEE,gBACF,CACA,2IAEE,mBACF,CACA,2IAEE,mBACF,CACA,2IAEE,mBACF,CACA,2IAEE,mBACF,CACA,2IAEE,mBACF,CACA,2IAEE,mBACF,CACA,qEACE,aACF,CACA,qEACE,eACF,CACA,qEACE,gBACF,CACA,qEACE,aACF,CACA,qEACE,eACF,CACA,qEACE,eACF,CACA,4BACE,WACF,CACA,uGAEE,oBACF,CACA,2IAEE,eACF,CACA,mJAEE,mBACF,CACA,qEACE,yCACF,CACA,yEACE,wCACF,CACA,4BACE,UACF,CACA,uGAEE,gBACF,CACA,2IAEE,eACF,CACA,2IAEE,eACF,CACA,yIAEE,cACF,CACA,qEACE,cACF,CACA,qEACE,cACF,CACA,oEACE,cACF,CACA,wDACE,qBACF,CACA,mDACE,qBACF,CACA,oBACE,qBAAsB,CACtB,qBAAsB,CACtB,qDAA+D,CAC/D,WACF,CACA,gCACE,iBACF,CACA,qCACE,4BACF,CACA,uCACE,4BAA6B,CAC7B,mCACF,CAIA,0HACE,iBACF,CACA,4HAEE,iBACF,CACA,0CACE,YACF,CACA,qBACE,qBAAsB,CACtB,qBAAsB,CACtB,uBAA4B,CAC5B,UAAW,CACX,gBAAiB,CACjB,kBACF,CACA,4BACE,oBAAqB,CACrB,gBAAiB,CACjB,gBACF,CACA,sCACE,YAAa,CACb,qBAAsB,CACtB,cAAe,CACf,WAAY,CACZ,QAAW,CACX,eAAgB,CAChB,WACF,CACA,kCACE,oBAAqB,CACrB,eAAgB,CAChB,iBAAkB,CAClB,sBAAuB,CACvB,kBACF,CACA,uCACE,2BAA4B,CAC5B,cAAe,CACf,gBAAiB,CACjB,iBACF,CACA,wCACE,gBAAiB,CACjB,eACF,CACA,uBACE,gBACF,CACA,yFAEE,YACF,CACA,iDACE,oBACF,CACA,kDACE,cAAiB,CACjB,cAAe,CACf,eACF,CACA,4CACE,qBACF,CACA,+CACE,wBACF,CACA,6CACE,sBACF,CACA,WACE,UACF,CACA,sBACE,qBACF","file":"postTSC.282b31d6.css","sourceRoot":"..\\..\\htmlStringTSCFirst\\postTSC","sourcesContent":["body {\r\n margin: 0;\r\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\r\n \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\r\n sans-serif;\r\n -webkit-font-smoothing: antialiased;\r\n -moz-osx-font-smoothing: grayscale;\r\n}\r\n\r\ncode {\r\n font-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\",\r\n monospace;\r\n}\r\n","/*!\n * Quill Editor v1.3.0\n * https://quilljs.com/\n * Copyright (c) 2014, Jason Chen\n * Copyright (c) 2013, salesforce.com\n */\n.ql-container {\n box-sizing: border-box;\n font-family: Helvetica, Arial, sans-serif;\n font-size: 13px;\n height: 100%;\n margin: 0px;\n position: relative;\n}\n.ql-container.ql-disabled .ql-tooltip {\n visibility: hidden;\n}\n.ql-container.ql-disabled .ql-editor ul[data-checked] > li::before {\n pointer-events: none;\n}\n.ql-clipboard {\n left: -100000px;\n height: 1px;\n overflow-y: hidden;\n position: absolute;\n top: 50%;\n}\n.ql-clipboard p {\n margin: 0;\n padding: 0;\n}\n.ql-editor {\n box-sizing: border-box;\n line-height: 1.42;\n height: 100%;\n outline: none;\n overflow-y: auto;\n padding: 12px 15px;\n tab-size: 4;\n -moz-tab-size: 4;\n text-align: left;\n white-space: pre-wrap;\n word-wrap: break-word;\n}\n.ql-editor > * {\n cursor: text;\n}\n.ql-editor p,\n.ql-editor ol,\n.ql-editor ul,\n.ql-editor pre,\n.ql-editor blockquote,\n.ql-editor h1,\n.ql-editor h2,\n.ql-editor h3,\n.ql-editor h4,\n.ql-editor h5,\n.ql-editor h6 {\n margin: 0;\n padding: 0;\n counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;\n}\n.ql-editor ol,\n.ql-editor ul {\n padding-left: 1.5em;\n}\n.ql-editor ol > li,\n.ql-editor ul > li {\n list-style-type: none;\n}\n.ql-editor ul > li::before {\n content: '\\2022';\n}\n.ql-editor ul[data-checked=true],\n.ql-editor ul[data-checked=false] {\n pointer-events: none;\n}\n.ql-editor ul[data-checked=true] > li *,\n.ql-editor ul[data-checked=false] > li * {\n pointer-events: all;\n}\n.ql-editor ul[data-checked=true] > li::before,\n.ql-editor ul[data-checked=false] > li::before {\n color: #777;\n cursor: pointer;\n pointer-events: all;\n}\n.ql-editor ul[data-checked=true] > li::before {\n content: '\\2611';\n}\n.ql-editor ul[data-checked=false] > li::before {\n content: '\\2610';\n}\n.ql-editor li::before {\n display: inline-block;\n white-space: nowrap;\n width: 1.2em;\n}\n.ql-editor li:not(.ql-direction-rtl)::before {\n margin-left: -1.5em;\n margin-right: 0.3em;\n text-align: right;\n}\n.ql-editor li.ql-direction-rtl::before {\n margin-left: 0.3em;\n margin-right: -1.5em;\n}\n.ql-editor ol li:not(.ql-direction-rtl),\n.ql-editor ul li:not(.ql-direction-rtl) {\n padding-left: 1.5em;\n}\n.ql-editor ol li.ql-direction-rtl,\n.ql-editor ul li.ql-direction-rtl {\n padding-right: 1.5em;\n}\n.ql-editor ol li {\n counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;\n counter-increment: list-0;\n}\n.ql-editor ol li:before {\n content: counter(list-0, decimal) '. ';\n}\n.ql-editor ol li.ql-indent-1 {\n counter-increment: list-1;\n}\n.ql-editor ol li.ql-indent-1:before {\n content: counter(list-1, lower-alpha) '. ';\n}\n.ql-editor ol li.ql-indent-1 {\n counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;\n}\n.ql-editor ol li.ql-indent-2 {\n counter-increment: list-2;\n}\n.ql-editor ol li.ql-indent-2:before {\n content: counter(list-2, lower-roman) '. ';\n}\n.ql-editor ol li.ql-indent-2 {\n counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9;\n}\n.ql-editor ol li.ql-indent-3 {\n counter-increment: list-3;\n}\n.ql-editor ol li.ql-indent-3:before {\n content: counter(list-3, decimal) '. ';\n}\n.ql-editor ol li.ql-indent-3 {\n counter-reset: list-4 list-5 list-6 list-7 list-8 list-9;\n}\n.ql-editor ol li.ql-indent-4 {\n counter-increment: list-4;\n}\n.ql-editor ol li.ql-indent-4:before {\n content: counter(list-4, lower-alpha) '. ';\n}\n.ql-editor ol li.ql-indent-4 {\n counter-reset: list-5 list-6 list-7 list-8 list-9;\n}\n.ql-editor ol li.ql-indent-5 {\n counter-increment: list-5;\n}\n.ql-editor ol li.ql-indent-5:before {\n content: counter(list-5, lower-roman) '. ';\n}\n.ql-editor ol li.ql-indent-5 {\n counter-reset: list-6 list-7 list-8 list-9;\n}\n.ql-editor ol li.ql-indent-6 {\n counter-increment: list-6;\n}\n.ql-editor ol li.ql-indent-6:before {\n content: counter(list-6, decimal) '. ';\n}\n.ql-editor ol li.ql-indent-6 {\n counter-reset: list-7 list-8 list-9;\n}\n.ql-editor ol li.ql-indent-7 {\n counter-increment: list-7;\n}\n.ql-editor ol li.ql-indent-7:before {\n content: counter(list-7, lower-alpha) '. ';\n}\n.ql-editor ol li.ql-indent-7 {\n counter-reset: list-8 list-9;\n}\n.ql-editor ol li.ql-indent-8 {\n counter-increment: list-8;\n}\n.ql-editor ol li.ql-indent-8:before {\n content: counter(list-8, lower-roman) '. ';\n}\n.ql-editor ol li.ql-indent-8 {\n counter-reset: list-9;\n}\n.ql-editor ol li.ql-indent-9 {\n counter-increment: list-9;\n}\n.ql-editor ol li.ql-indent-9:before {\n content: counter(list-9, decimal) '. ';\n}\n.ql-editor .ql-indent-1:not(.ql-direction-rtl) {\n padding-left: 3em;\n}\n.ql-editor li.ql-indent-1:not(.ql-direction-rtl) {\n padding-left: 4.5em;\n}\n.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right {\n padding-right: 3em;\n}\n.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right {\n padding-right: 4.5em;\n}\n.ql-editor .ql-indent-2:not(.ql-direction-rtl) {\n padding-left: 6em;\n}\n.ql-editor li.ql-indent-2:not(.ql-direction-rtl) {\n padding-left: 7.5em;\n}\n.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right {\n padding-right: 6em;\n}\n.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right {\n padding-right: 7.5em;\n}\n.ql-editor .ql-indent-3:not(.ql-direction-rtl) {\n padding-left: 9em;\n}\n.ql-editor li.ql-indent-3:not(.ql-direction-rtl) {\n padding-left: 10.5em;\n}\n.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right {\n padding-right: 9em;\n}\n.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right {\n padding-right: 10.5em;\n}\n.ql-editor .ql-indent-4:not(.ql-direction-rtl) {\n padding-left: 12em;\n}\n.ql-editor li.ql-indent-4:not(.ql-direction-rtl) {\n padding-left: 13.5em;\n}\n.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right {\n padding-right: 12em;\n}\n.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right {\n padding-right: 13.5em;\n}\n.ql-editor .ql-indent-5:not(.ql-direction-rtl) {\n padding-left: 15em;\n}\n.ql-editor li.ql-indent-5:not(.ql-direction-rtl) {\n padding-left: 16.5em;\n}\n.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right {\n padding-right: 15em;\n}\n.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right {\n padding-right: 16.5em;\n}\n.ql-editor .ql-indent-6:not(.ql-direction-rtl) {\n padding-left: 18em;\n}\n.ql-editor li.ql-indent-6:not(.ql-direction-rtl) {\n padding-left: 19.5em;\n}\n.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right {\n padding-right: 18em;\n}\n.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right {\n padding-right: 19.5em;\n}\n.ql-editor .ql-indent-7:not(.ql-direction-rtl) {\n padding-left: 21em;\n}\n.ql-editor li.ql-indent-7:not(.ql-direction-rtl) {\n padding-left: 22.5em;\n}\n.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right {\n padding-right: 21em;\n}\n.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right {\n padding-right: 22.5em;\n}\n.ql-editor .ql-indent-8:not(.ql-direction-rtl) {\n padding-left: 24em;\n}\n.ql-editor li.ql-indent-8:not(.ql-direction-rtl) {\n padding-left: 25.5em;\n}\n.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right {\n padding-right: 24em;\n}\n.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right {\n padding-right: 25.5em;\n}\n.ql-editor .ql-indent-9:not(.ql-direction-rtl) {\n padding-left: 27em;\n}\n.ql-editor li.ql-indent-9:not(.ql-direction-rtl) {\n padding-left: 28.5em;\n}\n.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right {\n padding-right: 27em;\n}\n.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right {\n padding-right: 28.5em;\n}\n.ql-editor .ql-video {\n display: block;\n max-width: 100%;\n}\n.ql-editor .ql-video.ql-align-center {\n margin: 0 auto;\n}\n.ql-editor .ql-video.ql-align-right {\n margin: 0 0 0 auto;\n}\n.ql-editor .ql-bg-black {\n background-color: #000;\n}\n.ql-editor .ql-bg-red {\n background-color: #e60000;\n}\n.ql-editor .ql-bg-orange {\n background-color: #f90;\n}\n.ql-editor .ql-bg-yellow {\n background-color: #ff0;\n}\n.ql-editor .ql-bg-green {\n background-color: #008a00;\n}\n.ql-editor .ql-bg-blue {\n background-color: #06c;\n}\n.ql-editor .ql-bg-purple {\n background-color: #93f;\n}\n.ql-editor .ql-color-white {\n color: #fff;\n}\n.ql-editor .ql-color-red {\n color: #e60000;\n}\n.ql-editor .ql-color-orange {\n color: #f90;\n}\n.ql-editor .ql-color-yellow {\n color: #ff0;\n}\n.ql-editor .ql-color-green {\n color: #008a00;\n}\n.ql-editor .ql-color-blue {\n color: #06c;\n}\n.ql-editor .ql-color-purple {\n color: #93f;\n}\n.ql-editor .ql-font-serif {\n font-family: Georgia, Times New Roman, serif;\n}\n.ql-editor .ql-font-monospace {\n font-family: Monaco, Courier New, monospace;\n}\n.ql-editor .ql-size-small {\n font-size: 0.75em;\n}\n.ql-editor .ql-size-large {\n font-size: 1.5em;\n}\n.ql-editor .ql-size-huge {\n font-size: 2.5em;\n}\n.ql-editor .ql-direction-rtl {\n direction: rtl;\n text-align: inherit;\n}\n.ql-editor .ql-align-center {\n text-align: center;\n}\n.ql-editor .ql-align-justify {\n text-align: justify;\n}\n.ql-editor .ql-align-right {\n text-align: right;\n}\n.ql-editor .ql-embed-selected {\n border: 1px solid #777;\n user-select: none;\n}\n.ql-editor.ql-blank::before {\n color: rgba(0,0,0,0.6);\n content: attr(data-placeholder);\n font-style: italic;\n pointer-events: none;\n position: absolute;\n}\n.ql-snow.ql-toolbar:after,\n.ql-snow .ql-toolbar:after {\n clear: both;\n content: '';\n display: table;\n}\n.ql-snow.ql-toolbar button,\n.ql-snow .ql-toolbar button {\n background: none;\n border: none;\n cursor: pointer;\n display: inline-block;\n float: left;\n height: 24px;\n padding: 3px 5px;\n width: 28px;\n}\n.ql-snow.ql-toolbar button svg,\n.ql-snow .ql-toolbar button svg {\n float: left;\n height: 100%;\n}\n.ql-snow.ql-toolbar button:active:hover,\n.ql-snow .ql-toolbar button:active:hover {\n outline: none;\n}\n.ql-snow.ql-toolbar input.ql-image[type=file],\n.ql-snow .ql-toolbar input.ql-image[type=file] {\n display: none;\n}\n.ql-snow.ql-toolbar button:hover,\n.ql-snow .ql-toolbar button:hover,\n.ql-snow.ql-toolbar button:focus,\n.ql-snow .ql-toolbar button:focus,\n.ql-snow.ql-toolbar button.ql-active,\n.ql-snow .ql-toolbar button.ql-active,\n.ql-snow.ql-toolbar .ql-picker-label:hover,\n.ql-snow .ql-toolbar .ql-picker-label:hover,\n.ql-snow.ql-toolbar .ql-picker-label.ql-active,\n.ql-snow .ql-toolbar .ql-picker-label.ql-active,\n.ql-snow.ql-toolbar .ql-picker-item:hover,\n.ql-snow .ql-toolbar .ql-picker-item:hover,\n.ql-snow.ql-toolbar .ql-picker-item.ql-selected,\n.ql-snow .ql-toolbar .ql-picker-item.ql-selected {\n color: #06c;\n}\n.ql-snow.ql-toolbar button:hover .ql-fill,\n.ql-snow .ql-toolbar button:hover .ql-fill,\n.ql-snow.ql-toolbar button:focus .ql-fill,\n.ql-snow .ql-toolbar button:focus .ql-fill,\n.ql-snow.ql-toolbar button.ql-active .ql-fill,\n.ql-snow .ql-toolbar button.ql-active .ql-fill,\n.ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill,\n.ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill,\n.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill,\n.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill,\n.ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill,\n.ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill,\n.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill,\n.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill,\n.ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill,\n.ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill,\n.ql-snow.ql-toolbar button:focus .ql-stroke.ql-fill,\n.ql-snow .ql-toolbar button:focus .ql-stroke.ql-fill,\n.ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill,\n.ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill,\n.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,\n.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,\n.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,\n.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,\n.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,\n.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,\n.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill,\n.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill {\n fill: #06c;\n}\n.ql-snow.ql-toolbar button:hover .ql-stroke,\n.ql-snow .ql-toolbar button:hover .ql-stroke,\n.ql-snow.ql-toolbar button:focus .ql-stroke,\n.ql-snow .ql-toolbar button:focus .ql-stroke,\n.ql-snow.ql-toolbar button.ql-active .ql-stroke,\n.ql-snow .ql-toolbar button.ql-active .ql-stroke,\n.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke,\n.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke,\n.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke,\n.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke,\n.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke,\n.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke,\n.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke,\n.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke,\n.ql-snow.ql-toolbar button:hover .ql-stroke-miter,\n.ql-snow .ql-toolbar button:hover .ql-stroke-miter,\n.ql-snow.ql-toolbar button:focus .ql-stroke-miter,\n.ql-snow .ql-toolbar button:focus .ql-stroke-miter,\n.ql-snow.ql-toolbar button.ql-active .ql-stroke-miter,\n.ql-snow .ql-toolbar button.ql-active .ql-stroke-miter,\n.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter,\n.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter,\n.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,\n.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,\n.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter,\n.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter,\n.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter,\n.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter {\n stroke: #06c;\n}\n@media (pointer: coarse) {\n .ql-snow.ql-toolbar button:hover:not(.ql-active),\n .ql-snow .ql-toolbar button:hover:not(.ql-active) {\n color: #444;\n }\n .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill,\n .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-fill,\n .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill,\n .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill {\n fill: #444;\n }\n .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke,\n .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke,\n .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter,\n .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter {\n stroke: #444;\n }\n}\n.ql-snow {\n box-sizing: border-box;\n}\n.ql-snow * {\n box-sizing: border-box;\n}\n.ql-snow .ql-hidden {\n display: none;\n}\n.ql-snow .ql-out-bottom,\n.ql-snow .ql-out-top {\n visibility: hidden;\n}\n.ql-snow .ql-tooltip {\n position: absolute;\n transform: translateY(10px);\n}\n.ql-snow .ql-tooltip a {\n cursor: pointer;\n text-decoration: none;\n}\n.ql-snow .ql-tooltip.ql-flip {\n transform: translateY(-10px);\n}\n.ql-snow .ql-formats {\n display: inline-block;\n vertical-align: middle;\n}\n.ql-snow .ql-formats:after {\n clear: both;\n content: '';\n display: table;\n}\n.ql-snow .ql-stroke {\n fill: none;\n stroke: #444;\n stroke-linecap: round;\n stroke-linejoin: round;\n stroke-width: 2;\n}\n.ql-snow .ql-stroke-miter {\n fill: none;\n stroke: #444;\n stroke-miterlimit: 10;\n stroke-width: 2;\n}\n.ql-snow .ql-fill,\n.ql-snow .ql-stroke.ql-fill {\n fill: #444;\n}\n.ql-snow .ql-empty {\n fill: none;\n}\n.ql-snow .ql-even {\n fill-rule: evenodd;\n}\n.ql-snow .ql-thin,\n.ql-snow .ql-stroke.ql-thin {\n stroke-width: 1;\n}\n.ql-snow .ql-transparent {\n opacity: 0.4;\n}\n.ql-snow .ql-direction svg:last-child {\n display: none;\n}\n.ql-snow .ql-direction.ql-active svg:last-child {\n display: inline;\n}\n.ql-snow .ql-direction.ql-active svg:first-child {\n display: none;\n}\n.ql-snow .ql-editor h1 {\n font-size: 2em;\n}\n.ql-snow .ql-editor h2 {\n font-size: 1.5em;\n}\n.ql-snow .ql-editor h3 {\n font-size: 1.17em;\n}\n.ql-snow .ql-editor h4 {\n font-size: 1em;\n}\n.ql-snow .ql-editor h5 {\n font-size: 0.83em;\n}\n.ql-snow .ql-editor h6 {\n font-size: 0.67em;\n}\n.ql-snow .ql-editor a {\n text-decoration: underline;\n}\n.ql-snow .ql-editor blockquote {\n border-left: 4px solid #ccc;\n margin-bottom: 5px;\n margin-top: 5px;\n padding-left: 16px;\n}\n.ql-snow .ql-editor code,\n.ql-snow .ql-editor pre {\n background-color: #f0f0f0;\n border-radius: 3px;\n}\n.ql-snow .ql-editor pre {\n white-space: pre-wrap;\n margin-bottom: 5px;\n margin-top: 5px;\n padding: 5px 10px;\n}\n.ql-snow .ql-editor code {\n font-size: 85%;\n padding-bottom: 2px;\n padding-top: 2px;\n}\n.ql-snow .ql-editor code:before,\n.ql-snow .ql-editor code:after {\n content: \"\\A0\";\n letter-spacing: -2px;\n}\n.ql-snow .ql-editor pre.ql-syntax {\n background-color: #23241f;\n color: #f8f8f2;\n overflow: visible;\n}\n.ql-snow .ql-editor img {\n max-width: 100%;\n}\n.ql-snow .ql-picker {\n color: #444;\n display: inline-block;\n float: left;\n font-size: 14px;\n font-weight: 500;\n height: 24px;\n position: relative;\n vertical-align: middle;\n}\n.ql-snow .ql-picker-label {\n cursor: pointer;\n display: inline-block;\n height: 100%;\n padding-left: 8px;\n padding-right: 2px;\n position: relative;\n width: 100%;\n}\n.ql-snow .ql-picker-label::before {\n display: inline-block;\n line-height: 22px;\n}\n.ql-snow .ql-picker-options {\n background-color: #fff;\n display: none;\n min-width: 100%;\n padding: 4px 8px;\n position: absolute;\n white-space: nowrap;\n}\n.ql-snow .ql-picker-options .ql-picker-item {\n cursor: pointer;\n display: block;\n padding-bottom: 5px;\n padding-top: 5px;\n}\n.ql-snow .ql-picker.ql-expanded .ql-picker-label {\n color: #ccc;\n z-index: 2;\n}\n.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill {\n fill: #ccc;\n}\n.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke {\n stroke: #ccc;\n}\n.ql-snow .ql-picker.ql-expanded .ql-picker-options {\n display: block;\n margin-top: -1px;\n top: 100%;\n z-index: 1;\n}\n.ql-snow .ql-color-picker,\n.ql-snow .ql-icon-picker {\n width: 28px;\n}\n.ql-snow .ql-color-picker .ql-picker-label,\n.ql-snow .ql-icon-picker .ql-picker-label {\n padding: 2px 4px;\n}\n.ql-snow .ql-color-picker .ql-picker-label svg,\n.ql-snow .ql-icon-picker .ql-picker-label svg {\n right: 4px;\n}\n.ql-snow .ql-icon-picker .ql-picker-options {\n padding: 4px 0px;\n}\n.ql-snow .ql-icon-picker .ql-picker-item {\n height: 24px;\n width: 24px;\n padding: 2px 4px;\n}\n.ql-snow .ql-color-picker .ql-picker-options {\n padding: 3px 5px;\n width: 152px;\n}\n.ql-snow .ql-color-picker .ql-picker-item {\n border: 1px solid transparent;\n float: left;\n height: 16px;\n margin: 2px;\n padding: 0px;\n width: 16px;\n}\n.ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg {\n position: absolute;\n margin-top: -9px;\n right: 0;\n top: 50%;\n width: 18px;\n}\n.ql-snow .ql-picker.ql-header .ql-picker-label[data-label]:not([data-label=''])::before,\n.ql-snow .ql-picker.ql-font .ql-picker-label[data-label]:not([data-label=''])::before,\n.ql-snow .ql-picker.ql-size .ql-picker-label[data-label]:not([data-label=''])::before,\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-label]:not([data-label=''])::before,\n.ql-snow .ql-picker.ql-font .ql-picker-item[data-label]:not([data-label=''])::before,\n.ql-snow .ql-picker.ql-size .ql-picker-item[data-label]:not([data-label=''])::before {\n content: attr(data-label);\n}\n.ql-snow .ql-picker.ql-header {\n width: 98px;\n}\n.ql-snow .ql-picker.ql-header .ql-picker-label::before,\n.ql-snow .ql-picker.ql-header .ql-picker-item::before {\n content: 'Normal';\n}\n.ql-snow .ql-picker.ql-header .ql-picker-label[data-value=\"1\"]::before,\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"1\"]::before {\n content: 'Heading 1';\n}\n.ql-snow .ql-picker.ql-header .ql-picker-label[data-value=\"2\"]::before,\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"2\"]::before {\n content: 'Heading 2';\n}\n.ql-snow .ql-picker.ql-header .ql-picker-label[data-value=\"3\"]::before,\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"3\"]::before {\n content: 'Heading 3';\n}\n.ql-snow .ql-picker.ql-header .ql-picker-label[data-value=\"4\"]::before,\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"4\"]::before {\n content: 'Heading 4';\n}\n.ql-snow .ql-picker.ql-header .ql-picker-label[data-value=\"5\"]::before,\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"5\"]::before {\n content: 'Heading 5';\n}\n.ql-snow .ql-picker.ql-header .ql-picker-label[data-value=\"6\"]::before,\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"6\"]::before {\n content: 'Heading 6';\n}\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"1\"]::before {\n font-size: 2em;\n}\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"2\"]::before {\n font-size: 1.5em;\n}\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"3\"]::before {\n font-size: 1.17em;\n}\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"4\"]::before {\n font-size: 1em;\n}\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"5\"]::before {\n font-size: 0.83em;\n}\n.ql-snow .ql-picker.ql-header .ql-picker-item[data-value=\"6\"]::before {\n font-size: 0.67em;\n}\n.ql-snow .ql-picker.ql-font {\n width: 108px;\n}\n.ql-snow .ql-picker.ql-font .ql-picker-label::before,\n.ql-snow .ql-picker.ql-font .ql-picker-item::before {\n content: 'Sans Serif';\n}\n.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before,\n.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {\n content: 'Serif';\n}\n.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before,\n.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {\n content: 'Monospace';\n}\n.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {\n font-family: Georgia, Times New Roman, serif;\n}\n.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {\n font-family: Monaco, Courier New, monospace;\n}\n.ql-snow .ql-picker.ql-size {\n width: 98px;\n}\n.ql-snow .ql-picker.ql-size .ql-picker-label::before,\n.ql-snow .ql-picker.ql-size .ql-picker-item::before {\n content: 'Normal';\n}\n.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before,\n.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {\n content: 'Small';\n}\n.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before,\n.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {\n content: 'Large';\n}\n.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before,\n.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {\n content: 'Huge';\n}\n.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {\n font-size: 10px;\n}\n.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {\n font-size: 18px;\n}\n.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {\n font-size: 32px;\n}\n.ql-snow .ql-color-picker.ql-background .ql-picker-item {\n background-color: #fff;\n}\n.ql-snow .ql-color-picker.ql-color .ql-picker-item {\n background-color: #000;\n}\n.ql-toolbar.ql-snow {\n border: 1px solid #ccc;\n box-sizing: border-box;\n font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;\n padding: 8px;\n}\n.ql-toolbar.ql-snow .ql-formats {\n margin-right: 15px;\n}\n.ql-toolbar.ql-snow .ql-picker-label {\n border: 1px solid transparent;\n}\n.ql-toolbar.ql-snow .ql-picker-options {\n border: 1px solid transparent;\n box-shadow: rgba(0,0,0,0.2) 0 2px 8px;\n}\n.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label {\n border-color: #ccc;\n}\n.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options {\n border-color: #ccc;\n}\n.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected,\n.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover {\n border-color: #000;\n}\n.ql-toolbar.ql-snow + .ql-container.ql-snow {\n border-top: 0px;\n}\n.ql-snow .ql-tooltip {\n background-color: #fff;\n border: 1px solid #ccc;\n box-shadow: 0px 0px 5px #ddd;\n color: #444;\n padding: 5px 12px;\n white-space: nowrap;\n}\n.ql-snow .ql-tooltip::before {\n content: \"Visit URL:\";\n line-height: 26px;\n margin-right: 8px;\n}\n.ql-snow .ql-tooltip input[type=text] {\n display: none;\n border: 1px solid #ccc;\n font-size: 13px;\n height: 26px;\n margin: 0px;\n padding: 3px 5px;\n width: 170px;\n}\n.ql-snow .ql-tooltip a.ql-preview {\n display: inline-block;\n max-width: 200px;\n overflow-x: hidden;\n text-overflow: ellipsis;\n vertical-align: top;\n}\n.ql-snow .ql-tooltip a.ql-action::after {\n border-right: 1px solid #ccc;\n content: 'Edit';\n margin-left: 16px;\n padding-right: 8px;\n}\n.ql-snow .ql-tooltip a.ql-remove::before {\n content: 'Remove';\n margin-left: 8px;\n}\n.ql-snow .ql-tooltip a {\n line-height: 26px;\n}\n.ql-snow .ql-tooltip.ql-editing a.ql-preview,\n.ql-snow .ql-tooltip.ql-editing a.ql-remove {\n display: none;\n}\n.ql-snow .ql-tooltip.ql-editing input[type=text] {\n display: inline-block;\n}\n.ql-snow .ql-tooltip.ql-editing a.ql-action::after {\n border-right: 0px;\n content: 'Save';\n padding-right: 0px;\n}\n.ql-snow .ql-tooltip[data-mode=link]::before {\n content: \"Enter link:\";\n}\n.ql-snow .ql-tooltip[data-mode=formula]::before {\n content: \"Enter formula:\";\n}\n.ql-snow .ql-tooltip[data-mode=video]::before {\n content: \"Enter video:\";\n}\n.ql-snow a {\n color: #06c;\n}\n.ql-container.ql-snow {\n border: 1px solid #ccc;\n}\n"]} -------------------------------------------------------------------------------- /demoApp/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reggie3/react-native-webview-quilljs/73405ca82f90b6acd432dabd659f5e5b2f653c17/demoApp/assets/splash.png -------------------------------------------------------------------------------- /demoApp/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /demoApp/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var clean = require("gulp-clean"); 3 | 4 | gulp.task("clean", function() { 5 | return gulp 6 | .src("node_modules", { allowEmpty: true, read: false }) 7 | .pipe(clean()); 8 | }); 9 | -------------------------------------------------------------------------------- /demoApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject", 9 | "clean": "yarn upgrade react-native-webview-quilljs" 10 | }, 11 | "dependencies": { 12 | "expo": "^36.0.0", 13 | "react": "16.9.0", 14 | "react-dom": "16.9.0", 15 | "react-native": "https://github.com/expo/react-native/archive/sdk-36.0.1.tar.gz", 16 | "react-native-webview-quilljs": "file:../WebViewQuillJS", 17 | "util": "^0.10.3" 18 | }, 19 | "devDependencies": { 20 | "@types/expo": "^33.0.1", 21 | "@types/react": "^16.8.23", 22 | "@types/react-native": "^0.57.65", 23 | "babel-preset-expo": "^7.1.0", 24 | "gulp": "^4.0.2", 25 | "gulp-clean": "^0.4.0", 26 | "typescript": "^3.7.3" 27 | }, 28 | "private": true 29 | } 30 | -------------------------------------------------------------------------------- /demoApp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "jsx": "react-native", 5 | "lib": ["dom", "esnext"], 6 | "moduleResolution": "node", 7 | "noEmit": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /expo-qr-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reggie3/react-native-webview-quilljs/73405ca82f90b6acd432dabd659f5e5b2f653c17/expo-qr-code.png -------------------------------------------------------------------------------- /html/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /html/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `yarn build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `yarn eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `yarn build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /html/gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require("gulp"); 2 | const inlinesource = require("gulp-inline-source"); 3 | const replace = require("gulp-replace"); 4 | const clean = require("gulp-clean"); 5 | const rename = require("gulp-rename"); 6 | 7 | const REACT_BUILD_DIRECTORY = "build"; 8 | const REACT_BUILD_FILES = "./build/*.html"; 9 | const FILE_NAME_AFTER_ADDING_INLINE_TAGS = "indexWithTags.html"; 10 | const DIST_HTML_FILE_NAME = "index.html"; 11 | const DIST_DIRECTORY = "dist"; 12 | 13 | gulp.task("clean", function() { 14 | return gulp 15 | .src( 16 | [ 17 | `${REACT_BUILD_DIRECTORY}/${FILE_NAME_AFTER_ADDING_INLINE_TAGS}`, 18 | DIST_DIRECTORY 19 | ], 20 | { 21 | allowEmpty: true, 22 | read: false 23 | } 24 | ) 25 | .pipe(clean()); 26 | }); 27 | 28 | gulp.task("disableBrowserTestFlag", () => { 29 | return gulp 30 | .src(["./src/QuillComponent.tsx"]) 31 | .pipe( 32 | replace( 33 | "const ENABLE_BROWSER_TESTING = true;", 34 | "const ENABLE_BROWSER_TESTING = false;" 35 | ) 36 | ) 37 | .pipe(gulp.dest("./src")); 38 | }); 39 | 40 | gulp.task("enableBrowserTestFlag", () => { 41 | return gulp 42 | .src(["./src/QuillComponent.tsx"]) 43 | .pipe( 44 | replace( 45 | "const ENABLE_BROWSER_TESTING = false;", 46 | "const ENABLE_BROWSER_TESTING = true;" 47 | ) 48 | ) 49 | .pipe(gulp.dest("./src")); 50 | }); 51 | 52 | gulp.task("addInlineTags", function() { 53 | return gulp 54 | .src(REACT_BUILD_FILES) 55 | .pipe(replace('rel="stylesheet"', 'rel="stylesheet" inline')) 56 | .pipe(replace(">", " inline>")) 57 | .pipe(rename(FILE_NAME_AFTER_ADDING_INLINE_TAGS)) 58 | .pipe(gulp.dest(REACT_BUILD_DIRECTORY)); 59 | }); 60 | 61 | gulp.task("inlineSource", function() { 62 | return gulp 63 | .src(`./${REACT_BUILD_DIRECTORY}/${FILE_NAME_AFTER_ADDING_INLINE_TAGS}`) 64 | .pipe(inlinesource()) 65 | .pipe(rename(DIST_HTML_FILE_NAME)) 66 | .pipe(gulp.dest(DIST_DIRECTORY)); 67 | }); 68 | 69 | exports.build = gulp.series("clean", "addInlineTags", "inlineSource"); 70 | -------------------------------------------------------------------------------- /html/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "react": "^16.12.0", 10 | "react-dom": "^16.12.0", 11 | "react-quill": "^1.3.3", 12 | "react-scripts": "3.3.0", 13 | "typescript": "^3.7.4" 14 | }, 15 | "devDependencies": { 16 | "del-cli": "^3.0.0", 17 | "gulp": "^4.0.2", 18 | "gulp-clean": "^0.4.0", 19 | "gulp-inline-source": "^4.0.0", 20 | "gulp-rename": "^2.0.0", 21 | "gulp-replace": "^1.0.0" 22 | }, 23 | "eslintConfig": { 24 | "extends": "react-app" 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | }, 38 | "scripts": { 39 | "start": "react-scripts start", 40 | "build": "react-scripts build", 41 | "test": "react-scripts test", 42 | "eject": "react-scripts eject", 43 | "cleanAssets": "del-cli --force ../WebViewQuillJS/assets/**/*", 44 | "copyDist": "npx copyfiles -u 1 ./dist/index.html ../WebViewQuillJS/assets", 45 | "dist": "gulp disableBrowserTestFlag && yarn build && gulp build && yarn cleanAssets && yarn copyDist && gulp enableBrowserTestFlag", 46 | "cpx": "cpx '../WebViewQuillJS/models.ts' ./src --watch" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /html/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reggie3/react-native-webview-quilljs/73405ca82f90b6acd432dabd659f5e5b2f653c17/html/public/favicon.ico -------------------------------------------------------------------------------- /html/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /html/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reggie3/react-native-webview-quilljs/73405ca82f90b6acd432dabd659f5e5b2f653c17/html/public/logo192.png -------------------------------------------------------------------------------- /html/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reggie3/react-native-webview-quilljs/73405ca82f90b6acd432dabd659f5e5b2f653c17/html/public/logo512.png -------------------------------------------------------------------------------- /html/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /html/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /html/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /html/src/QuillComponent.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "react-quill/dist/quill.snow.css"; 3 | import QuillComponentView from "./QuillComponent.view"; 4 | import { WebviewQuillJSMessage, WebviewQuillJSEvents } from "./models"; 5 | import * as Quill from "quill"; 6 | 7 | interface State { 8 | backgroundColor: string; 9 | debugMessages: string[]; 10 | containerHeight: number; 11 | content: string | any; 12 | defaultContent: any; 13 | doShowQuillComponentDebugMessages: boolean; 14 | isReadOnly: boolean; 15 | modules: object; 16 | } 17 | 18 | class QuillComponent extends React.Component<{}, State> { 19 | quillComponentRef: React.RefObject; 20 | 21 | constructor(props: any) { 22 | super(props); 23 | this.quillComponentRef = React.createRef(); 24 | this.state = { 25 | backgroundColor: "#FAFAD2", 26 | debugMessages: ["test"], 27 | containerHeight: null, 28 | content: null, 29 | defaultContent: null, 30 | doShowQuillComponentDebugMessages: false, 31 | isReadOnly: false, 32 | modules: {} 33 | }; 34 | } 35 | 36 | onChange = ( 37 | content: string, 38 | delta: Quill.Delta, 39 | source: Quill.Sources, 40 | editor: any 41 | ) => { 42 | this.sendMessage({ 43 | msg: WebviewQuillJSEvents.ON_CHANGE, 44 | payload: { 45 | html: editor.getHTML(), 46 | delta: editor.getContents(), 47 | text: editor.getText(), 48 | source, 49 | editor 50 | } 51 | }); 52 | }; 53 | 54 | onChangeSelection = ( 55 | range: Quill.RangeStatic, 56 | source: Quill.RangeStatic, 57 | editor: any 58 | ) => { 59 | this.sendMessage({ 60 | msg: WebviewQuillJSEvents.ON_CHANGE_SELECTION, 61 | payload: { 62 | range, 63 | selection: editor.getSelection(), 64 | html: editor.getHTML(), 65 | delta: editor.getContents(), 66 | text: editor.getText(), 67 | source, 68 | editor 69 | } 70 | }); 71 | }; 72 | onFocus = (range: Quill.RangeStatic, source: Quill.Sources, editor: any) => { 73 | this.sendMessage({ 74 | msg: WebviewQuillJSEvents.ON_FOCUS, 75 | payload: { 76 | contents: editor.getContents(), 77 | range, 78 | source, 79 | editor 80 | } 81 | }); 82 | }; 83 | onBlur = (previousRange: any, source: Quill.Sources, editor: any) => { 84 | this.sendMessage({ 85 | msg: WebviewQuillJSEvents.ON_BLUR, 86 | payload: { 87 | contents: editor.getContents(), 88 | previousRange, 89 | source, 90 | editor 91 | } 92 | }); 93 | }; 94 | onKeyPress = (event: any) => { 95 | this.sendMessage({ 96 | msg: WebviewQuillJSEvents.ON_KEY_PRESS, 97 | payload: { 98 | event 99 | } 100 | }); 101 | }; 102 | onKeyDown = (event: any) => { 103 | this.sendMessage({ 104 | msg: WebviewQuillJSEvents.ON_KEY_DOWN, 105 | payload: { 106 | event 107 | } 108 | }); 109 | }; 110 | onKeyUp = (event: any) => { 111 | this.sendMessage({ 112 | msg: WebviewQuillJSEvents.ON_KEY_UP, 113 | payload: { 114 | event 115 | } 116 | }); 117 | }; 118 | 119 | componentDidMount = () => { 120 | const { containerHeight } = this.state; 121 | this.addEventListeners(); 122 | 123 | // set the height to be used for the container window 124 | if (this.quillComponentRef && containerHeight === null) { 125 | this.setContainerHeight(); 126 | } 127 | 128 | this.sendMessage({ 129 | msg: WebviewQuillJSEvents.QUILLJS_COMPONENT_MOUNTED 130 | }); 131 | }; 132 | 133 | componentDidUpdate = (prevProps: {}, prevState: State) => { 134 | const { isReadOnly } = this.state; 135 | if (isReadOnly !== prevState.isReadOnly) { 136 | this.setContainerHeight(); 137 | } 138 | }; 139 | 140 | private setContainerHeight = () => { 141 | const quillDivElement = this.quillComponentRef.current; 142 | const toolBar = quillDivElement.querySelector(".ql-toolbar"); 143 | const windowHeight = quillDivElement.clientHeight; 144 | this.setState({ 145 | containerHeight: windowHeight - (toolBar ? toolBar.clientHeight + 2 : 0) 146 | }); 147 | }; 148 | 149 | private addDebugMessage = (msg: any) => { 150 | if (typeof msg === "object") { 151 | this.addDebugMessage("STRINGIFIED"); 152 | this.setState({ 153 | debugMessages: [ 154 | ...this.state.debugMessages, 155 | JSON.stringify(msg, null, 4) 156 | ] 157 | }); 158 | } else { 159 | this.setState({ debugMessages: [...this.state.debugMessages, msg] }); 160 | } 161 | }; 162 | 163 | private addEventListeners = () => { 164 | if (document) { 165 | document.addEventListener("message", this.handleMessage); 166 | this.addDebugMessage("set document listeners"); 167 | this.sendMessage({ 168 | msg: WebviewQuillJSEvents.DOCUMENT_EVENT_LISTENER_ADDED 169 | }); 170 | } 171 | if (window) { 172 | window.addEventListener("message", this.handleMessage); 173 | this.addDebugMessage("setting Window"); 174 | this.sendMessage({ 175 | msg: WebviewQuillJSEvents.WINDOW_EVENT_LISTENER_ADDED 176 | }); 177 | } 178 | if (!document && !window) { 179 | this.sendMessage({ 180 | error: WebviewQuillJSEvents.UNABLE_TO_ADD_EVENT_LISTENER 181 | }); 182 | return; 183 | } 184 | }; 185 | 186 | private handleMessage = (event: any & { data: State }) => { 187 | this.addDebugMessage(event.data); 188 | try { 189 | this.setState({ ...this.state, ...event.data }); 190 | } catch (error) { 191 | this.addDebugMessage({ error: JSON.stringify(error) }); 192 | } 193 | }; 194 | 195 | protected sendMessage = (message: WebviewQuillJSMessage) => { 196 | // @ts-ignore 197 | if (window.ReactNativeWebView) { 198 | // @ts-ignore 199 | window.ReactNativeWebView.postMessage(JSON.stringify(message)); 200 | console.log("sendMessage ", JSON.stringify(message)); 201 | } 202 | }; 203 | 204 | render() { 205 | const { 206 | backgroundColor, 207 | containerHeight, 208 | content, 209 | debugMessages, 210 | defaultContent, 211 | doShowQuillComponentDebugMessages, 212 | isReadOnly 213 | } = this.state; 214 | return ( 215 | <> 216 | 232 | {doShowQuillComponentDebugMessages && ( 233 |
247 |
    248 | {debugMessages.map((message: string, index: number) => { 249 | return
  • {message}
  • ; 250 | })} 251 |
252 |
253 | )} 254 | 255 | ); 256 | } 257 | } 258 | 259 | export default QuillComponent; 260 | -------------------------------------------------------------------------------- /html/src/QuillComponent.view.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import ReactQuill from "react-quill"; 3 | import "react-quill/dist/quill.snow.css"; 4 | 5 | interface QuillComponentViewProps { 6 | addDebugMessage?: (message: string) => void; 7 | backgroundColor: string; 8 | content?: string | any | undefined; 9 | debugMessages?: string[]; 10 | defaultContent?: string | any; 11 | containerHeight: number; 12 | isReadOnly?: boolean; 13 | modules?: object; 14 | onChange: (content: string, delta: any, source: any, editor: any) => void; 15 | onChangeSelection: (range: any, source: any, editor: any) => void; 16 | onFocus: (range: any, source: any, editor: any) => void; 17 | onBlur: (previousRange: any, source: any, editor: any) => void; 18 | onKeyPress: (event: any) => void; 19 | onKeyDown: (event: any) => void; 20 | onKeyUp: (event: any) => void; 21 | quillComponentRef: React.RefObject; 22 | } 23 | 24 | const QuillComponentView = ({ 25 | backgroundColor, 26 | containerHeight, 27 | defaultContent, 28 | isReadOnly, 29 | modules, 30 | onChange, 31 | onChangeSelection, 32 | onFocus, 33 | onBlur, 34 | onKeyPress, 35 | onKeyDown, 36 | onKeyUp, 37 | quillComponentRef, 38 | content 39 | }: QuillComponentViewProps) => { 40 | const getDynamicProps = () => { 41 | let dynamicProps: any = {}; 42 | if (content) { 43 | dynamicProps.value = content; 44 | } else if (defaultContent) { 45 | dynamicProps.defaultValue = defaultContent; 46 | } 47 | return dynamicProps; 48 | }; 49 | 50 | const getModules = (): object => { 51 | if (isReadOnly) { 52 | return { 53 | ...modules, 54 | toolbar: false 55 | }; 56 | } 57 | return modules; 58 | }; 59 | 60 | return ( 61 |
72 | 85 |
86 | ); 87 | }; 88 | 89 | export default QuillComponentView; 90 | -------------------------------------------------------------------------------- /html/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./QuillComponent"; 4 | 5 | ReactDOM.render(, document.getElementById("root")); 6 | -------------------------------------------------------------------------------- /html/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /html/src/models.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import * as Quill from "quill"; 4 | 5 | export interface WebViewQuillJSProps { 6 | backgroundColor?: string; 7 | content?: Quill.Delta | string | object; 8 | defaultContent?: Quill.Delta | string | object; 9 | doShowDebugMessages?: boolean; 10 | doShowQuillComponentDebugMessages?: boolean; 11 | isReadOnly?: boolean; 12 | loadingIndicator?: () => React.ReactElement; 13 | onError?: (syntheticEvent: any) => void; 14 | onLoadEnd?: () => void; 15 | onLoadStart?: () => void; 16 | onMessageReceived?: (message: any) => void; 17 | } 18 | export enum WebviewQuillJSEvents { 19 | QUILLJS_COMPONENT_MOUNTED = "QUILLJS_COMPONENT_MOUNTED", 20 | DOCUMENT_EVENT_LISTENER_ADDED = "DOCUMENT_EVENT_LISTENER_ADDED", 21 | WINDOW_EVENT_LISTENER_ADDED = "WINDOW_EVENT_LISTENER_ADDED", 22 | UNABLE_TO_ADD_EVENT_LISTENER = "UNABLE_TO_ADD_EVENT_LISTENER", 23 | DOCUMENT_EVENT_LISTENER_REMOVED = "DOCUMENT_EVENT_LISTENER_REMOVED", 24 | WINDOW_EVENT_LISTENER_REMOVED = "WINDOW_EVENT_LISTENER_REMOVED", 25 | ON_CHANGE = "ON_CHANGE", 26 | ON_CHANGE_SELECTION = "ON_CHANGE_SELECTION", 27 | ON_FOCUS = "ON_FOCUS", 28 | ON_BLUR = "ON_BLUR", 29 | ON_KEY_PRESS = "ON_KEY_PRESS", 30 | ON_KEY_DOWN = "ON_KEY_DOWN", 31 | ON_KEY_UP = "ON_KEY_UP" 32 | } 33 | 34 | export type WebviewQuillJSMessage = { 35 | event?: WebviewQuillJSEvents; 36 | msg?: string; 37 | error?: string; 38 | payload?: any; 39 | }; 40 | 41 | export type StartupMessage = { 42 | backgroundColor?: string; 43 | content?: string | object | Quill.Delta; 44 | doShowQuillComponentDebugMessages?: boolean; 45 | isReadOnly?: boolean; 46 | }; 47 | -------------------------------------------------------------------------------- /html/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /html/src/setupTeststjs: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /html/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "noEmit": true, 14 | "jsx": "react", 15 | "strict": true, 16 | "strictNullChecks": false, 17 | "isolatedModules": true 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # React Native Webview Quilljs 2 | 3 | ## Quill.js editor and viewer components with no native code for React Native apps. This is a purely JavaScript component based on @zenomaro's react-quill project (https://github.com/zenoamaro/react-quill/) 4 | 5 | [![npm](https://img.shields.io/npm/v/react-native-webview-quilljs.svg)](https://www.npmjs.com/package/react-native-webview-quilljs) 6 | [![npm](https://img.shields.io/npm/dm/react-native-webview-quilljs.svg)](https://www.npmjs.com/package/react-native-webview-quilljs) 7 | [![npm](https://img.shields.io/npm/dt/react-native-webview-quilljs.svg)](https://www.npmjs.com/package/react-native-webview-quilljs) 8 | [![npm](https://img.shields.io/npm/l/react-native-webview-quilljs.svg)](https://github.com/react-native-component/react-native-webview-quilljs/blob/master/LICENSE) 9 | 10 | ![Image](https://thumbs.gfycat.com/CelebratedSilentDromedary-size_restricted.gif) 11 | 12 | ## Try it in Expo 13 | 14 | ![QR Code](https://github.com/reggie3/react-native-webview-quilljs/blob/master/expo-qr-code.png) 15 | 16 | [Link to Expo Project Page](https://expo.io/@reggie3/react-native-webview-quilljs) 17 | 18 | ## Installation 19 | 20 | ``` 21 | npm install --save react-native-webview-quilljs 22 | ``` 23 | 24 | or 25 | 26 | ``` 27 | yarn add react-native-webview-quilljs 28 | ``` 29 | 30 | and then 31 | 32 | ``` 33 | import {WebViewQuillEditor, WebViewQuillViewer} from 'react-native-webview-quilljs' 34 | ``` 35 | 36 | ## Usage 37 | 38 | This package can be used to create both an editor and a viewer 39 | 40 | ```javascript 41 | // A Quill.js editor with the standard toolbar 42 | 47 | 48 | // A Quill.js viewer with no toolbar 49 | 54 | ``` 55 | 56 | This component accepts the following props: 57 | 58 | | property | required | type | purpose | 59 | | --------------------------------- | -------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------ | 60 | | backgroundColor | optional | string | The background color of the Quill Component | 61 | | content | optional | HTML, a Quill Delta, or a plain object representing a Delta | Content of a controlled viewer component | 62 | | defaultContent | optional | HTML, a Quill Delta, or a plain object representing a Delta | Inital value of an uncontrolled editor component | 63 | | doShowDebugMessages | optional | boolean | Show debug messages from the Webview Component | 64 | | doShowQuillComponentDebugMessages | optional | boolean | Show debug messages from the contents of the Webview component | 65 | | isReadOnly | optional | boolean | Editor or viewer component (false or true) | 66 | | onError | optional | function | Function called if the Webview component experiences an Error | 67 | | onLoadEnd | optional | function | Function called when the Webview has finished loading | 68 | | onLoadStart | optional | function | Function called if the Webview begins loading | 69 | | onMessageReceived | optional | function | Function called when the WebViewQuillJS component receives a message from the editor | 70 | 71 | ### Editor Messages & onMessageReceived function 72 | 73 | A WebviewQuillJSMessage object is passed to the onMessageReceived function: 74 | 75 | ```javascript 76 | export type WebviewQuillJSMessage = { 77 | event?: WebviewQuillJSEvents, 78 | msg?: string, 79 | error?: string, 80 | payload?: any 81 | }; 82 | ``` 83 | 84 | WebviewQuillJSEvents consists of the following enumerated values: 85 | 86 | ```javascript 87 | export enum WebviewQuillJSEvents { 88 | QUILLJS_COMPONENT_MOUNTED = "QUILLJS_COMPONENT_MOUNTED", 89 | DOCUMENT_EVENT_LISTENER_ADDED = "DOCUMENT_EVENT_LISTENER_ADDED", 90 | WINDOW_EVENT_LISTENER_ADDED = "WINDOW_EVENT_LISTENER_ADDED", 91 | UNABLE_TO_ADD_EVENT_LISTENER = "UNABLE_TO_ADD_EVENT_LISTENER", 92 | DOCUMENT_EVENT_LISTENER_REMOVED = "DOCUMENT_EVENT_LISTENER_REMOVED", 93 | WINDOW_EVENT_LISTENER_REMOVED = "WINDOW_EVENT_LISTENER_REMOVED", 94 | 95 | // The following events correlate to the similarly named react-quill props 96 | // https://github.com/zenoamaro/react-quill/#props 97 | ON_CHANGE = "ON_CHANGE", 98 | ON_CHANGE_SELECTION = "ON_CHANGE_SELECTION", 99 | ON_FOCUS = "ON_FOCUS", 100 | ON_BLUR = "ON_BLUR", 101 | ON_KEY_PRESS = "ON_KEY_PRESS", 102 | ON_KEY_DOWN = "ON_KEY_DOWN", 103 | ON_KEY_UP = "ON_KEY_UP" 104 | } 105 | ``` 106 | 107 | The `payload` object contains the values passed to by the corresponding react-quilljs prop functions here: https://github.com/zenoamaro/react-quill/#props 108 | 109 | ## Changelog 110 | 111 | ### 0.9.0 112 | 113 | - Replace Expo dependency with expo-asset-utils 114 | 115 | ### 0.8.3 116 | 117 | - Updated onChangeCallback to receive the changes, contents, and previous contents of the delta 118 | 119 | ### 0.8.0 120 | 121 | - Removes the propType specification for contentToDisplay to address issue #19 122 | 123 | ### 0.6.5 124 | 125 | - Added initial testing framework 126 | 127 | ### 0.6.0 128 | 129 | - Removed requirement to download JavaScript files from GitHub in order for the package to work. JavaScript files are now inline with the HTML which enables the package to work without an Internet connection. 130 | 131 | - Added getViewerCallback and getEditorCallback 132 | 133 | ### 0.4.4 134 | 135 | - Changed HTTP path for files to be downloaded in preparation for potential inline JS bundling or Expo Packager bundling of files. 136 | 137 | ### 0.4.0 138 | 139 | - Added "backgroundColor" props for both the WebViewQuillViewer and WebViewQuillEditor 140 | 141 | ### 0.3.20 142 | 143 | - Added copy and paste of HTML 144 | 145 | ### 0.3.10 146 | 147 | - Revert file access features from 0.3.0. 148 | 149 | ### 0.3.0 150 | 151 | - Library no longer relies on accessing WebView files from the Internet. All files needed by both the Editor and Viewer are included with the packages. 152 | - Fonts display correctly on iOS devices 153 | 154 | ### 0.2.5 155 | 156 | - Added onContentChange property to Editor 157 | 158 | ### 2.0.0 159 | 160 | - Add TypeScript support 161 | - Switch to react-native-community/react-native-webview implementation 162 | - Simplify event communication 163 | 164 | ## LICENSE 165 | 166 | MIT 167 | --------------------------------------------------------------------------------