├── .gitmodules ├── LICENSE ├── README.md ├── examples ├── calc.lua ├── htdocs │ ├── FileChooser.css │ ├── FileChooser.js │ ├── FileChooser.lua │ ├── assets │ │ ├── fetch.umd.js │ │ ├── promise.min.js │ │ ├── toastui │ │ │ ├── LICENSE │ │ │ ├── codemirror.min.css │ │ │ ├── toastui-editor-all.min.js │ │ │ ├── toastui-editor-viewer.min.css │ │ │ ├── toastui-editor-viewer.min.js │ │ │ └── toastui-editor.min.css │ │ └── vue.min.js │ ├── calc.css │ ├── calc.html │ ├── calc.js │ ├── file.html │ ├── md.html │ ├── simple.html │ ├── todo.css │ ├── todo.html │ ├── todo.js │ ├── winexp.css │ ├── winexp.html │ ├── winexp.js │ └── winexp.lua ├── launch.lua ├── open.lua └── simple.lua ├── rock.mk ├── test.lua ├── webview-init.js ├── webview-launcher.lua └── webview.c /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "webview-c"] 2 | path = webview-c 3 | url = https://github.com/javalikescript/webview-c.git 4 | [submodule "MemoryModule"] 5 | path = MemoryModule 6 | url = https://github.com/fancycode/MemoryModule.git 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | "lua-webview" contained in the files "webview.c" and "webview-launcher.lua" is licensed under the MIT License as follows: 2 | 3 | ==== 4 | 5 | Copyright (c) 2019-2024 SPYL, javalikescript@free.fr 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | ==== 26 | 27 | The examples maintained within the "lua-webview" project, 28 | excluding the folder "examples/htdocs/assets" and the files "examples/htdocs/todo.*", 29 | are licensed under the MIT License unless otherwise noticed. 30 | 31 | 32 | 33 | This repository also contains external software/libraries, the related license information is provided below for information only. 34 | 35 | The dependent libraries are licensed under the MIT License: 36 | - "webview-c" contained in the folder "webview-c" is licensed under the MIT License see https://github.com/javalikescript/webview-c 37 | "webview-c" is based on prior work by Serge Zaitsev, see https://github.com/zserge/webview 38 | "webview-c" embeds part of the Microsoft Edge WebView2 SDK, see https://aka.ms/webviewnuget 39 | - "MemoryModule" contained in the folder "MemoryModule" is licensed under the Mozilla Public License 2.0 see https://github.com/fancycode/MemoryModule 40 | Copyright (c) 2004-2015 by Joachim Bauch 41 | 42 | The example dependent libraries are mainly licensed under the MIT License: 43 | - "fetch" is licensed under the MIT License see https://github.com/github/fetch/releases 44 | - "vuejs" is licensed under the MIT License see https://vuejs.org/ 45 | - "promise" is licensed under the MIT License see https://github.com/taylorhakes/promise-polyfill 46 | - "todo" is licensed under the MIT License see https://github.com/tastejs/todomvc/tree/gh-pages/examples/vue 47 | - "toastui" is licensed under the MIT License see https://github.com/nhn/tui.editor 48 | - "Babylon.js" is licensed under the Apache-2.0 License see https://github.com/BabylonJS/Babylon.js 49 | - "shapes.html" is in the public domain see https://github.com/end3r/MDN-Games-3D/blob/gh-pages/Babylon.js/shapes.html 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | The Lua webview module provides functions to open a web page in a dedicated window from [Lua](http://www.lua.org/). 4 | 5 | ```lua 6 | require('webview').open('http://www.lua.org/') 7 | ``` 8 | 9 | It uses *gtk-webkit2* on Linux and *MSHTML* (IE10/11) or *Edge* (Chromium) on Windows. 10 | 11 | Lua can evaluate JavaScript code and JavaScript can call a registered Lua function, see the `simple.lua` file in the examples. 12 | 13 | This module is a binding of the tiny cross-platform [webview-c](https://github.com/javalikescript/webview-c) C library. 14 | 15 | This module is part of the [luaclibs](https://github.com/javalikescript/luaclibs) project, 16 | the binaries can be found on the [luajls](https://github.com/javalikescript/luajls/releases/latest) releases. 17 | You could also install it using [LuaRocks](#luarocks). 18 | 19 | Lua webview is covered by the MIT license. 20 | 21 | ## Build 22 | 23 | The Lua webview module could be build using the rock makefile. 24 | 25 | ```lua 26 | make -f rock.mk PLAT=windows MAKE=make CC=gcc LD=gcc LUA_LIBDIR=.../lib LUA_INCDIR=.../include LUA=.../bin/lua54.exe 27 | ``` 28 | 29 | ## Usage 30 | 31 | [Fast Cut](https://github.com/javalikescript/fcut) is an advanced example of webview usage. 32 | 33 | It allows to visually cut and join videos then export them losslessly thanks to FFmpeg. 34 | 35 | ## Launcher 36 | 37 | An optional Lua launcher module `webview-launcher.lua` is available. 38 | The HTML scripts using the type `text/lua` will be loaded automatically. 39 | The Lua scripts could expose named functions with callbacks to JavaScript. 40 | 41 | ```html 42 | 43 | 50 | ``` 51 | or using a Lua file 52 | ```html 53 | 54 | ``` 55 | 56 | Additionally a JavaScript file `webview-init.js` is available to deal with the launcher initialization including in case of reloading. 57 | 58 | The launcher supports events in Lua when used with [luajls](https://github.com/javalikescript/luajls). 59 | 60 | ## Examples 61 | 62 | Using an HTTP server 63 | ```sh 64 | lua examples/calc.lua 65 | ``` 66 | 67 | 68 | 69 | 70 | Using the file system 71 | ```sh 72 | lua examples/open.lua %CD%\examples\htdocs\todo.html 73 | ``` 74 | 75 | 76 | 77 | 78 | Pure Lua 79 | ```sh 80 | wlua54 examples/simple.lua 81 | ``` 82 | 83 | 84 | 85 | 86 | Generic launcher, with helper function to pass JSON objects and more 87 | ```sh 88 | lua examples/launch.lua examples/htdocs/simple.html --wv-event=thread 89 | ``` 90 | 91 | ## LuaRocks 92 | 93 | Lua webview can be intalled using LuaRocks 94 | 95 | ### LuaRocks on Linux 96 | 97 | Prerequisites: 98 | ```sh 99 | sudo apt install luarocks lua5.3 lua5.3-dev 100 | sudo apt install libgtk-3-dev libwebkit2gtk-4.0-dev 101 | ``` 102 | 103 | ```sh 104 | luarocks install lua-webview --local 105 | ``` 106 | 107 | ### LuaRocks on Windows 108 | 109 | Prerequisites: 110 | Download the Lua 64 bits dynamic libraries built with MinGW-w64 from [Lua Binaries](https://sourceforge.net/projects/luabinaries/). 111 | Add [MSYS2](https://www.msys2.org/), MinGW-w64 and [git](https://git-scm.com/) in the path. 112 | 113 | 114 | ```Batchfile 115 | luarocks --lua-dir C:/bin/lua-5.3.5 MAKE=make CC=gcc LD=gcc install lua-webview 116 | ``` 117 | Take care to use forward slashes for the Lua path. 118 | -------------------------------------------------------------------------------- /examples/calc.lua: -------------------------------------------------------------------------------- 1 | local event = require('jls.lang.event') 2 | local File = require('jls.io.File') 3 | local WebView = require('jls.util.WebView') 4 | local FileHttpHandler = require('jls.net.http.handler.FileHttpHandler') 5 | local RestHttpHandler = require('jls.net.http.handler.RestHttpHandler') 6 | 7 | local scriptFile = File:new(arg[0]):getAbsoluteFile() 8 | local scriptDir = scriptFile:getParentFile() 9 | local htdocsDir = File:new(scriptDir, 'htdocs') 10 | 11 | -- localhost ::1 127.0.0.1 12 | WebView.open('http://localhost:0/calc.html', 'Calc', 320, 480, true):next(function(webview) 13 | local httpServer = webview:getHttpServer() 14 | print('WebView opened with HTTP Server bound on address', httpServer:getAddress()) 15 | httpServer:createContext('/(.*)', FileHttpHandler:new(htdocsDir)) 16 | httpServer:createContext('/rest/(.*)', RestHttpHandler:new({ 17 | ['calculate(requestJson)?method=POST&content-type=application/json'] = function(exchange, requestJson) 18 | local f, err = load('return '..tostring(requestJson.line)) 19 | return {line = f and f() or err or ''} 20 | end 21 | })) 22 | return webview:getThread():ended() 23 | end):next(function() 24 | print('WebView closed') 25 | end):catch(function(err) 26 | print('Cannot open webview due to '..tostring(err)) 27 | end) 28 | 29 | --print('Looping') 30 | event:loop() 31 | event:close() 32 | -------------------------------------------------------------------------------- /examples/htdocs/FileChooser.css: -------------------------------------------------------------------------------- 1 | .file-chooser-dialog { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | .file-chooser-content { 6 | flex-grow: 1; 7 | } 8 | .file-chooser-flex-row { 9 | min-width: 100%; 10 | display: flex; 11 | flex-direction: row; 12 | } 13 | .file-chooser-flex-row > * { 14 | margin-top: auto; 15 | margin-bottom: auto; 16 | } 17 | .file-chooser-flex-row-content { 18 | flex-grow: 1; 19 | } 20 | .file-chooser-footer.file-chooser-flex-row { 21 | justify-content: flex-end; 22 | } 23 | .file-chooser-dialog ul { 24 | list-style-type: none; 25 | } 26 | .file-chooser-directory { 27 | text-decoration: underline; 28 | } 29 | -------------------------------------------------------------------------------- /examples/htdocs/FileChooser.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | function directoryFirst(fa, fb) { 4 | var da = fa.isDirectory; 5 | var db = fb.isDirectory; 6 | return da === db ? 0 : (da ? -1 : 1); 7 | } 8 | 9 | function filterFiles(files, showAll, contains) { 10 | var containsLowerCase = contains && contains.toLowerCase(); 11 | return files.filter(function(file) { 12 | if (!showAll && file.name.charAt(0) === '.' && file.name !== '..') { 13 | return false; 14 | } 15 | return file.isDirectory || !containsLowerCase || file.name.toLowerCase().indexOf(containsLowerCase) >= 0; 16 | }) 17 | } 18 | 19 | function fileList(path, useFetch, callback) { 20 | if (useFetch) { 21 | if (webview && webview.fileList) { 22 | webview.fileList(path, callback); 23 | } else { 24 | callback('webview.fileList is not available'); 25 | } 26 | } else { 27 | fetch('rest/listFiles', { 28 | method: 'POST', 29 | headers: { 30 | "Content-Type": "text/plain" 31 | }, 32 | body: path 33 | }).then(function(response) { 34 | return response.json(); 35 | }).then(function(list) { 36 | callback(null, list); 37 | }, function(reason) { 38 | callback(reason || 'Unknown error'); 39 | }); 40 | } 41 | } 42 | 43 | var FileChooserDialog = Vue.component('file-chooser-dialog', { 44 | template: '
' + 45 | '
' + 46 | ' ' + 47 | ' ' + 48 | '
' + 49 | '
' + 50 | ' ' + 56 | '
' + 57 | '
' + 58 | ' ' + 59 | ' Filter:' + 60 | ' ' + 61 | ' ' + 62 | ' Show All' + 63 | '
' + 64 | '
', 69 | data: function() { return { 70 | extention: '', 71 | inputPath: '', 72 | name: '', 73 | path: '', 74 | files: [], 75 | label: 'Open', 76 | multiple: false, 77 | save: false, 78 | directory: false, 79 | showAll: false, 80 | showCancel: true, 81 | showSettings: false, 82 | fetch: false 83 | }; }, 84 | methods: { 85 | onFilePressed: function(file) { 86 | if (file.isDirectory) { 87 | this.setPath(this.path + '/' + file.name); 88 | return; 89 | } 90 | if (!this.multiple) { 91 | this.files.forEach(function(f) { 92 | if (f !== file) { 93 | f.selected = false; 94 | } 95 | }); 96 | } 97 | if (!this.directory) { 98 | if (this.save) { 99 | this.name = file.name; 100 | } 101 | file.selected = !file.selected; 102 | } 103 | }, 104 | setPath: function(path) { 105 | if (this.path !== path) { 106 | this.list(path); 107 | } 108 | }, 109 | refresh: function() { 110 | if (this.save) { 111 | this.multiple = false; 112 | } 113 | this.list(this.path !== '' ? this.path : '.') 114 | }, 115 | list: function(path) { 116 | var fc = this; 117 | fileList(path, this.fetch, function(reason, files) { 118 | if (files) { 119 | var path = files.shift(); 120 | fc.show(path, files); 121 | } else { 122 | fc.error(reason); 123 | } 124 | }); 125 | }, 126 | error: function(message) { 127 | console.error('file-chooser error', message); 128 | this.$emit('selected', []); 129 | }, 130 | cancel: function() { 131 | this.$emit('selected', []); 132 | }, 133 | done: function() { 134 | var files; 135 | if (this.directory) { 136 | files = []; 137 | } else if (this.save) { 138 | files = [this.name]; 139 | } else { 140 | files = this.files.filter(function(file) { 141 | return file.selected; 142 | }).map(function(file) { 143 | return file.name; 144 | }); 145 | } 146 | files.unshift(this.path) 147 | this.$emit('selected', files) 148 | }, 149 | show: function(path, files) { 150 | this.inputPath = path; 151 | this.path = path; 152 | if (!Array.isArray(files)) { 153 | files = []; 154 | } 155 | files.forEach(function(file) { 156 | file.selected = false; 157 | file.selectable = true; 158 | }); 159 | this.files = files; 160 | } 161 | }, 162 | computed: { 163 | filteredList: function () { 164 | var files = filterFiles(this.files, this.showAll, this.extention); 165 | files.sort(directoryFirst); 166 | return files; 167 | } 168 | } 169 | }); 170 | 171 | FileChooserDialog.show = function(vm, options) { 172 | var fileChooser = new FileChooserDialog(); 173 | fileChooser.$mount(); 174 | if (typeof options === 'object') { 175 | for (var k in options) { 176 | fileChooser[k] = options[k]; 177 | } 178 | } 179 | vm.$el.appendChild(fileChooser.$el); 180 | fileChooser.$on('selected', function(files) { 181 | vm.$el.removeChild(fileChooser.$el); 182 | fileChooser.$destroy(); 183 | fileChooser = null; 184 | }); 185 | return fileChooser; 186 | } 187 | 188 | Vue.component('file-chooser-input', { 189 | template: '' + 190 | '' + 192 | '' + 193 | ' ', 195 | data: function() { return { 196 | name: '', 197 | path: '', 198 | files: [], 199 | size: 40, 200 | fetch: false 201 | }; }, 202 | methods: { 203 | nameChanged: function() { 204 | console.info('nameChanged() "' + this.name + '"'); 205 | var value = this.name; 206 | if (this.path === '') { 207 | this.list('.'); 208 | return; 209 | } 210 | if (value === '') { 211 | return; 212 | } 213 | for (var i = 0; i < this.files.length; i++) { 214 | var file = this.files[i]; 215 | if (file.isDirectory && file.name === value) { 216 | this.list(this.path + '/' + file.name); 217 | break; 218 | } 219 | } 220 | }, 221 | clean: function() { 222 | if (this.path === '') { 223 | this.list('.'); 224 | } else if (this.name !== '') { 225 | console.info('clean() "' + this.path + '"'); 226 | this.name = ''; 227 | } 228 | }, 229 | refresh: function() { 230 | this.list(this.path !== '' ? this.path : '.') 231 | }, 232 | list: function(path) { 233 | console.info('list("' + path + '")'); 234 | var fc = this; 235 | fileList(path, this.fetch, function(reason, files) { 236 | if (files) { 237 | var path = files.shift(); 238 | fc.show(path, files); 239 | } else { 240 | this.error(reason); 241 | } 242 | }); 243 | }, 244 | error: function(message) { 245 | console.error('file-chooser error', message); 246 | this.$emit('selected', []); 247 | }, 248 | show: function(path, files) { 249 | console.info('show("' + path + '")'); 250 | this.path = path; 251 | if (!Array.isArray(files)) { 252 | files = []; 253 | } 254 | this.files = files; 255 | this.name = ''; 256 | } 257 | }, 258 | computed: { 259 | filteredList: function () { 260 | var files = filterFiles(this.files); 261 | files.sort(directoryFirst); 262 | return files; 263 | }, 264 | placeholder: function() { 265 | if (this.path) { 266 | return this.path.length <= this.size ? this.path : '...' + this.path.slice(3-this.size); 267 | } 268 | return 'Click to browse'; 269 | } 270 | } 271 | }); 272 | 273 | })(); -------------------------------------------------------------------------------- /examples/htdocs/FileChooser.lua: -------------------------------------------------------------------------------- 1 | local Path = require('jls.io.Path') 2 | local File = require('jls.io.File') 3 | 4 | local function listFiles(value, callback) 5 | if type(callback) ~= 'function' then 6 | return 7 | end 8 | local dir = File:new(value or '.'):getAbsoluteFile() 9 | local files = dir:listFiles() 10 | local parent = dir:getParent() 11 | if files then 12 | local list = {} 13 | if parent then 14 | table.insert(list, { 15 | name = '..', 16 | isDirectory = true, 17 | length = 0, 18 | lastModified = dir:lastModified(), 19 | }) 20 | end 21 | for _, file in ipairs(files) do 22 | table.insert(list, { 23 | name = file:getName(), 24 | isDirectory = file:isDirectory(), 25 | length = file:length(), 26 | lastModified = file:lastModified(), 27 | }) 28 | end 29 | local path = Path.normalizePath(dir:getPath()) 30 | --print('listFiles('..tostring(value)..') "'..tostring(path)..'" found '..tostring(#list)..' entries, parent: "'..tostring(parent)..'"') 31 | table.insert(list, 1, path) 32 | callback(nil, list) 33 | end 34 | end 35 | 36 | if expose ~= nil then 37 | -- loaded as html src 38 | expose('fileList', listFiles) 39 | else 40 | -- loaded as module 41 | return { 42 | listFiles = listFiles 43 | } 44 | end 45 | -------------------------------------------------------------------------------- /examples/htdocs/assets/fetch.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 3 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 4 | (factory((global.WHATWGFetch = {}))); 5 | }(this, (function (exports) { 'use strict'; 6 | 7 | var support = { 8 | searchParams: 'URLSearchParams' in self, 9 | iterable: 'Symbol' in self && 'iterator' in Symbol, 10 | blob: 11 | 'FileReader' in self && 12 | 'Blob' in self && 13 | (function() { 14 | try { 15 | new Blob(); 16 | return true 17 | } catch (e) { 18 | return false 19 | } 20 | })(), 21 | formData: 'FormData' in self, 22 | arrayBuffer: 'ArrayBuffer' in self 23 | }; 24 | 25 | function isDataView(obj) { 26 | return obj && DataView.prototype.isPrototypeOf(obj) 27 | } 28 | 29 | if (support.arrayBuffer) { 30 | var viewClasses = [ 31 | '[object Int8Array]', 32 | '[object Uint8Array]', 33 | '[object Uint8ClampedArray]', 34 | '[object Int16Array]', 35 | '[object Uint16Array]', 36 | '[object Int32Array]', 37 | '[object Uint32Array]', 38 | '[object Float32Array]', 39 | '[object Float64Array]' 40 | ]; 41 | 42 | var isArrayBufferView = 43 | ArrayBuffer.isView || 44 | function(obj) { 45 | return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1 46 | }; 47 | } 48 | 49 | function normalizeName(name) { 50 | if (typeof name !== 'string') { 51 | name = String(name); 52 | } 53 | if (/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(name)) { 54 | throw new TypeError('Invalid character in header field name') 55 | } 56 | return name.toLowerCase() 57 | } 58 | 59 | function normalizeValue(value) { 60 | if (typeof value !== 'string') { 61 | value = String(value); 62 | } 63 | return value 64 | } 65 | 66 | // Build a destructive iterator for the value list 67 | function iteratorFor(items) { 68 | var iterator = { 69 | next: function() { 70 | var value = items.shift(); 71 | return {done: value === undefined, value: value} 72 | } 73 | }; 74 | 75 | if (support.iterable) { 76 | iterator[Symbol.iterator] = function() { 77 | return iterator 78 | }; 79 | } 80 | 81 | return iterator 82 | } 83 | 84 | function Headers(headers) { 85 | this.map = {}; 86 | 87 | if (headers instanceof Headers) { 88 | headers.forEach(function(value, name) { 89 | this.append(name, value); 90 | }, this); 91 | } else if (Array.isArray(headers)) { 92 | headers.forEach(function(header) { 93 | this.append(header[0], header[1]); 94 | }, this); 95 | } else if (headers) { 96 | Object.getOwnPropertyNames(headers).forEach(function(name) { 97 | this.append(name, headers[name]); 98 | }, this); 99 | } 100 | } 101 | 102 | Headers.prototype.append = function(name, value) { 103 | name = normalizeName(name); 104 | value = normalizeValue(value); 105 | var oldValue = this.map[name]; 106 | this.map[name] = oldValue ? oldValue + ', ' + value : value; 107 | }; 108 | 109 | Headers.prototype['delete'] = function(name) { 110 | delete this.map[normalizeName(name)]; 111 | }; 112 | 113 | Headers.prototype.get = function(name) { 114 | name = normalizeName(name); 115 | return this.has(name) ? this.map[name] : null 116 | }; 117 | 118 | Headers.prototype.has = function(name) { 119 | return this.map.hasOwnProperty(normalizeName(name)) 120 | }; 121 | 122 | Headers.prototype.set = function(name, value) { 123 | this.map[normalizeName(name)] = normalizeValue(value); 124 | }; 125 | 126 | Headers.prototype.forEach = function(callback, thisArg) { 127 | for (var name in this.map) { 128 | if (this.map.hasOwnProperty(name)) { 129 | callback.call(thisArg, this.map[name], name, this); 130 | } 131 | } 132 | }; 133 | 134 | Headers.prototype.keys = function() { 135 | var items = []; 136 | this.forEach(function(value, name) { 137 | items.push(name); 138 | }); 139 | return iteratorFor(items) 140 | }; 141 | 142 | Headers.prototype.values = function() { 143 | var items = []; 144 | this.forEach(function(value) { 145 | items.push(value); 146 | }); 147 | return iteratorFor(items) 148 | }; 149 | 150 | Headers.prototype.entries = function() { 151 | var items = []; 152 | this.forEach(function(value, name) { 153 | items.push([name, value]); 154 | }); 155 | return iteratorFor(items) 156 | }; 157 | 158 | if (support.iterable) { 159 | Headers.prototype[Symbol.iterator] = Headers.prototype.entries; 160 | } 161 | 162 | function consumed(body) { 163 | if (body.bodyUsed) { 164 | return Promise.reject(new TypeError('Already read')) 165 | } 166 | body.bodyUsed = true; 167 | } 168 | 169 | function fileReaderReady(reader) { 170 | return new Promise(function(resolve, reject) { 171 | reader.onload = function() { 172 | resolve(reader.result); 173 | }; 174 | reader.onerror = function() { 175 | reject(reader.error); 176 | }; 177 | }) 178 | } 179 | 180 | function readBlobAsArrayBuffer(blob) { 181 | var reader = new FileReader(); 182 | var promise = fileReaderReady(reader); 183 | reader.readAsArrayBuffer(blob); 184 | return promise 185 | } 186 | 187 | function readBlobAsText(blob) { 188 | var reader = new FileReader(); 189 | var promise = fileReaderReady(reader); 190 | reader.readAsText(blob); 191 | return promise 192 | } 193 | 194 | function readArrayBufferAsText(buf) { 195 | var view = new Uint8Array(buf); 196 | var chars = new Array(view.length); 197 | 198 | for (var i = 0; i < view.length; i++) { 199 | chars[i] = String.fromCharCode(view[i]); 200 | } 201 | return chars.join('') 202 | } 203 | 204 | function bufferClone(buf) { 205 | if (buf.slice) { 206 | return buf.slice(0) 207 | } else { 208 | var view = new Uint8Array(buf.byteLength); 209 | view.set(new Uint8Array(buf)); 210 | return view.buffer 211 | } 212 | } 213 | 214 | function Body() { 215 | this.bodyUsed = false; 216 | 217 | this._initBody = function(body) { 218 | this._bodyInit = body; 219 | if (!body) { 220 | this._bodyText = ''; 221 | } else if (typeof body === 'string') { 222 | this._bodyText = body; 223 | } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { 224 | this._bodyBlob = body; 225 | } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { 226 | this._bodyFormData = body; 227 | } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { 228 | this._bodyText = body.toString(); 229 | } else if (support.arrayBuffer && support.blob && isDataView(body)) { 230 | this._bodyArrayBuffer = bufferClone(body.buffer); 231 | // IE 10-11 can't handle a DataView body. 232 | this._bodyInit = new Blob([this._bodyArrayBuffer]); 233 | } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { 234 | this._bodyArrayBuffer = bufferClone(body); 235 | } else { 236 | this._bodyText = body = Object.prototype.toString.call(body); 237 | } 238 | 239 | if (!this.headers.get('content-type')) { 240 | if (typeof body === 'string') { 241 | this.headers.set('content-type', 'text/plain;charset=UTF-8'); 242 | } else if (this._bodyBlob && this._bodyBlob.type) { 243 | this.headers.set('content-type', this._bodyBlob.type); 244 | } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { 245 | this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8'); 246 | } 247 | } 248 | }; 249 | 250 | if (support.blob) { 251 | this.blob = function() { 252 | var rejected = consumed(this); 253 | if (rejected) { 254 | return rejected 255 | } 256 | 257 | if (this._bodyBlob) { 258 | return Promise.resolve(this._bodyBlob) 259 | } else if (this._bodyArrayBuffer) { 260 | return Promise.resolve(new Blob([this._bodyArrayBuffer])) 261 | } else if (this._bodyFormData) { 262 | throw new Error('could not read FormData body as blob') 263 | } else { 264 | return Promise.resolve(new Blob([this._bodyText])) 265 | } 266 | }; 267 | 268 | this.arrayBuffer = function() { 269 | if (this._bodyArrayBuffer) { 270 | return consumed(this) || Promise.resolve(this._bodyArrayBuffer) 271 | } else { 272 | return this.blob().then(readBlobAsArrayBuffer) 273 | } 274 | }; 275 | } 276 | 277 | this.text = function() { 278 | var rejected = consumed(this); 279 | if (rejected) { 280 | return rejected 281 | } 282 | 283 | if (this._bodyBlob) { 284 | return readBlobAsText(this._bodyBlob) 285 | } else if (this._bodyArrayBuffer) { 286 | return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer)) 287 | } else if (this._bodyFormData) { 288 | throw new Error('could not read FormData body as text') 289 | } else { 290 | return Promise.resolve(this._bodyText) 291 | } 292 | }; 293 | 294 | if (support.formData) { 295 | this.formData = function() { 296 | return this.text().then(decode) 297 | }; 298 | } 299 | 300 | this.json = function() { 301 | return this.text().then(JSON.parse) 302 | }; 303 | 304 | return this 305 | } 306 | 307 | // HTTP methods whose capitalization should be normalized 308 | var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']; 309 | 310 | function normalizeMethod(method) { 311 | var upcased = method.toUpperCase(); 312 | return methods.indexOf(upcased) > -1 ? upcased : method 313 | } 314 | 315 | function Request(input, options) { 316 | options = options || {}; 317 | var body = options.body; 318 | 319 | if (input instanceof Request) { 320 | if (input.bodyUsed) { 321 | throw new TypeError('Already read') 322 | } 323 | this.url = input.url; 324 | this.credentials = input.credentials; 325 | if (!options.headers) { 326 | this.headers = new Headers(input.headers); 327 | } 328 | this.method = input.method; 329 | this.mode = input.mode; 330 | this.signal = input.signal; 331 | if (!body && input._bodyInit != null) { 332 | body = input._bodyInit; 333 | input.bodyUsed = true; 334 | } 335 | } else { 336 | this.url = String(input); 337 | } 338 | 339 | this.credentials = options.credentials || this.credentials || 'same-origin'; 340 | if (options.headers || !this.headers) { 341 | this.headers = new Headers(options.headers); 342 | } 343 | this.method = normalizeMethod(options.method || this.method || 'GET'); 344 | this.mode = options.mode || this.mode || null; 345 | this.signal = options.signal || this.signal; 346 | this.referrer = null; 347 | 348 | if ((this.method === 'GET' || this.method === 'HEAD') && body) { 349 | throw new TypeError('Body not allowed for GET or HEAD requests') 350 | } 351 | this._initBody(body); 352 | } 353 | 354 | Request.prototype.clone = function() { 355 | return new Request(this, {body: this._bodyInit}) 356 | }; 357 | 358 | function decode(body) { 359 | var form = new FormData(); 360 | body 361 | .trim() 362 | .split('&') 363 | .forEach(function(bytes) { 364 | if (bytes) { 365 | var split = bytes.split('='); 366 | var name = split.shift().replace(/\+/g, ' '); 367 | var value = split.join('=').replace(/\+/g, ' '); 368 | form.append(decodeURIComponent(name), decodeURIComponent(value)); 369 | } 370 | }); 371 | return form 372 | } 373 | 374 | function parseHeaders(rawHeaders) { 375 | var headers = new Headers(); 376 | // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space 377 | // https://tools.ietf.org/html/rfc7230#section-3.2 378 | var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' '); 379 | preProcessedHeaders.split(/\r?\n/).forEach(function(line) { 380 | var parts = line.split(':'); 381 | var key = parts.shift().trim(); 382 | if (key) { 383 | var value = parts.join(':').trim(); 384 | headers.append(key, value); 385 | } 386 | }); 387 | return headers 388 | } 389 | 390 | Body.call(Request.prototype); 391 | 392 | function Response(bodyInit, options) { 393 | if (!options) { 394 | options = {}; 395 | } 396 | 397 | this.type = 'default'; 398 | this.status = options.status === undefined ? 200 : options.status; 399 | this.ok = this.status >= 200 && this.status < 300; 400 | this.statusText = 'statusText' in options ? options.statusText : 'OK'; 401 | this.headers = new Headers(options.headers); 402 | this.url = options.url || ''; 403 | this._initBody(bodyInit); 404 | } 405 | 406 | Body.call(Response.prototype); 407 | 408 | Response.prototype.clone = function() { 409 | return new Response(this._bodyInit, { 410 | status: this.status, 411 | statusText: this.statusText, 412 | headers: new Headers(this.headers), 413 | url: this.url 414 | }) 415 | }; 416 | 417 | Response.error = function() { 418 | var response = new Response(null, {status: 0, statusText: ''}); 419 | response.type = 'error'; 420 | return response 421 | }; 422 | 423 | var redirectStatuses = [301, 302, 303, 307, 308]; 424 | 425 | Response.redirect = function(url, status) { 426 | if (redirectStatuses.indexOf(status) === -1) { 427 | throw new RangeError('Invalid status code') 428 | } 429 | 430 | return new Response(null, {status: status, headers: {location: url}}) 431 | }; 432 | 433 | exports.DOMException = self.DOMException; 434 | try { 435 | new exports.DOMException(); 436 | } catch (err) { 437 | exports.DOMException = function(message, name) { 438 | this.message = message; 439 | this.name = name; 440 | var error = Error(message); 441 | this.stack = error.stack; 442 | }; 443 | exports.DOMException.prototype = Object.create(Error.prototype); 444 | exports.DOMException.prototype.constructor = exports.DOMException; 445 | } 446 | 447 | function fetch(input, init) { 448 | return new Promise(function(resolve, reject) { 449 | var request = new Request(input, init); 450 | 451 | if (request.signal && request.signal.aborted) { 452 | return reject(new exports.DOMException('Aborted', 'AbortError')) 453 | } 454 | 455 | var xhr = new XMLHttpRequest(); 456 | 457 | function abortXhr() { 458 | xhr.abort(); 459 | } 460 | 461 | xhr.onload = function() { 462 | var options = { 463 | status: xhr.status, 464 | statusText: xhr.statusText, 465 | headers: parseHeaders(xhr.getAllResponseHeaders() || '') 466 | }; 467 | options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL'); 468 | var body = 'response' in xhr ? xhr.response : xhr.responseText; 469 | resolve(new Response(body, options)); 470 | }; 471 | 472 | xhr.onerror = function() { 473 | reject(new TypeError('Network request failed')); 474 | }; 475 | 476 | xhr.ontimeout = function() { 477 | reject(new TypeError('Network request failed')); 478 | }; 479 | 480 | xhr.onabort = function() { 481 | reject(new exports.DOMException('Aborted', 'AbortError')); 482 | }; 483 | 484 | xhr.open(request.method, request.url, true); 485 | 486 | if (request.credentials === 'include') { 487 | xhr.withCredentials = true; 488 | } else if (request.credentials === 'omit') { 489 | xhr.withCredentials = false; 490 | } 491 | 492 | if ('responseType' in xhr && support.blob) { 493 | xhr.responseType = 'blob'; 494 | } 495 | 496 | request.headers.forEach(function(value, name) { 497 | xhr.setRequestHeader(name, value); 498 | }); 499 | 500 | if (request.signal) { 501 | request.signal.addEventListener('abort', abortXhr); 502 | 503 | xhr.onreadystatechange = function() { 504 | // DONE (success or failure) 505 | if (xhr.readyState === 4) { 506 | request.signal.removeEventListener('abort', abortXhr); 507 | } 508 | }; 509 | } 510 | 511 | xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit); 512 | }) 513 | } 514 | 515 | fetch.polyfill = true; 516 | 517 | if (!self.fetch) { 518 | self.fetch = fetch; 519 | self.Headers = Headers; 520 | self.Request = Request; 521 | self.Response = Response; 522 | } 523 | 524 | exports.Headers = Headers; 525 | exports.Request = Request; 526 | exports.Response = Response; 527 | exports.fetch = fetch; 528 | 529 | Object.defineProperty(exports, '__esModule', { value: true }); 530 | 531 | }))); 532 | -------------------------------------------------------------------------------- /examples/htdocs/assets/promise.min.js: -------------------------------------------------------------------------------- 1 | !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n():"function"==typeof define&&define.amd?define(n):n()}(0,function(){"use strict";function e(e){var n=this.constructor;return this.then(function(t){return n.resolve(e()).then(function(){return t})},function(t){return n.resolve(e()).then(function(){return n.reject(t)})})}function n(e){return!(!e||"undefined"==typeof e.length)}function t(){}function o(e){if(!(this instanceof o))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=undefined,this._deferreds=[],c(e,this)}function r(e,n){for(;3===e._state;)e=e._value;0!==e._state?(e._handled=!0,o._immediateFn(function(){var t=1===e._state?n.onFulfilled:n.onRejected;if(null!==t){var o;try{o=t(e._value)}catch(r){return void f(n.promise,r)}i(n.promise,o)}else(1===e._state?i:f)(n.promise,e._value)})):e._deferreds.push(n)}function i(e,n){try{if(n===e)throw new TypeError("A promise cannot be resolved with itself.");if(n&&("object"==typeof n||"function"==typeof n)){var t=n.then;if(n instanceof o)return e._state=3,e._value=n,void u(e);if("function"==typeof t)return void c(function(e,n){return function(){e.apply(n,arguments)}}(t,n),e)}e._state=1,e._value=n,u(e)}catch(r){f(e,r)}}function f(e,n){e._state=2,e._value=n,u(e)}function u(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var n=0,t=e._deferreds.length;t>n;n++)r(e,e._deferreds[n]);e._deferreds=null}function c(e,n){var t=!1;try{e(function(e){t||(t=!0,i(n,e))},function(e){t||(t=!0,f(n,e))})}catch(o){if(t)return;t=!0,f(n,o)}}var a=setTimeout;o.prototype["catch"]=function(e){return this.then(null,e)},o.prototype.then=function(e,n){var o=new this.constructor(t);return r(this,new function(e,n,t){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof n?n:null,this.promise=t}(e,n,o)),o},o.prototype["finally"]=e,o.all=function(e){return new o(function(t,o){function r(e,n){try{if(n&&("object"==typeof n||"function"==typeof n)){var u=n.then;if("function"==typeof u)return void u.call(n,function(n){r(e,n)},o)}i[e]=n,0==--f&&t(i)}catch(c){o(c)}}if(!n(e))return o(new TypeError("Promise.all accepts an array"));var i=Array.prototype.slice.call(e);if(0===i.length)return t([]);for(var f=i.length,u=0;i.length>u;u++)r(u,i[u])})},o.resolve=function(e){return e&&"object"==typeof e&&e.constructor===o?e:new o(function(n){n(e)})},o.reject=function(e){return new o(function(n,t){t(e)})},o.race=function(e){return new o(function(t,r){if(!n(e))return r(new TypeError("Promise.race accepts an array"));for(var i=0,f=e.length;f>i;i++)o.resolve(e[i]).then(t,r)})},o._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){a(e,0)},o._unhandledRejectionFn=function(e){void 0!==console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)};var l=function(){if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if("undefined"!=typeof global)return global;throw Error("unable to locate global object")}();"Promise"in l?l.Promise.prototype["finally"]||(l.Promise.prototype["finally"]=e):l.Promise=o}); 2 | -------------------------------------------------------------------------------- /examples/htdocs/assets/toastui/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 NHN Corp. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /examples/htdocs/assets/toastui/codemirror.min.css: -------------------------------------------------------------------------------- 1 | .CodeMirror{font-family:monospace;height:300px;color:#000;direction:ltr}.CodeMirror-lines{padding:4px 0}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{padding:0 4px}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:#fff}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid #000;border-right:none;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0!important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-fat-cursor-mark{background-color:rgba(20,255,20,.5);-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite}.cm-animate-fat-cursor{width:auto;border:0;-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite;background-color:#7e7}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-rulers{position:absolute;left:0;right:0;top:-50px;bottom:0;overflow:hidden}.CodeMirror-ruler{border-left:1px solid #ccc;top:0;bottom:0;position:absolute}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-type,.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta{color:#555}.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-s-default .cm-error{color:red}.cm-invalidchar{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0b0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:#fff}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:0;position:relative}.CodeMirror-sizer{position:relative;border-right:30px solid transparent}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-30px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:0 0!important;border:none!important}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper ::selection{background-color:transparent}.CodeMirror-gutter-wrapper ::-moz-selection{background-color:transparent}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:0 0;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:contextual;font-variant-ligatures:contextual}.CodeMirror-wrap pre.CodeMirror-line,.CodeMirror-wrap pre.CodeMirror-line-like{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;padding:.1px}.CodeMirror-rtl pre{direction:rtl}.CodeMirror-code{outline:0}.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber,.CodeMirror-scroll,.CodeMirror-sizer{-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-cursor{position:absolute;pointer-events:none}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-focused div.CodeMirror-cursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background-color:#ffa;background-color:rgba(255,255,0,.4)}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:''}span.CodeMirror-selectedtext{background:0 0} 2 | /*# sourceMappingURL=codemirror.min.css.map */ -------------------------------------------------------------------------------- /examples/htdocs/assets/toastui/toastui-editor-viewer.min.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /*! 3 | * @toast-ui/editor 4 | * @version 2.5.1 | Tue Nov 24 2020 5 | * @author NHN FE Development Lab 6 | * @license MIT 7 | */.tui-editor-contents{margin:0;padding:0;font-size:13px;font-family:Open Sans,Helvetica Neue,Helvetica,Arial,나눔바른고딕,Nanum Barun Gothic,맑은고딕,Malgun Gothic,sans-serif}.tui-editor-contents :not(table){line-height:160%;box-sizing:content-box}.tui-editor-contents address,.tui-editor-contents cite,.tui-editor-contents dfn,.tui-editor-contents em,.tui-editor-contents i,.tui-editor-contents var{font-style:italic}.tui-editor-contents strong{font-weight:700}.tui-editor-contents p{margin:10px 0;color:#222}.tui-editor-contents>div>div:first-of-type h1,.tui-editor-contents>h1:first-of-type{margin-top:14px}.tui-editor-contents h1,.tui-editor-contents h2,.tui-editor-contents h3,.tui-editor-contents h4,.tui-editor-contents h5,.tui-editor-contents h6{font-weight:700;color:#222}.tui-editor-contents h1{font-size:24px;line-height:28px;border-bottom:3px double #999;margin:52px 0 15px;padding-bottom:7px}.tui-editor-contents h2{font-size:22px;line-height:23px;border-bottom:1px solid #dbdbdb;margin:20px 0 13px;padding-bottom:7px}.tui-editor-contents h3{font-size:20px;margin:18px 0 2px}.tui-editor-contents h4{font-size:18px;margin:10px 0 2px}.tui-editor-contents h3,.tui-editor-contents h4{line-height:18px}.tui-editor-contents h5{font-size:16px}.tui-editor-contents h6{font-size:14px}.tui-editor-contents h5,.tui-editor-contents h6{line-height:17px;margin:9px 0 -4px}.tui-editor-contents del{color:#999}.tui-editor-contents blockquote{margin:14px 0;border-left:4px solid #e5e5e5;padding:0 16px;color:#999}.tui-editor-contents blockquote ol,.tui-editor-contents blockquote p,.tui-editor-contents blockquote ul{color:#999}.tui-editor-contents blockquote>:first-child{margin-top:0}.tui-editor-contents blockquote>:last-child{margin-bottom:0}.tui-editor-contents code,.tui-editor-contents pre{font-family:Consolas,Courier,Apple SD 산돌고딕 Neo,-apple-system,Lucida Grande,Apple SD Gothic Neo,맑은 고딕,Malgun Gothic,Segoe UI,돋움,dotum,sans-serif;border:0;border-radius:0}.tui-editor-contents pre{margin:2px 0 8px;padding:18px;background-color:#f5f7f8}.tui-editor-contents code{color:#c1798b;background-color:#f9f2f4;padding:2px 3px;letter-spacing:-.3px;border-radius:2px}.tui-editor-contents pre code{padding:0;color:inherit;white-space:pre-wrap;background-color:transparent}.tui-editor-contents pre.addon{border:1px solid #e8ebed;background-color:#fff}.tui-editor-contents img{margin:4px 0 10px;box-sizing:border-box;vertical-align:top;max-width:100%}.tui-editor-contents table{border:1px solid rgba(0,0,0,.1);margin:12px 0 14px;color:#222;width:auto;border-collapse:collapse;box-sizing:border-box}.tui-editor-contents table td,.tui-editor-contents table th{border:1px solid rgba(0,0,0,.1);padding:5px 14px 5px 12px;height:32px}.tui-editor-contents table th{background-color:#555;font-weight:300;color:#fff;padding-top:6px}.tui-editor-contents dir,.tui-editor-contents menu,.tui-editor-contents ol,.tui-editor-contents ul{display:block;list-style-type:none;padding-left:24px;margin:6px 0 10px;color:#222}.tui-editor-contents ol{list-style-type:none;counter-reset:li}.tui-editor-contents ol>li{counter-increment:li}.tui-editor-contents ol>li:before,.tui-editor-contents ul>li:before{display:inline-block;position:absolute}.tui-editor-contents ul>li:before{content:"";margin-top:6px;margin-left:-17px;width:5px;height:5px;border-radius:50%;background-color:#ccc}.tui-editor-contents ol>li:before{content:"." counter(li);margin-left:-28px;width:24px;text-align:right;direction:rtl;color:#aaa}.tui-editor-contents ol ol,.tui-editor-contents ol ul,.tui-editor-contents ul ol,.tui-editor-contents ul ul{margin-top:0!important;margin-bottom:0!important}.tui-editor-contents ol li,.tui-editor-contents ul li{position:relative}.tui-editor-contents ol p,.tui-editor-contents ul p{margin:0}.tui-editor-contents ol li.task-list-item:before,.tui-editor-contents pre ul li:before,.tui-editor-contents ul li.task-list-item:before{content:""}.tui-editor-contents th ol,.tui-editor-contents th ul{color:#fff}.tui-editor-contents hr{border-top:1px solid #eee;margin:16px 0}.tui-editor-contents a{text-decoration:underline;color:#4b96e6}.tui-editor-contents a:hover{color:#1f70de}.tui-editor-contents a.image-link{position:relative}.tui-editor-contents a.image-link:before{content:"";position:absolute;margin:0;width:20px;height:20px;top:2px;right:2px;background-repeat:no-repeat;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAFKADAAQAAAABAAAAFAAAAACy3fD9AAAA/0lEQVQ4Ee2UIY6FQAyG/91wAQQJDg8SSwI3QIFAcQHuwFHQoOAEEFAELB6H4wIku+9vQgIP9zLyVbTTTufLtJ3MzzRNf1AoGlmu6ypBzvOMXyWkC+QLvDTjw6VM+Xr2OA6UZYmu67Dvu2zleX6zuq7D933EcQxNuyPu3usYYXVdw/M8mKYpIMMwxEZRJHbbNsmhkySJxE71APJmhGVZhnVdURQFlmU585GmKSzLEp+570Dlz+ZxQ/aGJVNYsm3bCIJA/LZtMY4jmqbBMAwIw1DiV/UAstEUltP3vawdxxFbVZVYDoWwM1eCp+LnoErIUt7DL/Ac1edWng1/WlXyD380myY5A34sAAAAAElFTkSuQmCC");cursor:pointer}.tui-editor-contents .task-list-item{border:0;list-style:none;padding-left:24px;margin-left:-24px}.tui-editor-contents .task-list-item:before{background-repeat:no-repeat;background-size:18px 18px;background-position:50%;content:"";margin-left:0;margin-top:0;border-radius:0;height:18px;width:18px;position:absolute;left:0;top:1px;cursor:pointer;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEqADAAQAAAABAAAAEgAAAACaqbJVAAAAQklEQVQ4EWM8c+bMfwYqABaQGcbGxhQZdfbsWQYmikxA0jxqEFJg4GCOhhGOgEESHg0jpMDAwRx8YQQuj0DlCaUAAEdBCPJ7TaEPAAAAAElFTkSuQmCC")}.tui-editor-contents .task-list-item.checked:before{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEqADAAQAAAABAAAAEgAAAACaqbJVAAAA1ElEQVQ4EWP0nvbsPwMVABMVzAAbMWoQIiT5OJgYvLS5EAJQFguGCB4BkCHt/kIM8kKsYFXbrn6DqyY6sJENefjuN8ORuz/ghoAYWA0COR2kEQbQDanc+I7h049/MGkwjVANFQYZkmXHD/YCyABiDAFpxQgjkJO9dbjA4QAKDxAAhQnIO9hcAlYAJDBcBHIySANII8gAYgwBGYZhEEgQZFjVJohhhFwCUg8CjPgyLT8nE8N/YJZGD1iIVlQSI4yQpT9+R40ZZDl0NlavoSsihj/4DAIAR+hZHUj727YAAAAASUVORK5CYII=")}.tui-editor-contents .task-list-item .task-list-item-checkbox,.tui-editor-contents .task-list-item input[type=checkbox]{margin-left:-17px;margin-right:3.8px;margin-top:3px}.tui-editor-contents-placeholder:before{content:attr(data-placeholder);color:grey;line-height:160%;position:absolute}.te-preview .tui-editor-contents h1{min-height:28px}.te-preview .tui-editor-contents h2{min-height:23px}.te-preview .tui-editor-contents blockquote{min-height:20px}.te-preview .tui-editor-contents li{min-height:22px}@media (-ms-high-contrast:active),(-ms-high-contrast:none){.te-ww-container .tui-editor-contents li{vertical-align:middle}.te-ww-container .tui-editor-contents .task-list-item:before,.te-ww-container .tui-editor-contents ol>li:before,.te-ww-container .tui-editor-contents ul>li:before{position:static;vertical-align:middle}.te-ww-container .tui-editor-contents ul>li:before{margin-top:-3px;margin-right:12px}.te-ww-container .tui-editor-contents ol>li:before{margin-right:6px}.te-ww-container .tui-editor-contents .task-list-item{padding-left:2px}} -------------------------------------------------------------------------------- /examples/htdocs/assets/toastui/toastui-editor.min.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /*! 3 | * @toast-ui/editor 4 | * @version 2.5.1 | Tue Nov 24 2020 5 | * @author NHN FE Development Lab 6 | * @license MIT 7 | */.auto-height,.auto-height .tui-editor-defaultUI{height:auto}.auto-height .tui-editor{position:relative}:not(.auto-height)>.tui-editor-defaultUI,:not(.auto-height)>.tui-editor-defaultUI>.te-editor-section{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}:not(.auto-height)>.tui-editor-defaultUI>.te-editor-section{-ms-flex:1;flex:1}.tui-editor-defaultUI-toolbar:after,.tui-editor:after{content:"";display:block;height:0;clear:both}.tui-editor{position:absolute;line-height:1;color:#222;width:100%;height:inherit}.te-editor-section{min-height:0;position:relative;height:inherit}.te-md-container{display:none;overflow:hidden;height:100%}.te-md-container .te-editor{line-height:1.5}.te-md-container .te-editor,.te-md-container .te-preview{box-sizing:border-box;padding:0;height:inherit}.te-md-container .CodeMirror{font-size:13px;height:inherit}.te-md-container .te-preview{overflow:auto;padding:0 25px;height:100%}.te-md-container .te-preview>p:first-child{margin-top:0!important}.te-md-container .te-preview .tui-editor-contents{padding-top:8px}.tui-editor .te-preview-style-tab>.te-editor,.tui-editor .te-preview-style-tab>.te-preview{float:left;width:100%;display:none}.tui-editor .te-preview-style-tab>.te-tab-active{display:block}.tui-editor .te-preview-style-vertical>.te-tab-section{display:none}.tui-editor .te-preview-style-tab>.te-tab-section{display:block}.tui-editor .te-preview-style-vertical .te-editor,.tui-editor .te-preview-style-vertical .te-preview{float:left;width:50%}.tui-editor .te-md-splitter{display:none;position:absolute;left:50%;top:0;height:100%;width:1px;border-left:1px solid #e5e5e5}.tui-editor .te-preview-style-vertical .te-md-splitter{display:block}.te-ww-container{display:none;overflow:hidden;z-index:10;height:inherit;background-color:#fff}.te-ww-container>.te-editor{overflow:auto;height:inherit}.te-ww-container .tui-editor-contents:focus{outline:none}.te-ww-container .tui-editor-contents{padding:0 25px}.te-ww-container .tui-editor-contents:first-child{box-sizing:border-box;margin:0;padding:16px 25px 0;height:inherit}.te-ww-container .tui-editor-contents:last-child{margin-bottom:16px}.te-md-mode .te-md-container,.te-ww-mode .te-ww-container{display:block;z-index:100}.tui-editor-defaultUI.te-hide,.tui-editor.te-hide{display:none}.tui-editor-defaultUI .CodeMirror-lines{padding-top:18px;padding-bottom:18px}.tui-editor-defaultUI pre.CodeMirror-line{padding-left:25px;padding-right:25px}.tui-editor-defaultUI .CodeMirror pre.CodeMirror-placeholder{margin:0;padding-left:25px;color:grey}.tui-editor-defaultUI .CodeMirror-scroll{cursor:text}.tui-editor-contents td.te-cell-selected{background-color:#d8dfec}.tui-editor-contents td.te-cell-selected::selection{background-color:#d8dfec}.tui-editor-contents th.te-cell-selected{background-color:#908f8f}.tui-editor-contents th.te-cell-selected::selection{background-color:#908f8f}.tui-editor-defaultUI{position:relative;border:1px solid #e5e5e5;height:100%;font-family:Open Sans,Helvetica Neue,Helvetica,Arial,나눔바른고딕,Nanum Barun Gothic,맑은고딕,Malgun Gothic,sans-serif}.tui-editor-defaultUI button{color:#fff;padding:0 14px 0 15px;height:28px;font-size:12px;border:none;cursor:pointer;outline:none}.tui-editor-defaultUI button.te-ok-button{background-color:#4b96e6}.tui-editor-defaultUI button.te-close-button{background-color:#777}.tui-editor-defaultUI-toolbar{padding:0 25px;height:31px;background-color:#fff;border:0;overflow:hidden}.tui-toolbar-divider{float:left;display:inline-block;width:1px;height:14px;background-color:#ddd;margin:9px 6px}.tui-toolbar-button-group{height:28px;border-right:1px solid #d9d9d9;float:left}.te-toolbar-section{height:32px;box-sizing:border-box;border-bottom:1px solid #e5e5e5}.tui-editor-defaultUI-toolbar button{float:left;box-sizing:border-box;outline:none;cursor:pointer;background-color:#fff;width:22px;height:22px;padding:3px;border-radius:0;margin:5px 3px;border:1px solid #fff}.tui-editor-defaultUI-toolbar button.active,.tui-editor-defaultUI-toolbar button:active,.tui-editor-defaultUI-toolbar button:hover{border:1px solid #aaa;background-color:#fff}.tui-editor-defaultUI-toolbar button:first-child{margin-left:0}.tui-editor-defaultUI-toolbar button:last-child{margin-right:0}.tui-editor-defaultUI-toolbar button.tui-scrollsync{width:auto;color:#777;border:0}.tui-editor-defaultUI button.tui-scrollsync:after{content:"Scroll off"}.tui-editor-defaultUI button.tui-scrollsync.active{color:#4b96e6;font-weight:700}.tui-editor-defaultUI button.tui-scrollsync.active:after{content:"Scroll on"}.tui-editor-defaultUI .te-mode-switch-section{background-color:#f9f9f9;border-top:1px solid #e5e5e5;height:20px;font-size:12px}.tui-editor-defaultUI .te-mode-switch{float:right;height:100%}.tui-editor-defaultUI .te-switch-button{width:92px;height:inherit;background:#e5e5e5;outline:0;color:#a0aabf;cursor:pointer;border:0;border-left:1px solid #ddd;border-right:1px solid #ddd}.tui-editor-defaultUI .te-switch-button.active{background-color:#fff;color:#000}.tui-editor-defaultUI .te-markdown-tab-section{float:left;height:31px;background:#fff}.te-markdown-tab-section .te-tab{margin:0 -7px 0 24px;background:#fff}.tui-editor-defaultUI .te-tab button{box-sizing:border-box;line-height:100%;position:relative;cursor:pointer;z-index:1;font-size:13px;background-color:#f9f9f9;border:1px solid #e5e5e5;border-top:0;padding:0 9px;color:#777;border-radius:0;outline:0}.te-markdown-tab-section .te-tab button:last-child{margin-left:-1px}.te-markdown-tab-section .te-tab button.te-tab-active,.te-markdown-tab-section .te-tab button:hover.te-tab-active{background-color:#fff;color:#333;border-bottom:1px solid #fff;z-index:2}.te-markdown-tab-section .te-tab button:hover{background-color:#fff;color:#333}.tui-popup-modal-background{background-color:hsla(0,0%,79.2%,.6);position:fixed;margin:0;left:0;top:0;width:100%;height:100%;z-index:9999}.tui-popup-modal-background.fit-window .tui-popup-wrapper,.tui-popup-wrapper.fit-window{width:100%;height:100%}.tui-popup-wrapper{width:500px;margin-right:auto;border:1px solid #cacaca;background:#fff;z-index:9999}.tui-popup-modal-background .tui-popup-wrapper{position:absolute;margin:auto;top:0;right:0;bottom:0;left:0}.tui-popup-header{padding:10px;height:auto;line-height:normal;position:relative;border-bottom:1px solid #cacaca}.tui-popup-header .tui-popup-header-buttons{float:right}.tui-popup-header .tui-popup-header-buttons button{padding:0;background-color:transparent;background-size:cover;float:left}.tui-popup-header .tui-popup-close-button{margin:3px;width:13px;height:13px;background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTUgMy41ODZMMS43MDcuMjkzLjI5MyAxLjcwNyAzLjU4NiA1IC4yOTMgOC4yOTNsMS40MTQgMS40MTRMNSA2LjQxNGwzLjI5MyAzLjI5MyAxLjQxNC0xLjQxNEw2LjQxNCA1bDMuMjkzLTMuMjkzTDguMjkzLjI5MyA1IDMuNTg2eiIgZmlsbC1ydWxlPSJub256ZXJvIiBmaWxsPSIjNzc3Ii8+PC9zdmc+)}.tui-popup-header .tui-popup-title{font-size:13px;font-weight:700;color:#333;vertical-align:bottom}.tui-popup-body{padding:15px;font-size:12px}.tui-editor-popup{position:absolute;top:30px;left:50%;margin-left:-250px}.tui-editor-popup.tui-popup-modal-background{position:fixed;top:0;left:0;margin:0}.tui-editor-popup .tui-popup-body label{font-weight:700;color:#666;display:block;margin:10px 0 5px}.tui-editor-popup .tui-popup-body .te-button-section{margin-top:15px}.tui-editor-popup .tui-popup-body input[type=file],.tui-editor-popup .tui-popup-body input[type=text]{padding:4px 10px;border:1px solid #bfbfbf;box-sizing:border-box;width:100%}.tui-editor-popup .tui-popup-body input[type=text].disabled{border-color:#e5e5e5;background-color:#eee;color:#e5e5e5}.tui-editor-popup .tui-popup-body input.wrong{border-color:red}.te-popup-add-link .tui-popup-wrapper{height:219px}.te-popup-add-image .tui-popup-wrapper{height:243px}.te-popup-add-image .te-tab{display:block;background:none;border-bottom:1px solid #ebebeb;margin-bottom:8px}.te-popup-add-image .te-file-type,.te-popup-add-image .te-url-type{display:none}.te-popup-add-image div.te-tab-active,.te-popup-add-image form.te-tab-active{display:block}.te-popup-add-image .te-tab button{border:1px solid #ccc;background:#eee;min-width:100px;margin-left:-1px;border-bottom:0;border-radius:3px 3px 0 0}.te-popup-add-image .te-tab button.te-tab-active{background:#fff}.te-popup-add-table .te-table-selection{position:relative}.te-popup-add-table .te-table-body{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAARCAYAAAAougcOAAAC7mlDQ1BJQ0MgUHJvZmlsZQAAeAGFVM9rE0EU/jZuqdAiCFprDrJ4kCJJWatoRdQ2/RFiawzbH7ZFkGQzSdZuNuvuJrWliOTi0SreRe2hB/+AHnrwZC9KhVpFKN6rKGKhFy3xzW5MtqXqwM5+8943731vdt8ADXLSNPWABOQNx1KiEWlsfEJq/IgAjqIJQTQlVdvsTiQGQYNz+Xvn2HoPgVtWw3v7d7J3rZrStpoHhP1A4Eea2Sqw7xdxClkSAog836Epx3QI3+PY8uyPOU55eMG1Dys9xFkifEA1Lc5/TbhTzSXTQINIOJT1cVI+nNeLlNcdB2luZsbIEL1PkKa7zO6rYqGcTvYOkL2d9H5Os94+wiHCCxmtP0a4jZ71jNU/4mHhpObEhj0cGDX0+GAVtxqp+DXCFF8QTSeiVHHZLg3xmK79VvJKgnCQOMpkYYBzWkhP10xu+LqHBX0m1xOv4ndWUeF5jxNn3tTd70XaAq8wDh0MGgyaDUhQEEUEYZiwUECGPBoxNLJyPyOrBhuTezJ1JGq7dGJEsUF7Ntw9t1Gk3Tz+KCJxlEO1CJL8Qf4qr8lP5Xn5y1yw2Fb3lK2bmrry4DvF5Zm5Gh7X08jjc01efJXUdpNXR5aseXq8muwaP+xXlzHmgjWPxHOw+/EtX5XMlymMFMXjVfPqS4R1WjE3359sfzs94i7PLrXWc62JizdWm5dn/WpI++6qvJPmVflPXvXx/GfNxGPiKTEmdornIYmXxS7xkthLqwviYG3HCJ2VhinSbZH6JNVgYJq89S9dP1t4vUZ/DPVRlBnM0lSJ93/CKmQ0nbkOb/qP28f8F+T3iuefKAIvbODImbptU3HvEKFlpW5zrgIXv9F98LZua6N+OPwEWDyrFq1SNZ8gvAEcdod6HugpmNOWls05Uocsn5O66cpiUsxQ20NSUtcl12VLFrOZVWLpdtiZ0x1uHKE5QvfEp0plk/qv8RGw/bBS+fmsUtl+ThrWgZf6b8C8/UXAeIuJAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAOklEQVQ4EWO8e/fuPwYGBkYgphlgAZmspKREMwtABjPR1HSo4aOWkBTKo8E1GlwkhQBJikdT1wgNLgAMSwQgckFvTgAAAABJRU5ErkJggg==")}.te-popup-add-table .te-table-header{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAARCAYAAAAougcOAAAC7mlDQ1BJQ0MgUHJvZmlsZQAAeAGFVM9rE0EU/jZuqdAiCFprDrJ4kCJJWatoRdQ2/RFiawzbH7ZFkGQzSdZuNuvuJrWliOTi0SreRe2hB/+AHnrwZC9KhVpFKN6rKGKhFy3xzW5MtqXqwM5+8943731vdt8ADXLSNPWABOQNx1KiEWlsfEJq/IgAjqIJQTQlVdvsTiQGQYNz+Xvn2HoPgVtWw3v7d7J3rZrStpoHhP1A4Eea2Sqw7xdxClkSAog836Epx3QI3+PY8uyPOU55eMG1Dys9xFkifEA1Lc5/TbhTzSXTQINIOJT1cVI+nNeLlNcdB2luZsbIEL1PkKa7zO6rYqGcTvYOkL2d9H5Os94+wiHCCxmtP0a4jZ71jNU/4mHhpObEhj0cGDX0+GAVtxqp+DXCFF8QTSeiVHHZLg3xmK79VvJKgnCQOMpkYYBzWkhP10xu+LqHBX0m1xOv4ndWUeF5jxNn3tTd70XaAq8wDh0MGgyaDUhQEEUEYZiwUECGPBoxNLJyPyOrBhuTezJ1JGq7dGJEsUF7Ntw9t1Gk3Tz+KCJxlEO1CJL8Qf4qr8lP5Xn5y1yw2Fb3lK2bmrry4DvF5Zm5Gh7X08jjc01efJXUdpNXR5aseXq8muwaP+xXlzHmgjWPxHOw+/EtX5XMlymMFMXjVfPqS4R1WjE3359sfzs94i7PLrXWc62JizdWm5dn/WpI++6qvJPmVflPXvXx/GfNxGPiKTEmdornIYmXxS7xkthLqwviYG3HCJ2VhinSbZH6JNVgYJq89S9dP1t4vUZ/DPVRlBnM0lSJ93/CKmQ0nbkOb/qP28f8F+T3iuefKAIvbODImbptU3HvEKFlpW5zrgIXv9F98LZua6N+OPwEWDyrFq1SNZ8gvAEcdod6HugpmNOWls05Uocsn5O66cpiUsxQ20NSUtcl12VLFrOZVWLpdtiZ0x1uHKE5QvfEp0plk/qv8RGw/bBS+fmsUtl+ThrWgZf6b8C8/UXAeIuJAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAOklEQVQ4EWO8e/fuPwYGBkYgphlgAZksLCxMMwtABjPR1HSo4aOWkBTKo8E1GlwkhQBJikdT1wgNLgDxKwPzTeWPdAAAAABJRU5ErkJggg==")}.te-popup-add-table .te-selection-area{position:absolute;top:0;left:0;background:#80d2ff;opacity:.3;z-index:999}.te-popup-add-table .te-description{margin:10px 0 0;text-align:center}.te-popup-table-utils{width:auto;min-width:120px}.te-popup-table-utils .tui-popup-body{padding:0}.te-popup-table-utils button{display:block;width:100%;background-color:#fff;border:none;outline:0;padding:0 10px;font-size:12px;line-height:28px;text-align:left;color:#777}.te-popup-table-utils button:hover{background-color:#f4f4f4}.te-popup-table-utils hr{margin:0;background-color:#cacaca;border-style:none;height:1px}.te-popup-table-utils .te-context-menu-disabled{color:#ccc}.te-popup-table-utils .te-context-menu-disabled:hover{background-color:#fff}.te-heading-add{width:auto}.te-heading-add .tui-popup-body{padding:0}.te-heading-add h1,.te-heading-add h2,.te-heading-add h3,.te-heading-add h4,.te-heading-add h5,.te-heading-add h6,.te-heading-add p,.te-heading-add ul{padding:0;margin:0}.te-heading-add ul{list-style:none}.te-heading-add ul li{padding:2px 10px;cursor:pointer}.te-heading-add ul li:hover{background-color:#eee}.te-heading-add h1{font-size:24px}.te-heading-add h2{font-size:22px}.te-heading-add h3{font-size:20px}.te-heading-add h4{font-size:18px}.te-heading-add h5{font-size:16px}.te-heading-add h6{font-size:14px}.te-dropdown-toolbar{position:absolute;width:auto}.te-dropdown-toolbar .tui-popup-body,.tui-popup-color{padding:0}.tui-popup-color .tui-colorpicker-container,.tui-popup-color .tui-colorpicker-palette-container{width:144px}.tui-popup-color .tui-colorpicker-container ul{width:144px;margin-bottom:8px}.tui-popup-color .tui-colorpicker-container li{padding:0 1px 1px 0}.tui-popup-color .tui-colorpicker-container li .tui-colorpicker-palette-button{border:0;width:17px;height:17px}.tui-popup-color .tui-popup-body{padding:10px}.tui-popup-color .tui-colorpicker-container .tui-colorpicker-palette-toggle-slider{display:none}.tui-popup-color .te-apply-button,.tui-popup-color .tui-colorpicker-palette-hex{float:right}.tui-popup-color .te-apply-button{height:21px;width:35px;background:#fff;border:1px solid #efefef;position:absolute;bottom:135px;right:10px;color:#000}.tui-popup-color .tui-colorpicker-container .tui-colorpicker-palette-hex{border:1px solid #e1e1e1;padding:3px 14px;margin-left:-1px}.tui-popup-color .tui-colorpicker-container div.tui-colorpicker-clearfix{display:inline-block}.tui-popup-color .tui-colorpicker-container .tui-colorpicker-palette-preview{width:19px;height:19px}.tui-popup-color .tui-colorpicker-slider-container .tui-colorpicker-slider-right{width:22px}.tui-popup-color .tui-colorpicker-slider-container .tui-colorpicker-huebar-handle{display:none}.tui-tooltip{z-index:999;opacity:.8;color:#fff;padding:2px 5px;font-size:10px}.tui-tooltip,.tui-tooltip .arrow{position:absolute;background-color:#222}.tui-tooltip .arrow{content:"";display:inline-block;width:10px;height:10px;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg);top:-3px;left:6px;z-index:-1}.tui-toolbar-icons{background:url(data:image/png;base64,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);background-size:218px 188px;display:inline-block}@media only screen and (-o-min-device-pixel-ratio:2/1),only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min--moz-device-pixel-ratio:2),only screen and (min-device-pixel-ratio:2),only screen and (min-resolution:2dppx),only screen and (min-resolution:192dpi){.tui-toolbar-icons{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAbQAAAF4CAYAAAA8HgyJAAAAAXNSR0IArs4c6QAAQABJREFUeAHtvQmYHUd5733ObJrRPtJotSxZsrVZxnjBmO2C7GsINtzk8oE0Wix5LBz5i0GExSwmD/FAIJgAJrFiEQYvY0mWNBLgwH0SQpzYgssSf8TgTdZiW7IWa0brjKSRZ5/z/d+jrlZ1T3ef7tPLOTP69/P0VHUtb7396z719ltV3ZNKcSMBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEhjcBNJRq79o0aJMQJn70+n0TyZNmnTP2rVruwLWDVwc+l0bsFJ3aWlp27x5896or6/vD1g3cHE7v23btlmukT0/aAN2eUHr2/lB3rO6DHu+nucnbpfnp45XmbC8vGRLXtQ8c7UXNJ88gxLzLh81T+/WUincvxUosxbt3pmrbCHy0SeW7Nmz5+JNmzbtL0T79jZL7AkFOJ6RyWQ+09LS8h8CpwDt52qyoq+vb+KOHTtmQ0+LcclVkfkkQAIkkC+BW2+9dQrqbse+Ol8ZcdZbvXp1+e7du+f09PTUxNlOENnFZEDe8/LLL388iPIJlx25ePHiorlwCZ87myMBEkiQADyzd3Z1dcnoxzsTbNZ3U9Bv5JkzZ+b39vaO8F0pgYLFZNBS8ICWJXDOYZoYF6Yy65IACZBALgIwFuKRbccuHlp2K6bRIeg3oaSkZA6MWXmx6Rf5EBpO1nEOTeYaxKpjPmoBhvC+BRDvUzC08BjKTdSOI49CB8c5NLT7LPJKK7F1dnZOQ8Mj7Y3jIvY2NTU9b0/nMQlcKATsv2/5XYc59xjkWX7f8rsOqV+k8rx0AYsK5K/F7jbE2IX1Bs3Ifxn7L9GX/mzz5s27EE9kU/NlbkOM6B8z/f39PeXl5Z0oc2bEiBFtjY2NnYkoZzSSqIeGm6t9y5Ytz1RUVNzqdJK4WMOd0pNKg359GzZsOIv29jm1Cf0S5eWkA9NIgASGHgGf82XD4Kldgv0W7N+Ch7QT0yBPLlmy5G1xE/EzXwZjJg83FTBmoxFedPbs2QW1tbVzVqxYkdiwZFncIALKfzVg+USLw7OMfRVmoifExkggIgLouG5Gh9Yg4vCkvhojGT+PSHQkYuD9jCkrK5shwmAI9uPh9VQkgiMQAt1kvuzHEGUOMfoVC8N2E/qlG2DY/mbr1q1f9VsvSDnoJ/Nls/QhRr/1cU+MwojXPBjdZjgzh/3Wy7dcoh6HgMGJXd/d3b3RReENLumJJEO/UuNpYqZTg/hBnHBKZxoJXOgEDGMmQ/XTlGErJiZizKRDll0ZtmLQD32ODC9uxx7YmGn6l8Kw1cOofUNLiyQK/QbMl+UjGEZ3CmRdlE/dIHUS89BwMtm5NZyYo34YztuIi/L3jpkJJEK/7Fg5nibcWjuJdy2OYszaLT+SdMVJCcOTpGWOwp6vyvkN7fL81lPlFCd1DHmWOQp7virnN7TL81vPrVyhebnppdKLjZfSK8oQHW095O2DB/FYlHKjkoWH7KmYBulav379oH5gRf/5ZbD+DTj/a1RsIpYzGfd7O37jsXnHiXpoOeBcDqO2ChfF0oHnqJNYNoZRKnGz1BSrfomBYEMk4EBAhhmRfEh2I54tJcYMv5l7sa9DZ3a5Q9VEkmSYEZ5Zj+wSV42KMRPvAaNG06FflUpPKkTnLsO0C7HLYo8otu9h8UZkjgr0OwaPe49wi0I53BsXx9mHFo1Bw0leg70BP4CtUV6QKC6CyMBFlQUr0/EDmBXnBYlKX8ohgSQJyJwZOr+LZVfzZ8qYGXrI72cbFhcUZOEX9DqF0ZUXZJe46KSMmcRh1ErQac8qxMcdoM/vhg0bJiNEvxNdwmzom+bgfd4PhJFhrwv92keNGrUTfGTBXKgN/egw3BeyaCSWrWgMmnZ2H9u5c2eddlxUUVyQscuWLRtfVEpRGRIoMgI2Y6a0u7y1tfVBdVDIUDdmSg94bpW7du2aro6TDDdu3Cge2kLs2YU1Dm23IW0XRrH+gHA3wtMOZVRSrYpEFTY0NPTMnTt3D5bkH3eR2QeD1wkP7E3sXXilwOszgbG9zxuZa+pykmYyxnVLcBEycOtzvYsm3tCfo+JDZuUEItDvD4Z+nu+iiSrQbwICtwsbSls8DXkOuebKD9W4j8po3zJnZq+SK99ePu7jQvPKdX7FxiuXvn7yXYyZqlqHPuCXOO9GlZB06GTMlA7w1MYvXbq0HZ5cLL9v1Y5TCCbdSL8TfOQ3Ju+jVahyyKtWcRWi3BjEb8T+KewLsWc3eGnvUfEoQ3ivYqT2o10xWhejHzT7Kuj3nL0tlCuFARyNZfzybrH+Xq8et1cLdZyYhybGQjTFiXu+iyZlUHa2hElumn6e76KJTriQw5LUjW2RwGAhkMOYqdN4EJ1dQebTvIyZUg4G4WLol/h8mmoffaSveTWUO4X9Cew3wMCIUcv2sQhnLl++fLSSF3WI9nzNq6FcHxbStSLcje9VHNT0qBBjpx1HFk3MoNk1xpOQad3tebihzE+q2POK4RjutKvuxaAfdSCBQhDwacxEtYLMp/kxZqJcIefTpH3ZYAQCzath3lI8unqpiy2N4dNYl8hDv0DzavhgxVH0m/rCF9P7PKdyNH8TNWgwVGl5csBLmO+Al7PR4xT2eeTFliX6yZMDdnGJZ7o1hBueL1i7wWH6BUkggDFTfC5va2tbpw7iDv0aM6VHIefTlA4+5tVUURV+AyNNO+UA/Wvs8/w+5tWUXtkQw7jNMs9mJMbioSU2hwYjkcFNbzlBtwNclJ+65cWVDv2u9asfdIjtPQrhpJ8jnoQs3qA9Xy/rJ26X56eOXkY46ceQZ5lTs+frZf3E7fL81PEqE5aXl2zJi5pnrvaC5kfN096+4ouHQXtWzmPUuS1noZAF1P2Ih9DAklAndqOQSylcv+y8Wq5yko+yfTjfv0L0J/CGEunb1byaH/1kWgcL6g6j7CysmrT0a37q+ymTqIfmRyGU2YclrPf5LFuIYnKDtRSiYbZJAiRAAl4EYNSegOF4Cca416tcofJkTk28NHxKK/gTkA+li82gncCTxa3GB4J9qJ94EXnM2ydPQom3zAZJgARIwB+BRiwSkWX+xbrJCtJY+tCiMGh4opAVMI3Yr8AHLH+LsKg2PFH0wNCeqK6u3gFj1l5UylEZEiABEtAIoL96Av3VG1pSUUVramraFixYEMmXR4rqxKgMCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACQwhAukhdC5D4lQWLVqU0U9k27ZtRXWNoN+1Nv2e1Y8ZJwESIIFCESgpVMNslwRIgARIgARI4AIgIJ6a3VsrptMWT83urRWTftSFBEjgwiNAD+3Cu+Y8YxIgARIYkgTKkjirxYsX35TJZO5AW/Oxz8Z+Jp1O70LaTxF/GPNEpxAWbFu+fPlo6FLT09NTWVJSUglF+vr7+zvLysraent7j0O/voIp59JwWO8t7rm5sN4b9Itkbi4sJxf8ZnJYjmE5mYq4RKLi6CKeyQUigPvmB2h6Da5vd4FU8Gx22bJlM+bMmXOwvr6+37NgxJmxemirVq0aBWP2LzAWT0LvWuxXYq/CPhFp70X4Xeyv4eKsQJj4hnZLAX52d3f3bBizaihQBUOWxi6GfiSM2TSEb1m5cuX4xJVjgyRAAiTgTmA1srbfeuutU9yLFC4H/WnN7t2756xevbo8SS1i9dDOnDnzGE7mFu2E/oj469hHY3879lHYxVish+Hbu3Xr1t8gntgGb+wSgBdd1NaBtC4clMKDHNHX1ycGv7Sjo+MSGL8uPA21q4JxhWjHsspRHYf1BKLSF/pYVjmqY3oCUREubjm1tbVX4IFPRlvej3069pFOGtvvV3UfO5SV39QB7E/it/dQU1PTSw5lfCehnarS0tIa/H5l1KXC+A0PqG+/X9V9bC8IWf2Q1Q1ZpyFLRms67GUKePzOrq6uZ6H7R6HX7wqoh2PTcAhGwAbMh357oV/sfacoEZtBw0l8DPI/Io3ghjiNm/XmLVu2/FaOZUN+BdI/hxvlXhw+lrQxg2dWDWM2VnSRmxY36ys6dLjKJa+++uoklJmC/BObNm1K5IKIPtxIoNgIGL/X7+H3+v9CtyhHdsQgXi47ZK/Bg+0/IfwMfouBhtLk97pjxw4ZUZmA3zKCaDbDIMo0hOwTweHYggULDiU9lOZxNuKhbYdeMvzY4FGuIFkwauXo++dAv4PQ71jcSsT2jhNuzIdxY66SE4Dh+iYM1pedTkaG/EaOHPl6Q0NDj1N+XGlLliy5BDerGkpsAew3nNqqq6urfPTRR7twDhbPyaks00hgKBIQY4bz+jn2G/2eH35Plr4FMoL8fp5COzdDhi+jJsZs586dl8FzlBEfXxtkW+ZooZ9l5MFLCDroM/Pnz381CaMGvcah7/nf0Oed6E/fgnAujsX7dHqoEIOW6LwaGJRhaHEsdBoBnapgwMTwl2IfsJWXlx+Pe14tNg8NZ6MPRbh6N/B8Xhlw5gkk4KYsUU9yw4YNc524bGxs7MSegEZsggSKkwA6q/vRWfk2ZhGcxY3SJuR80o8swzPzbcz8yPQqI4bTaPOAV7kweTI3hrn9vwb3VdjlgcLccGzGbRGZV3sL6n5048aNzba8SA9lbqy9vX0qHiTGg4fl4cWtIWNerQp1X4vLgfGliJuCXunw0L4C8F8zyrxaVVX1jvXr15/wqpNkntwwGH+eKm3CtnXhiWsXnjZ6k9TBqS37kyx+2F+Fd1vvVLYQafYnWQzHNmMo+XAhdGGb8ROQOTP8jp/HrnsEh/Cb+TyeuH8ZtuOU3yE6uvehU/w2zkaGDLMb7nuZu3prrjk13I+yyEyGLM0Nq5N74Ckcqq6uPhO245SOu7W1dRRkTpPhM7ORc5GX4elFPqcG5jeD93rsNbb2/B6KMYttXg3Mx+D6X4JrlpdDZFyfWObV9JvULyxf5dDR/QQ3pBpGvAwLK54HiI/LEJ4vATEXgjFrw0XJPurgwgyD23z50qVLa2T4IuamA4nHTX0vHg7qA1VKsDC83CkYvs0+GCTYLJtKiAB+G3fYjRmaFkOzJawxk1MQGSJLZGI/JGmySZvS9rkj97/oZyydvnSWc+fOFUNzMqwxk1ZFhsgSmSJb18Tetp6Xbxy/9T/Bef9zCGMmTat5NfHYIt0MY3YpdMzLmIky8mCAvlfm1SZEqhyExeahiaJQ+EsIvilxtcHIHUf8hzih7+PJ/qBKL0QI/Saj3Yv0tqFXLy6W6HgMN7KvMXy9fpRxMWRi0EQmdLFcK+juOu7gRwe7PD917GXEkIlBk3TIy3tOwi7XSZ5TmXzSioGbl97Qz/dcjpMc+3VwKhMkDfrsQHnTA8LvdxlGDDbbZQTl6nT/4X5fivt9kyZbDNMC7XhAFA+hC9BB6g/J+1DnpL1gUK5OHCFjHOTOVLJh4Do3b94sfCLZ5IEa5yJTMNnFaiGFSt8V6UIRmS/DUOsVkOs4RxZEX3Em0M9GvlAkVm8EN8V9+AF8HCdqvjgtTx7Y70FHuA838Aa415cGARFlWejXgptyP2Say6KMJw8xdG/BDTxzzZo1w6Js068s3Zj5rZNkOd2YJdku20qcgCzNN7eKiort5kHEEQfZlradmkNfUqGnyzCjfhxl3C7b3nbYttAnfh0yojBmMuS4EP1bQ1id9Pp79uyRh//Qxkw8XfSze6Bf5Kse83Yb1YnmejLDRVdF7WEp8m6VHTLseeYxTtrimZgZPiOQ7fnEiyciL0njWlpaxuXQz+KZeAnzm2czZr/E0Ea937pJlLMZs/ZRo0YdTqJdtkECQ5WAfIQCiyxui+D8fodFbpEvCkEfWAqDOz6sfjBmZ9FfxLYoJLRBC3uCrG8lYDNmMpS30FqisEc2Yyb67S6sRmw9ZgIHIN8ccsTKu4U4HjDkGIUOhmxdlLTtuWEESIbWzCFHWcCB4wFDjp5CfGYass3SRtvmcZgIjNmf4eHePA+bLDlHGUmSOTzxkGTo02n+STyyNZiXlPKRbljUNxbrIBydCxk+RGPdxjCilCk1RrosOiSxbN9RQYsWMR1gqHERLuAPsMsnp2R7Ep3jB85FC//XePF6BjTJuti4WGcweb0nbs3wJOTo0ob1VKPS283jhX6Re6pR6Uw5+RPAA5a8TP1pTcIhxN+K6x2p0cB9JZ3089j1lY5/j/m6z2htD4jiAetieA4TVYYMZ8kCjqhXLMv8kSwckwUNqi2MnByNah0AOMsL5Xcq2TCWMurRgHAjVmDvQ/uWV4ts/YQYsEjny5QeKkR/OEOW3atj4Qx9j0+YMOHEAw880A09Lf2W3k/ENV+mdNHDgnloMA7bcBHlIv3IUEg+hVU0G96Pa8VFTOEizhKl8MQxvGiUoyIkkBABdNoPwWB8Cp2Xmm8Xg/M8frtfkDmvsCsdjfetFkL+30Gubsz6pe1cpwndZAGXadDE4IjhQYca6bJ9yJymGzPRy2g7l4q+8nH+V6mCMA7bhg8fvlLegVVpHqHMl8W2RF+1C/3k9YjsBk+rFS9Iv66M7Nq1a1XWgFAMH7jFskR/QGNIKJiHJsoYXpD+pDcKT36uL2E7nUCcafJUhlU9spxYbc9BP3MBiUpkSAJDmQCM1z+iQ/tEkHPE78TSt9g8ipyi0Kk/CO/skzkLogBky+IRpyE41+rQzzKioHsUrpWsGccgI+eQqLWK+5EYYOTKootf4tNaN6Hv6XUvnT1n8YhimS9zahcrMK80DHo7rsseu0dmryM8454vs7cpx+qpyykvVJqcEH4IG/BioqtnA+9nntZIG26QxIzZihUrRkDHmbhxXBnAmOlj2n3Qj8ZMu2CMXhgEYMw+izN9KsGzfcpo01eT8m1FmRLwVTiCQtKWtBmBKFMEDIQsuOiF9/PnuYyZUUnmyxaG9ZBNBXJE1JzYiBEj9ucyZiJK5ssw9LsnincBc6hmyY5lyBH/X2wajNXPcFNOxUTqu2A4voxWn4BBkLHe7IY5tDmA9EN1jHCrFo81Cn0q8LQhrwuUw2iNxPEbuEHbcCOZ49TyAjhevp6BclldxM2OVSlDOHSxjEXj5nH8Uoi9XFDdcC0sT9B+66Pda/WyGBZy/FKIvZxex08c+lmeoP3UcSoTlpOTTD0tX45KRlhOSo5bGAVHyOiGnjfjXrwf7fwFfteuD4FuevhJh3z5/X1fjJm06aeOlJHfLfZX8VuWIctAnprfNrRyxzCnFfnHiXHO5Tj/H/r9FCD4mPNtmm6xRdFXp8VI+RwGTeE89semjIfgWAwaVitdjDaHGe3OQrgFexs8tj/iwrUhfjHCaxCqH8arGI//PI6T2sSgqbYr0OhM/Bj6YGTfRFy8sIqzZ8+aniWeyLpgoCN9IvN7ouAkXwpJwc2v91snyXKYR5AvhaT4+askqSfflmFgPonfiCxeuAMavB+7DPWNDKmNjMocwP4kOvS8/32M8TB6AIb3GB6ycv77GL86Q9aAfx8DFn6rBynXBYPxjSAVkiwrHNAHynxdUW+xGDRc8N9hfuztAPAAzv5DBoGx+CHcoGggrqLPwGDc/vjjj59WCXGH0K8dL0zvxDtmYnjHGO3JUlNZ8mvZZBwYxm8/6iQy3Ih2TM8Jhiz7pRAxalCq3qJYgQ6gn+k5qSX8YtSgjqzK4jbECWAx10s4RX3Vo68z1u9rXxXyLIR25NuKB4NW1+/roHWjKA9jvg19YEEemn3q3wpGvr1mnzIjLxaLQRMt4XLuRfBhdHrXw1DIEv3/ieOLceEqEX8F8d2I/1S+44bQtG5IT2TDypwuNPSqzKVBv2p4laNxXIEnkTQ6aHla6oRBbpPVjoXQTxmzRGDk0YgyZnlUZRUSIAEbAfQxD9qSiuoQ/WHkX/WI4wRNbyAO4ZSZPwG7QUvqCdevxnaDVugnXL96sxwJkMDQJaDmkYbuGQ7SM5M5Mzy1fbVY1Zc5M3izRT+mXqz8qBcJkED0BGIbcoxe1QtDor4qT80zFpNh01flYWg2e1Fo2C6Me5NnSQLFToAeWpFfITFmxbrCUdCJMeMKxyK/iageCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACQQgkA5QNlTRRYsWZbwEbNu2LTFdnPSAftc6pas06PesihdTmIurXdekOefi6qBfpJyD8rHrk+s4LM+gfHLpY8+P+r6Ngmc6nf7q1q1b6+26RnEcBc/S0tLmLVu2HI5Cn6Ay7Hxz3V9BywfVx17ezjfX/RW0vL29oMclQSuwPAmQAAnkS0A3ZrW1tR9Gh1ebr6w46unGrK6ubiz0GxdHO5QZD4HEDJo8aeBmmWk7jf2SnuspxFYnlkN50pg8efJLNuHdki67LZ2HJEACAQnYjVl/f/+PIeLxYjFqdmPW0dExC/rNpFELeKELWDwxgybn2NfXt1A/V9zg2/XjQsePHz8+StcBN/gZ/ZhxEiCB/Ai4GLMKSCvFXnCj5mTMYHDVNAiNWn6XPfFaZUm2iJt6YSZzfioN8e1Jtp+rLegzUi8Dfdv14yTiixcvbhw7duxdDQ0Nb/ppL2nvdsmSJZfMmzfvQH19fb9P/Qrq3SbNxw8Tvcxg9/6D8pVhRsMzE2OmthJslodJlRE2DMpXhhnFM9OMWVaFsrKyRB/+w573hVo/0YsEg7FQB42nou36caHj8CAtP6qamprEPTQwuq21tfX3GOa4vNA8nNoHo/G7d++eD/2qnPKZRgJuBFyMWQbGbHVTU9NDbvWSSvcwZvs3b958PCk92E7+BBIzaPJkDzVnaKrux0qi17XjgkbXrFkzDAroT43da9eu7SqQUmLMxKjVFah9z2Z7e3sr8TAyb+nSpTWeBZl5wRGQewKjDP/bfuLFYswwslAmhsuuH42ZncjgPE7MoHH+LPANMhw1HpUhyNWrV0u8qDZczxIYthnyoIJOIrH7qKggUBkLATFmuCf+E4k/xsPYCpVZTMZsx44dc86ePXvpypUrxyv9aMwUicEfJjaHxvmz/G4WYwjyOnQQizAf8LJdCpLPT0raMx2Og855OIiwJBlDkCOgx17I7rBk4gDpnu/32csHnfOw1w96HJSfXX7UPO3yg/Kz10+Kp2bMrsQ9m8LvvRG6pzCc2OowZ5b4MKN4ZmLMwCc7VI55sktg1ES/Ppc5s6IYZgx7f9rvh6iPw96fUeuT2JM1589CXToOQYbCx8pxE8CDzbUwYvNVO/i9l4hRKwZjJjrBmI2A8apU+kkoRq2YjZmuK+P+CCRi0Dh/5u9i5CqFDiKQN5ZLXtT55eXlRa1f1OdLeecJ4Msfv4ARq8U92qNSxaghrs9LO3pmGFavV3XiCuGpnsK87z4YNcs96rCacYBnhv5ralx6UW60BBIxaJw/C33RZKjxOnQaj4WWFIMALGnuxDXetX79+hMxiKfIQUIARuMJu1HTVHc1Zqhzr1YutuimTZtanYyaahD3saMxw709RZVhWNwEEplDw1Mb3z/L8z4Au8e83kuLew4nl9roIE7MnTvX9b20pOZwcunpll9ofm56qfRi56f0VCH0fQLzKrIg5HHs8tJ0doNn9En70nzxzJIyZkoPMWrQT16Ytny1qLKy8uCGDRssS/PFMyu0Mct1fxZ6ji3X/Zn0HFsiBg037UJ1Q0mITnC7flzoOG7agr9/5sDgTRizu4rVK8M17K+oqDhAr8zhyl3gSejkmtCRCYWsUYMx+wSM2TodSyGMmWof+p009MsaNcOYHVX5EhaDMdP1YdwfgdiHHDl/5u9C2EpxiNEGhIeDi4AYNWi83MmYyZkk7ZnZ6YlRQ9o+J2MmZQvtmdn15bE/ArF7aLgxFuqqwOvYrh8XOl5s32/MNcRYaF65hhgLrR/bLx4ChlErHoVsmhhGzZbKw8FMIHaDhg6a82cB7hAMMdYFKC7veVlWbeWqix+xzB/kvQX9ukvQMXTo92zeyjlUDMrHQYRnUlieQfl4KuOQGTVPexNx87W3F/Q4br5B9WH5eAnEPuTI+bN4LyClkwAJkAAJnCMQm4fm9uSGIch9yEuFfbINewHdntxaWlquMPSL1FMIqy/rkwAJkAAJeBOI3UPzbp65JEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJFC8BNJRq7Zo0aKMLnPbtm2ebQQtr8vOJ472rtXrQb9n9WN7PGh5e/24j+38crWX63rkqh80384vV/1c1yNXfXt+UD72+rmOw/IMyieXPvb8qHkq+dB7TDqdvgPHtZlMZrak4/gVBE04fgjtnpI0tfm4Ds2ov728vPwLjz/++CFVL98Q7ZWWlZXV9Pf3j8M+TOSUlJR0YT/Z29t7HPr16bJzXQfI6oF+7T09PYdQt1uvG1fc0Om/bfLf5nRNX5xytaXftdVJvaX5j579sL28n+MVK1aM6OzsnKeXrays3LVhw4azeprEd065xtLv2vPnN//Bsx+2l3c7LnHLYDoJkAAJOBFYvHjxDUh/CYbrO9ivQ3ys7BKXNMkzyiDqe5uCuku7u7ufX758+TTftRwKrlq1ahQM0AIYrmkwZsNRpFR2iUua5EkZh6quSahXDmNWjQKXw9BUuBaMMAMG9E67OKc0e5mkjvv6+mrsbTml2cvEeUyDFiddyiaBIUYAnfn7YXh+jtPyMjrTpMySJUv+Zx6nPw6G4+/yqJetAmM4+uzZs7PFALnJkDwpI2Xdynikl8KL9Dp3j6r+s8B5pBh4ew1Jkzx7etLH0EEeEMbZ25U0ybOnJ3VMg5YUabZDAoOcwK233joFHkITTiM7hOd1Oij3Mzytv+BVxiPvfR55rlmrV68uh4c3C51qzuG10tLSthEjRnS4CvPIgFFJwqAsgwrZdsDy97IbKkma5BV6G4frm7UfGMZ9U3ZRyEgbYOiSUrYsqYbYTjgC+LEOb2trW7d169Y6XRLG03P+ePXyccXr6+tLdu3aNX3Lli2v6204jffr+XHHi4WP23kWmo+bXk7pMBb3ozOXYTd9a0Rn9nUM4x1D/v9Ax3sP9n9oamrapheyx+3XBU/15hwQ2phq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Q4EADMZCdR5O82cqr1BhrvmzQunFdqMhEGr8368KGKb7NDyb7xnl/xHDjWu86sKIrUX+J6UMjMZnMPz3917lw+bJC9dYij/NkHMM+h3wkgn9piM/a2j5TpoXKeaRAAmQQHIEEvHQ9OFGPLW5Djeq09bL6HVVftSh/l4ZPlLsOtyo2tXL6HVVPkMSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESGKwE0oNV8aGq92uvvZaJ89wuvfTSUNd8//7918ap34wZM56NUz5lkwAJDF0CJUP31HhmJEACJEACFxIBGrQL6WrzXEmABEhgCBOgQRvCF5enRgIkQAIXEoGyOE520aJFEyD389jfg33Otm3barzaCVreS5afvNWrV5efPXt2Un9//8je3t5K6PecV72g5b1kRZF3+PDh1AMPPGAR9alPfSo1depUS1qhDg4cOOCo3/Tp0wulEtslARK4AAhEbtBqa2v/DIZiI9iN9MMvaHk/Mr3K1NXVjT19+vTMvr4+X95p0PJebUeV9+yzA9dNSFqxGLQ//OEPA05V0mjQBmBhAgmQQIQEIjVoixcvfi+M2VboVyE6ptPpdRUVFV930zdoeTc5ftNXrVo1Cp7ZLOioVvodq66ubnarH7S8m5wo02GIU889N9ChlLRbbrklVVpaGmVzgWX19PSkXnzxxQH1JO1DH/pQqry8fEAeE0iABLwJjFtx2HP188kNU1Wf5i0oZG4uPQaKT+8vSaV/MnbspHteXZvuGpgfbUpkBg3DhtKTPog9a8wQ/rapqemTMGqOFyJo+bCnnclk0suWLZuujFlZWdnZTZs2HXTTL2j5sPr5rb9r164UjHK2uPLIZAhS0iRvwYIFfkXFUk50OHXqVFa28shkCFLSJO8tb3lLLO1GJbS+vn6iyEJ4NCqZUcr5wQ9+kH0iuPPOO3uilEtZJBAPgcyM/lTmMydP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VSp27NgxTQnDMOHxMWPG7JIhQ5WmhximPIrjl5E/wDhDRzHI+6L8yr5qW7fqKo0hCZAACZBATATgVaXx+ag/QPxVWhP3whB9TR3Da6tAx38zvJp3I03iexD+BwyehPLVEFl08Uns02F85MXm38HA/Ezm4yQ/is3QU96Py3qQIhOGqhlf+zeNqXhtr7/+unx8eCSGJtNY/NGFMqcbGxuzhk7OA9tEyKrAufSgzFn8r7Y2mY+LQke7DBo0OxEekwAJkEDMBLCwYjbmop5BR69/eeNHMAir8RmpwC9Ew3B8ECr/GkaxPUrVjU9dzYNM8zNWMK5t8+fP3w+jZA59+m0TespnsdqhZ2QvU+ttm0rqiYyTAAmQAAnER+DFF188CU/lWXhWS9GK6ocvhxdzB9JHX3nllbteeuml7Ff43bQQD2r37t0LYVxkwUg9ZHVgWPBXbuXzSX/uued6r7766jfR1jhpT2QgrDx27NgE6FmKvevll1/2NE5S7+DBg6Og5wyc3xSsiOx/4YUXIjW86tzooSkSDEmABEggYQIYenwvOvxtaHaiQ9MvI+3XMFSHUOY4PCMZpqtCXIzLfKRfj/BirV4b3hObieE+c4Wilhcqavwjz1kwSOZ7bkqgzKNBl3bo143VjlmvDWkl0K0UexXSR8AbLVflEfbBEL6Uj4enyXCM0qA5YmEiCZAACSRDAP8j7WIYCvmnnjeFbHEf5q8+ijmuP4aU41hd5sNgnC6BrpbVio6FvRO78f7da1HO96nmaNAUCYYkQAIkUEAC8NZugFGThSHvCaIGvKPnUe8HMBIPyftsQermU1a8NXwJZCrqmv9ixqcc+Wr/Mby6cBw66++x+ayeuxgNWm5GLEECJEACiRFYvnz5NAzRfRBG6gPYZ6JhGY6cYChwCsbgOOIvYn8WHtm/RfmlDaMNX4F4bBhaHA19RmP14jAZjoQHp4YW+xDvRV4HzuEszuc0FoKY/4bGVwMsRAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAKDgMD/D2WyCk5hENCOAAAAAElFTkSuQmCC);background-size:218px 188px;display:inline-block}}.tui-toolbar-icons.tui-heading{background-position:-172px -48px}.tui-toolbar-icons.tui-heading:disabled{background-position:-193px -48px}.tui-toolbar-icons.tui-bold{background-position:-4px -4px}.tui-toolbar-icons.tui-bold:disabled{background-position:-25px -4px}.tui-toolbar-icons.tui-italic{background-position:-4px -48px}.tui-toolbar-icons.tui-italic:disabled{background-position:-25px -48px}.tui-toolbar-icons.tui-color{background-position:-172px -70px}.tui-toolbar-icons.tui-color:disabled{background-position:-193px -70px}.tui-toolbar-icons.tui-strike{background-position:-4px -26px}.tui-toolbar-icons.tui-strike:disabled{background-position:-25px -26px}.tui-toolbar-icons.tui-hrline{background-position:-46px -92px}.tui-toolbar-icons.tui-hrline:disabled{background-position:-67px -92px}.tui-toolbar-icons.tui-quote{background-position:-4px -114px}.tui-toolbar-icons.tui-quote:disabled{background-position:-25px -114px}.tui-toolbar-icons.tui-ul{background-position:-46px -4px}.tui-toolbar-icons.tui-ul:disabled{background-position:-67px -4px}.tui-toolbar-icons.tui-ol{background-position:-46px -26px}.tui-toolbar-icons.tui-ol:disabled{background-position:-67px -26px}.tui-toolbar-icons.tui-task{background-position:-130px -48px}.tui-toolbar-icons.tui-task:disabled{background-position:-151px -48px}.tui-toolbar-icons.tui-indent{background-position:-46px -48px}.tui-toolbar-icons.tui-indent:disabled{background-position:-67px -48px}.tui-toolbar-icons.tui-outdent{background-position:-46px -70px}.tui-toolbar-icons.tui-outdent:disabled{background-position:-67px -70px}.tui-toolbar-icons.tui-table{background-position:-88px -92px}.tui-toolbar-icons.tui-table:disabled{background-position:-109px -92px}.tui-toolbar-icons.tui-image{background-position:-130px -4px}.tui-toolbar-icons.tui-image:disabled{background-position:-151px -4px}.tui-toolbar-icons.tui-link{background-position:-130px -26px}.tui-toolbar-icons.tui-link:disabled{background-position:-151px -26px}.tui-toolbar-icons.tui-code{background-position:-130px -92px}.tui-toolbar-icons.tui-code:disabled{background-position:-151px -92px}.tui-toolbar-icons.tui-codeblock{background-position:-130px -70px}.tui-toolbar-icons.tui-codeblock:disabled{background-position:-151px -70px}.tui-toolbar-icons.tui-more{background-position:-172px -92px}.tui-toolbar-icons.tui-more:disabled{background-position:-193px -92px}.tui-colorpicker-svg-huebar,.tui-colorpicker-svg-slider,.tui-colorpicker-vml-slider{border:1px solid #ebebeb}.tui-editor-pseudo-clipboard{position:fixed;left:-1000px;top:-1000px;width:100px;height:100px}.te-ww-block-overlay.code-block-header{text-align:right;font-family:Open Sans,Helvetica Neue,Helvetica,Arial,sans-serif}.te-ww-block-overlay.code-block-header span{font-size:10px;font-weight:600;padding:0 10px;color:#333;cursor:default}.te-ww-block-overlay.code-block-header button{margin:8px;font-size:10px;color:#333;background-color:#f9f9f9;border:1px solid #ddd;padding:4px;height:auto}.te-popup-code-block-languages{position:fixed;box-sizing:border-box;width:130px}.te-popup-code-block-languages .tui-popup-body{max-height:169px;overflow:auto;padding:0}.te-popup-code-block-languages button{width:100%;background-color:#fff;border:none;outline:0;padding:0 10px;font-size:12px;line-height:24px;text-align:left;color:#777}.te-popup-code-block-languages button.active{background-color:#f4f4f4}.tui-popup-code-block-editor .tui-popup-wrapper{width:70%;height:70%;margin:auto;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.te-input-language{position:relative;margin-left:15px;cursor:pointer}.te-input-language input{font-family:Open Sans,Helvetica Neue,Helvetica,Arial,sans-serif;font-size:10px;padding:3px 5px;border:1px solid #ddd;background-color:#f9f9f9;box-sizing:border-box;width:130px;outline:none}.te-input-language input::-ms-clear{display:none}.te-input-language:after{content:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIgNWg4bC00IDV6IiBmaWxsPSIjNTU1IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=);position:absolute;top:1px;right:3px}.te-input-language.active:after{content:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIgOWg4TDYgNHoiIGZpbGw9IiM1NTUiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==)}.tui-popup-code-block-editor button{margin:-1px 3px}.tui-popup-code-block-editor .tui-popup-header-buttons{height:20px}.tui-popup-code-block-editor .popup-editor-toggle-preview:after{content:"Preview off";color:#777;margin-right:22px}.tui-popup-code-block-editor .popup-editor-toggle-preview.active:after{content:"Preview on";color:#4b96e6}.tui-popup-code-block-editor .popup-editor-toggle-scroll:after{content:"Scroll off";color:#777;margin-right:16px}.tui-popup-code-block-editor .popup-editor-toggle-scroll.active:after{content:"Scroll on";color:#4b96e6}.tui-popup-code-block-editor .popup-editor-toggle-fit{width:18px;height:18px;margin-top:4px;margin-right:14px;background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iIzU1NSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJNMTUgM0g5djJoNHY0aDJWM3pNMyAxNWg2di0ySDVWOUgzdjZ6Ii8+PHBhdGggZmlsbC1ydWxlPSJub256ZXJvIiBkPSJNMTMuOTE0IDUuNUwxMC41IDguOTE0IDkuMDg2IDcuNSAxMi41IDQuMDg2ek04LjkxNCAxMC41TDUuNSAxMy45MTQgNC4wODYgMTIuNSA3LjUgOS4wODZ6Ii8+PC9nPjwvc3ZnPg==)}.tui-popup-code-block-editor .popup-editor-toggle-fit.active{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iIzU1NSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBmaWxsLXJ1bGU9Im5vbnplcm8iIGQ9Ik03LjkxNCAxMS41TDQuNSAxNC45MTQgMy4wODYgMTMuNSA2LjUgMTAuMDg2eiIvPjxwYXRoIGQ9Ik05IDlIM3YyaDR2NGgyVjl6bTAgMGg2VjdoLTRWM0g5djZ6Ii8+PHBhdGggZmlsbC1ydWxlPSJub256ZXJvIiBkPSJNMTAuMDg2IDYuNUwxMy41IDMuMDg2IDE0LjkxNCA0LjUgMTEuNSA3LjkxNHoiLz48L2c+PC9zdmc+)}.tui-popup-code-block-editor .tui-popup-close-button{margin-top:6px}.tui-popup-code-block-editor .tui-popup-body{z-index:-1;padding:0;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex:1;flex:1}.tui-popup-code-block-editor .popup-editor-body{position:relative;-ms-flex:1;flex:1;border-bottom:1px solid #cacaca}.tui-popup-code-block-editor .te-button-section{padding:15px}.tui-popup-code-block-editor .te-button-section button{float:left}.tui-popup-code-block-editor .tui-editor-contents pre{margin:0;background-color:transparent}.tui-popup-code-block-editor .CodeMirror{height:auto}.tui-popup-code-block-editor .CodeMirror-line{font-family:Consolas,Courier,Lucida Grande,나눔바른고딕,Nanum Barun Gothic,맑은고딕,Malgun Gothic,sans-serif;font-size:13px;line-height:160%;letter-spacing:-.3px}.tui-popup-code-block-editor .popup-editor-editor-wrapper{min-height:100%}.tui-split-scroll-wrapper{position:relative}.tui-split-scroll{position:absolute}.tui-split-scroll,.tui-split-scroll-wrapper{width:100%;height:100%}.tui-split-scroll .tui-split-content-left,.tui-split-scroll .tui-split-content-right{position:absolute;top:0;width:50%;box-sizing:border-box}.tui-split-scroll .tui-split-content-left{left:0}.tui-split-scroll .tui-split-content-right{left:50%}.tui-split-scroll .tui-splitter{position:absolute;left:50%;top:0;height:100%;width:1px;border-left:1px solid #cacaca}.tui-split-scroll .tui-split-scroll-content{width:100%;height:100%;overflow:hidden;position:relative}.tui-split-scroll .tui-split-content-left,.tui-split-scroll .tui-split-content-right{height:100%;overflow-x:hidden;overflow-y:auto}.tui-split-scroll button.tui-scrollsync{top:10px;opacity:.2}.tui-split-scroll button.tui-scrollsync:after{content:"scroll off"}.tui-split-scroll.scroll-sync button.tui-scrollsync{opacity:.5}.tui-split-scroll.scroll-sync .tui-split-content-left,.tui-split-scroll.scroll-sync .tui-split-content-right{height:auto;overflow:initial}.tui-split-scroll.scroll-sync button.tui-scrollsync:after{content:"scroll on"}.tui-split-scroll.scroll-sync .tui-split-scroll-content{overflow-y:auto}.tui-split-scroll.single-content .tui-splitter{display:none}.tui-split-scroll.single-content .tui-split-content-left{width:100%}.tui-split-scroll.single-content .tui-split-content-right,.tui-split-scroll.single-content button.tui-scrollsync{display:none}@media (-ms-high-contrast:active),(-ms-high-contrast:none){.tui-split-scroll-wrapper .tui-splitter{left:calc(50% - 9px)}}@supports (-ms-accelerator:true){.tui-split-scroll-wrapper .tui-splitter{left:calc(50% - 9px)}}@media screen and (max-width:480px){.tui-popup-wrapper{max-width:300px}.tui-editor-popup{margin-left:-150px}.te-dropdown-toolbar{max-width:none}}.tui-editor-contents{margin:0;padding:0;font-size:13px;font-family:Open Sans,Helvetica Neue,Helvetica,Arial,나눔바른고딕,Nanum Barun Gothic,맑은고딕,Malgun Gothic,sans-serif}.tui-editor-contents :not(table){line-height:160%;box-sizing:content-box}.tui-editor-contents address,.tui-editor-contents cite,.tui-editor-contents dfn,.tui-editor-contents em,.tui-editor-contents i,.tui-editor-contents var{font-style:italic}.tui-editor-contents strong{font-weight:700}.tui-editor-contents p{margin:10px 0;color:#222}.tui-editor-contents>div>div:first-of-type h1,.tui-editor-contents>h1:first-of-type{margin-top:14px}.tui-editor-contents h1,.tui-editor-contents h2,.tui-editor-contents h3,.tui-editor-contents h4,.tui-editor-contents h5,.tui-editor-contents h6{font-weight:700;color:#222}.tui-editor-contents h1{font-size:24px;line-height:28px;border-bottom:3px double #999;margin:52px 0 15px;padding-bottom:7px}.tui-editor-contents h2{font-size:22px;line-height:23px;border-bottom:1px solid #dbdbdb;margin:20px 0 13px;padding-bottom:7px}.tui-editor-contents h3{font-size:20px;margin:18px 0 2px}.tui-editor-contents h4{font-size:18px;margin:10px 0 2px}.tui-editor-contents h3,.tui-editor-contents h4{line-height:18px}.tui-editor-contents h5{font-size:16px}.tui-editor-contents h6{font-size:14px}.tui-editor-contents h5,.tui-editor-contents h6{line-height:17px;margin:9px 0 -4px}.tui-editor-contents del{color:#999}.tui-editor-contents blockquote{margin:14px 0;border-left:4px solid #e5e5e5;padding:0 16px;color:#999}.tui-editor-contents blockquote ol,.tui-editor-contents blockquote p,.tui-editor-contents blockquote ul{color:#999}.tui-editor-contents blockquote>:first-child{margin-top:0}.tui-editor-contents blockquote>:last-child{margin-bottom:0}.tui-editor-contents code,.tui-editor-contents pre{font-family:Consolas,Courier,Apple SD 산돌고딕 Neo,-apple-system,Lucida Grande,Apple SD Gothic Neo,맑은 고딕,Malgun Gothic,Segoe UI,돋움,dotum,sans-serif;border:0;border-radius:0}.tui-editor-contents pre{margin:2px 0 8px;padding:18px;background-color:#f5f7f8}.tui-editor-contents code{color:#c1798b;background-color:#f9f2f4;padding:2px 3px;letter-spacing:-.3px;border-radius:2px}.tui-editor-contents pre code{padding:0;color:inherit;white-space:pre-wrap;background-color:transparent}.tui-editor-contents pre.addon{border:1px solid #e8ebed;background-color:#fff}.tui-editor-contents img{margin:4px 0 10px;box-sizing:border-box;vertical-align:top;max-width:100%}.tui-editor-contents table{border:1px solid rgba(0,0,0,.1);margin:12px 0 14px;color:#222;width:auto;border-collapse:collapse;box-sizing:border-box}.tui-editor-contents table td,.tui-editor-contents table th{border:1px solid rgba(0,0,0,.1);padding:5px 14px 5px 12px;height:32px}.tui-editor-contents table th{background-color:#555;font-weight:300;color:#fff;padding-top:6px}.tui-editor-contents dir,.tui-editor-contents menu,.tui-editor-contents ol,.tui-editor-contents ul{display:block;list-style-type:none;padding-left:24px;margin:6px 0 10px;color:#222}.tui-editor-contents ol{list-style-type:none;counter-reset:li}.tui-editor-contents ol>li{counter-increment:li}.tui-editor-contents ol>li:before,.tui-editor-contents ul>li:before{display:inline-block;position:absolute}.tui-editor-contents ul>li:before{content:"";margin-top:6px;margin-left:-17px;width:5px;height:5px;border-radius:50%;background-color:#ccc}.tui-editor-contents ol>li:before{content:"." counter(li);margin-left:-28px;width:24px;text-align:right;direction:rtl;color:#aaa}.tui-editor-contents ol ol,.tui-editor-contents ol ul,.tui-editor-contents ul ol,.tui-editor-contents ul ul{margin-top:0!important;margin-bottom:0!important}.tui-editor-contents ol li,.tui-editor-contents ul li{position:relative}.tui-editor-contents ol p,.tui-editor-contents ul p{margin:0}.tui-editor-contents ol li.task-list-item:before,.tui-editor-contents pre ul li:before,.tui-editor-contents ul li.task-list-item:before{content:""}.tui-editor-contents th ol,.tui-editor-contents th ul{color:#fff}.tui-editor-contents hr{border-top:1px solid #eee;margin:16px 0}.tui-editor-contents a{text-decoration:underline;color:#4b96e6}.tui-editor-contents a:hover{color:#1f70de}.tui-editor-contents a.image-link{position:relative}.tui-editor-contents a.image-link:before{content:"";position:absolute;margin:0;width:20px;height:20px;top:2px;right:2px;background-repeat:no-repeat;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAFKADAAQAAAABAAAAFAAAAACy3fD9AAAA/0lEQVQ4Ee2UIY6FQAyG/91wAQQJDg8SSwI3QIFAcQHuwFHQoOAEEFAELB6H4wIku+9vQgIP9zLyVbTTTufLtJ3MzzRNf1AoGlmu6ypBzvOMXyWkC+QLvDTjw6VM+Xr2OA6UZYmu67Dvu2zleX6zuq7D933EcQxNuyPu3usYYXVdw/M8mKYpIMMwxEZRJHbbNsmhkySJxE71APJmhGVZhnVdURQFlmU585GmKSzLEp+570Dlz+ZxQ/aGJVNYsm3bCIJA/LZtMY4jmqbBMAwIw1DiV/UAstEUltP3vawdxxFbVZVYDoWwM1eCp+LnoErIUt7DL/Ac1edWng1/WlXyD380myY5A34sAAAAAElFTkSuQmCC");cursor:pointer}.tui-editor-contents .task-list-item{border:0;list-style:none;padding-left:24px;margin-left:-24px}.tui-editor-contents .task-list-item:before{background-repeat:no-repeat;background-size:18px 18px;background-position:50%;content:"";margin-left:0;margin-top:0;border-radius:0;height:18px;width:18px;position:absolute;left:0;top:1px;cursor:pointer;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEqADAAQAAAABAAAAEgAAAACaqbJVAAAAQklEQVQ4EWM8c+bMfwYqABaQGcbGxhQZdfbsWQYmikxA0jxqEFJg4GCOhhGOgEESHg0jpMDAwRx8YQQuj0DlCaUAAEdBCPJ7TaEPAAAAAElFTkSuQmCC")}.tui-editor-contents .task-list-item.checked:before{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEqADAAQAAAABAAAAEgAAAACaqbJVAAAA1ElEQVQ4EWP0nvbsPwMVABMVzAAbMWoQIiT5OJgYvLS5EAJQFguGCB4BkCHt/kIM8kKsYFXbrn6DqyY6sJENefjuN8ORuz/ghoAYWA0COR2kEQbQDanc+I7h049/MGkwjVANFQYZkmXHD/YCyABiDAFpxQgjkJO9dbjA4QAKDxAAhQnIO9hcAlYAJDBcBHIySANII8gAYgwBGYZhEEgQZFjVJohhhFwCUg8CjPgyLT8nE8N/YJZGD1iIVlQSI4yQpT9+R40ZZDl0NlavoSsihj/4DAIAR+hZHUj727YAAAAASUVORK5CYII=")}.tui-editor-contents .task-list-item .task-list-item-checkbox,.tui-editor-contents .task-list-item input[type=checkbox]{margin-left:-17px;margin-right:3.8px;margin-top:3px}.tui-editor-contents-placeholder:before{content:attr(data-placeholder);color:grey;line-height:160%;position:absolute}.te-preview .tui-editor-contents h1{min-height:28px}.te-preview .tui-editor-contents h2{min-height:23px}.te-preview .tui-editor-contents blockquote{min-height:20px}.te-preview .tui-editor-contents li{min-height:22px}@media (-ms-high-contrast:active),(-ms-high-contrast:none){.te-ww-container .tui-editor-contents li{vertical-align:middle}.te-ww-container .tui-editor-contents .task-list-item:before,.te-ww-container .tui-editor-contents ol>li:before,.te-ww-container .tui-editor-contents ul>li:before{position:static;vertical-align:middle}.te-ww-container .tui-editor-contents ul>li:before{margin-top:-3px;margin-right:12px}.te-ww-container .tui-editor-contents ol>li:before{margin-right:6px}.te-ww-container .tui-editor-contents .task-list-item{padding-left:2px}}.tui-editor-contents .te-preview-highlight{position:relative;z-index:0}.tui-editor-contents .te-preview-highlight:after{content:"";background-color:rgba(255,245,131,.5);border-radius:4px;z-index:-1;position:absolute;top:-4px;right:-4px;left:-4px;bottom:-4px}.tui-editor-contents h1.te-preview-highlight:after,.tui-editor-contents h2.te-preview-highlight:after{bottom:0}.tui-editor-contents td.te-preview-highlight:after,.tui-editor-contents th.te-preview-highlight:after{display:none}.tui-editor-contents td.te-preview-highlight,.tui-editor-contents th.te-preview-highlight{background-color:rgba(255,245,131,.5)}.tui-editor-contents th.te-preview-highlight{color:#222}.te-md-container .CodeMirror{font-family:Open Sans,Helvetica Neue,Helvetica,Arial,나눔바른고딕,Nanum Barun Gothic,맑은고딕,Malgun Gothic,sans-serif;color:#222}.tui-md-heading1{font-size:24px}.tui-md-heading2{font-size:22px}.tui-md-heading3{font-size:20px}.tui-md-heading4{font-size:18px}.tui-md-heading5{font-size:16px}.tui-md-heading6{font-size:14px}.tui-md-heading.tui-md-delimiter.setext{line-height:15px}.tui-md-heading,.tui-md-list-item.tui-md-list-item-bullet,.tui-md-list-item.tui-md-meta,.tui-md-strong{font-weight:700}.tui-md-emph{font-style:italic}.tui-md-strike{text-decoration:line-through}.tui-md-strike.tui-md-delimiter{text-decoration:none}.tui-md-block-quote,.tui-md-delimiter,.tui-md-link,.tui-md-table,.tui-md-thematic-break{color:#ccc}.tui-md-code-block.tui-md-meta,.tui-md-code.tui-md-delimiter{color:#aaa}.tui-md-html,.tui-md-link.tui-md-link-url.tui-md-marked-text,.tui-md-meta{color:#999}.tui-md-block-quote.tui-md-marked-text,.tui-md-list-item.tui-md-meta{color:#555}.tui-md-table.tui-md-marked-text{color:#222}.tui-md-link.tui-md-link-desc.tui-md-marked-text,.tui-md-list-item-odd.tui-md-list-item-bullet{color:#4b96e6}.tui-md-list-item-even.tui-md-list-item-bullet{color:#cb4848}.tui-md-code.tui-md-marked-text{color:#c1798b}.tui-md-code{background-color:rgba(243,229,233,.5);padding:2px 0;letter-spacing:-.3px}.tui-md-code.tui-md-delimiter.start{padding-left:2px;border-top-left-radius:2px;border-bottom-left-radius:2px}.tui-md-code.tui-md-delimiter.end{padding-right:2px;border-top-right-radius:2px;border-bottom-right-radius:2px}.tui-md-code-block.CodeMirror-linebackground{left:20px;right:20px;background-color:#f5f7f8}.tui-md-code-block.CodeMirror-linebackground.start{top:2px}.tui-md-code-block.CodeMirror-linebackground.end{bottom:2px}.tui-md-code,.tui-md-code-block{font-family:Consolas,Courier,Lucida Grande,나눔바른고딕,Nanum Barun Gothic,맑은고딕,Malgun Gothic,sans-serif} -------------------------------------------------------------------------------- /examples/htdocs/calc.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | background: #ededed; 4 | } 5 | div.toolbar { 6 | text-align: right; 7 | } 8 | .line { 9 | width: 90%; 10 | height: 4rem; 11 | font-size: 3rem; 12 | text-align: right; 13 | text-overflow: ellipsis; 14 | } 15 | table { 16 | width: 100%; 17 | } 18 | button { 19 | background: none; 20 | border: none; 21 | overflow: hidden; 22 | margin: 1px; 23 | height: 3rem; 24 | font-size: 1.5rem; 25 | } 26 | table button { 27 | background: #fbfbfb; 28 | width: 100%; 29 | } 30 | table button.input { 31 | background: #f3f3f3; 32 | } 33 | table button:hover, button:hover { 34 | background: #c0c0c0; 35 | } 36 | button:active { 37 | transform: scale(0.9); 38 | } 39 | -------------------------------------------------------------------------------- /examples/htdocs/calc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Calc 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 |
16 |
17 | {{ value }} 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
59 |
60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /examples/htdocs/calc.js: -------------------------------------------------------------------------------- 1 | var calc = new Vue({ 2 | el: '#calc', 3 | data: { 4 | seen: false, 5 | value: '0' 6 | }, 7 | methods: { 8 | clearLine: function() { 9 | this.value = '0'; 10 | }, 11 | append: function(value) { 12 | if (this.value == '0') { 13 | this.value = value; 14 | } else { 15 | this.value += value; 16 | } 17 | }, 18 | backspace: function() { 19 | if (this.value.length > 1) { 20 | this.value = this.value.substring(0, this.value.length - 1); 21 | } else { 22 | this.value = '0'; 23 | } 24 | }, 25 | calculate: function() { 26 | var calc = this; 27 | fetch('rest/calculate', { 28 | method: 'POST', 29 | headers: { 30 | "Content-Type": "application/json" 31 | }, 32 | body: JSON.stringify({ 33 | line: calc.value 34 | }) 35 | }).then(function(response) { 36 | return response.json(); 37 | }).then(function(response) { 38 | calc.value = '' + response.line; 39 | }); 40 | } 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /examples/htdocs/file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | File 6 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 |

This example shows how to choose a file to operate using Lua.

37 |
38 |
Using a dedicated panel
39 | 40 | 41 | 42 | 43 |

{{ filename }}

44 |
45 |
46 |
Using a single input field
47 | 48 |
49 |
50 | 71 | -------------------------------------------------------------------------------- /examples/htdocs/md.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Markdown Editor 6 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 60 | 84 | -------------------------------------------------------------------------------- /examples/htdocs/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple WebView Example 5 | 6 | 7 | 8 |

Initializing...

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

Print Lua:

19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |


27 |
28 | 29 | 30 | 31 |
32 | 33 | 47 | 85 | 86 | -------------------------------------------------------------------------------- /examples/htdocs/todo.css: -------------------------------------------------------------------------------- 1 | /* See https://github.com/tastejs/todomvc/tree/gh-pages/examples/vue */ 2 | 3 | html, 4 | body { 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | button { 10 | margin: 0; 11 | padding: 0; 12 | border: 0; 13 | background: none; 14 | font-size: 100%; 15 | vertical-align: baseline; 16 | font-family: inherit; 17 | font-weight: inherit; 18 | color: inherit; 19 | -webkit-appearance: none; 20 | appearance: none; 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | } 24 | 25 | body { 26 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 27 | line-height: 1.4em; 28 | background: #f5f5f5; 29 | color: #4d4d4d; 30 | min-width: 230px; 31 | max-width: 550px; 32 | margin: 0 auto; 33 | -webkit-font-smoothing: antialiased; 34 | -moz-osx-font-smoothing: grayscale; 35 | font-weight: 300; 36 | } 37 | 38 | :focus { 39 | outline: 0; 40 | } 41 | 42 | .hidden { 43 | display: none; 44 | } 45 | 46 | .todoapp { 47 | background: #fff; 48 | margin: 130px 0 40px 0; 49 | position: relative; 50 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 51 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 52 | } 53 | 54 | .todoapp input::-webkit-input-placeholder { 55 | font-style: italic; 56 | font-weight: 300; 57 | color: #e6e6e6; 58 | } 59 | 60 | .todoapp input::-moz-placeholder { 61 | font-style: italic; 62 | font-weight: 300; 63 | color: #e6e6e6; 64 | } 65 | 66 | .todoapp input::input-placeholder { 67 | font-style: italic; 68 | font-weight: 300; 69 | color: #e6e6e6; 70 | } 71 | 72 | .todoapp h1 { 73 | position: absolute; 74 | top: -155px; 75 | width: 100%; 76 | font-size: 100px; 77 | font-weight: 100; 78 | text-align: center; 79 | color: rgba(175, 47, 47, 0.15); 80 | -webkit-text-rendering: optimizeLegibility; 81 | -moz-text-rendering: optimizeLegibility; 82 | text-rendering: optimizeLegibility; 83 | } 84 | 85 | .new-todo, 86 | .edit { 87 | position: relative; 88 | margin: 0; 89 | width: 100%; 90 | font-size: 24px; 91 | font-family: inherit; 92 | font-weight: inherit; 93 | line-height: 1.4em; 94 | border: 0; 95 | color: inherit; 96 | padding: 6px; 97 | border: 1px solid #999; 98 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 99 | box-sizing: border-box; 100 | -webkit-font-smoothing: antialiased; 101 | -moz-osx-font-smoothing: grayscale; 102 | } 103 | 104 | .new-todo { 105 | padding: 16px 16px 16px 60px; 106 | border: none; 107 | background: rgba(0, 0, 0, 0.003); 108 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 109 | } 110 | 111 | .main { 112 | position: relative; 113 | z-index: 2; 114 | border-top: 1px solid #e6e6e6; 115 | } 116 | 117 | .toggle-all { 118 | width: 1px; 119 | height: 1px; 120 | border: none; /* Mobile Safari */ 121 | opacity: 0; 122 | position: absolute; 123 | right: 100%; 124 | bottom: 100%; 125 | } 126 | 127 | .toggle-all + label { 128 | width: 60px; 129 | height: 34px; 130 | font-size: 0; 131 | position: absolute; 132 | top: -52px; 133 | left: -13px; 134 | -webkit-transform: rotate(90deg); 135 | transform: rotate(90deg); 136 | } 137 | 138 | .toggle-all + label:before { 139 | content: '❯'; 140 | font-size: 22px; 141 | color: #e6e6e6; 142 | padding: 10px 27px 10px 27px; 143 | } 144 | 145 | .toggle-all:checked + label:before { 146 | color: #737373; 147 | } 148 | 149 | .todo-list { 150 | margin: 0; 151 | padding: 0; 152 | list-style: none; 153 | } 154 | 155 | .todo-list li { 156 | position: relative; 157 | font-size: 24px; 158 | border-bottom: 1px solid #ededed; 159 | } 160 | 161 | .todo-list li:last-child { 162 | border-bottom: none; 163 | } 164 | 165 | .todo-list li.editing { 166 | border-bottom: none; 167 | padding: 0; 168 | } 169 | 170 | .todo-list li.editing .edit { 171 | display: block; 172 | width: calc(100% - 43px); 173 | padding: 12px 16px; 174 | margin: 0 0 0 43px; 175 | } 176 | 177 | .todo-list li.editing .view { 178 | display: none; 179 | } 180 | 181 | .todo-list li .toggle { 182 | text-align: center; 183 | width: 40px; 184 | /* auto, since non-WebKit browsers doesn't support input styling */ 185 | height: auto; 186 | position: absolute; 187 | top: 0; 188 | bottom: 0; 189 | margin: auto 0; 190 | border: none; /* Mobile Safari */ 191 | -webkit-appearance: none; 192 | appearance: none; 193 | } 194 | 195 | .todo-list li .toggle { 196 | opacity: 0; 197 | } 198 | 199 | .todo-list li .toggle + label { 200 | /* 201 | Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433 202 | IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/ 203 | */ 204 | background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E'); 205 | background-repeat: no-repeat; 206 | background-position: center left; 207 | } 208 | 209 | .todo-list li .toggle:checked + label { 210 | background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E'); 211 | } 212 | 213 | .todo-list li label { 214 | word-break: break-all; 215 | padding: 15px 15px 15px 60px; 216 | display: block; 217 | line-height: 1.2; 218 | transition: color 0.4s; 219 | } 220 | 221 | .todo-list li.completed label { 222 | color: #d9d9d9; 223 | text-decoration: line-through; 224 | } 225 | 226 | .todo-list li .destroy { 227 | display: none; 228 | position: absolute; 229 | top: 0; 230 | right: 10px; 231 | bottom: 0; 232 | width: 40px; 233 | height: 40px; 234 | margin: auto 0; 235 | font-size: 30px; 236 | color: #cc9a9a; 237 | margin-bottom: 11px; 238 | transition: color 0.2s ease-out; 239 | } 240 | 241 | .todo-list li .destroy:hover { 242 | color: #af5b5e; 243 | } 244 | 245 | .todo-list li .destroy:after { 246 | content: '×'; 247 | } 248 | 249 | .todo-list li:hover .destroy { 250 | display: block; 251 | } 252 | 253 | .todo-list li .edit { 254 | display: none; 255 | } 256 | 257 | .todo-list li.editing:last-child { 258 | margin-bottom: -1px; 259 | } 260 | 261 | .footer { 262 | color: #777; 263 | padding: 10px 15px; 264 | height: 20px; 265 | text-align: center; 266 | border-top: 1px solid #e6e6e6; 267 | } 268 | 269 | .footer:before { 270 | content: ''; 271 | position: absolute; 272 | right: 0; 273 | bottom: 0; 274 | left: 0; 275 | height: 50px; 276 | overflow: hidden; 277 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 278 | 0 8px 0 -3px #f6f6f6, 279 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 280 | 0 16px 0 -6px #f6f6f6, 281 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 282 | } 283 | 284 | .todo-count { 285 | float: left; 286 | text-align: left; 287 | } 288 | 289 | .todo-count strong { 290 | font-weight: 300; 291 | } 292 | 293 | .filters { 294 | margin: 0; 295 | padding: 0; 296 | list-style: none; 297 | position: absolute; 298 | right: 0; 299 | left: 0; 300 | } 301 | 302 | .filters li { 303 | display: inline; 304 | } 305 | 306 | .filters li a { 307 | color: inherit; 308 | margin: 3px; 309 | padding: 3px 7px; 310 | text-decoration: none; 311 | border: 1px solid transparent; 312 | border-radius: 3px; 313 | } 314 | 315 | .filters li a:hover { 316 | border-color: rgba(175, 47, 47, 0.1); 317 | } 318 | 319 | .filters li a.selected { 320 | border-color: rgba(175, 47, 47, 0.2); 321 | } 322 | 323 | .clear-completed, 324 | html .clear-completed:active { 325 | float: right; 326 | position: relative; 327 | line-height: 20px; 328 | text-decoration: none; 329 | cursor: pointer; 330 | } 331 | 332 | .clear-completed:hover { 333 | text-decoration: underline; 334 | } 335 | 336 | .info { 337 | margin: 65px auto 0; 338 | color: #bfbfbf; 339 | font-size: 10px; 340 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 341 | text-align: center; 342 | } 343 | 344 | .info p { 345 | line-height: 1; 346 | } 347 | 348 | .info a { 349 | color: inherit; 350 | text-decoration: none; 351 | font-weight: 400; 352 | } 353 | 354 | .info a:hover { 355 | text-decoration: underline; 356 | } 357 | 358 | /* 359 | Hack to remove background from Mobile Safari. 360 | Can't use it globally since it destroys checkboxes in Firefox 361 | */ 362 | @media screen and (-webkit-min-device-pixel-ratio:0) { 363 | .toggle-all, 364 | .todo-list li .toggle { 365 | background: none; 366 | } 367 | 368 | .todo-list li .toggle { 369 | height: 40px; 370 | } 371 | } 372 | 373 | @media (max-width: 430px) { 374 | .footer { 375 | height: 50px; 376 | } 377 | 378 | .filters { 379 | bottom: 10px; 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /examples/htdocs/todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue.js - TodoMVC 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |

todos

14 | 19 |
20 |
21 | 22 | 23 |
    24 |
  • 28 |
    29 | 30 | 31 | 32 |
    33 | 39 |
  • 40 |
41 |
42 |
43 | 44 | {{ remaining }} {{ remaining | pluralize }} left 45 | 46 | 51 | 54 |
55 |
56 |
57 |

Double-click to edit a todo

58 |

Written by Evan You

59 |

Part of TodoMVC

60 |
61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /examples/htdocs/todo.js: -------------------------------------------------------------------------------- 1 | // See https://github.com/tastejs/todomvc/tree/gh-pages/examples/vue 2 | 3 | // IE on file does not provide localStorage 4 | var windowLocalStorage = window.localStorage; 5 | if (!windowLocalStorage) { 6 | windowLocalStorage = { 7 | _data : {}, 8 | setItem : function(id, val) { return this._data[id] = String(val); }, 9 | getItem : function(id) { return this._data.hasOwnProperty(id) ? this._data[id] : undefined; }, 10 | removeItem : function(id) { return delete this._data[id]; }, 11 | clear : function() { return this._data = {}; } 12 | }; 13 | } 14 | 15 | // Full spec-compliant TodoMVC with localStorage persistence 16 | // and hash-based routing in ~120 effective lines of JavaScript. 17 | 18 | // localStorage persistence 19 | var STORAGE_KEY = 'todos-vuejs-2.0' 20 | var todoStorage = { 21 | fetch: function () { 22 | var todos = JSON.parse(windowLocalStorage.getItem(STORAGE_KEY) || '[]') 23 | todos.forEach(function (todo, index) { 24 | todo.id = index 25 | }) 26 | todoStorage.uid = todos.length 27 | return todos 28 | }, 29 | save: function (todos) { 30 | windowLocalStorage.setItem(STORAGE_KEY, JSON.stringify(todos)) 31 | } 32 | } 33 | 34 | // visibility filters 35 | var filters = { 36 | all: function (todos) { 37 | return todos 38 | }, 39 | active: function (todos) { 40 | return todos.filter(function (todo) { 41 | return !todo.completed 42 | }) 43 | }, 44 | completed: function (todos) { 45 | return todos.filter(function (todo) { 46 | return todo.completed 47 | }) 48 | } 49 | } 50 | 51 | // app Vue instance 52 | var app = new Vue({ 53 | // app initial state 54 | data: { 55 | todos: todoStorage.fetch(), 56 | newTodo: '', 57 | editedTodo: null, 58 | visibility: 'all' 59 | }, 60 | 61 | // watch todos change for localStorage persistence 62 | watch: { 63 | todos: { 64 | handler: function (todos) { 65 | todoStorage.save(todos) 66 | }, 67 | deep: true 68 | } 69 | }, 70 | 71 | // computed properties 72 | // http://vuejs.org/guide/computed.html 73 | computed: { 74 | filteredTodos: function () { 75 | return filters[this.visibility](this.todos) 76 | }, 77 | remaining: function () { 78 | return filters.active(this.todos).length 79 | }, 80 | allDone: { 81 | get: function () { 82 | return this.remaining === 0 83 | }, 84 | set: function (value) { 85 | this.todos.forEach(function (todo) { 86 | todo.completed = value 87 | }) 88 | } 89 | } 90 | }, 91 | 92 | filters: { 93 | pluralize: function (n) { 94 | return n === 1 ? 'item' : 'items' 95 | } 96 | }, 97 | 98 | // methods that implement data logic. 99 | // note there's no DOM manipulation here at all. 100 | methods: { 101 | addTodo: function () { 102 | var value = this.newTodo && this.newTodo.trim() 103 | if (!value) { 104 | return 105 | } 106 | this.todos.push({ 107 | id: todoStorage.uid++, 108 | title: value, 109 | completed: false 110 | }) 111 | this.newTodo = '' 112 | }, 113 | 114 | removeTodo: function (todo) { 115 | this.todos.splice(this.todos.indexOf(todo), 1) 116 | }, 117 | 118 | editTodo: function (todo) { 119 | this.beforeEditCache = todo.title 120 | this.editedTodo = todo 121 | }, 122 | 123 | doneEdit: function (todo) { 124 | if (!this.editedTodo) { 125 | return 126 | } 127 | this.editedTodo = null 128 | todo.title = todo.title.trim() 129 | if (!todo.title) { 130 | this.removeTodo(todo) 131 | } 132 | }, 133 | 134 | cancelEdit: function (todo) { 135 | this.editedTodo = null 136 | todo.title = this.beforeEditCache 137 | }, 138 | 139 | removeCompleted: function () { 140 | this.todos = filters.active(this.todos) 141 | } 142 | }, 143 | 144 | // a custom directive to wait for the DOM to be updated 145 | // before focusing on the input field. 146 | // http://vuejs.org/guide/custom-directive.html 147 | directives: { 148 | 'todo-focus': function (el, binding) { 149 | if (binding.value) { 150 | el.focus() 151 | } 152 | } 153 | } 154 | }) 155 | 156 | // handle routing 157 | function onHashChange () { 158 | var visibility = window.location.hash.replace(/#\/?/, '') 159 | if (filters[visibility]) { 160 | app.visibility = visibility 161 | } else { 162 | window.location.hash = '' 163 | app.visibility = 'all' 164 | } 165 | } 166 | 167 | window.addEventListener('hashchange', onHashChange) 168 | onHashChange() 169 | 170 | // mount 171 | app.$mount('.todoapp') 172 | -------------------------------------------------------------------------------- /examples/htdocs/winexp.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 12px Helvetica, Arial, sans-serif; 3 | line-height: 1.4em; 4 | background: #f5f5f5; 5 | color: #4d4d4d; 6 | } 7 | div.toolbar { 8 | text-align: right; 9 | } 10 | .strikeout { 11 | text-decoration: line-through; 12 | } 13 | -------------------------------------------------------------------------------- /examples/htdocs/winexp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Window Explorer 6 | 7 | 8 | 9 |
10 |
11 | 13 | 14 | 15 | 16 | 17 |
18 |

Windows ({{ list.length }})

19 | 20 | 26 | 🔍 27 | 28 |
    29 |
  • 30 | 31 | 33 | {{ wnd.text }} 34 |
  • 35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/htdocs/winexp.js: -------------------------------------------------------------------------------- 1 | var filters = { 2 | all: function (list) { 3 | return list; 4 | }, 5 | visible: function (list) { 6 | return list.filter(function(item) { 7 | return item.visible; 8 | }); 9 | }, 10 | hidden: function (list) { 11 | return list.filter(function(item) { 12 | return !item.visible; 13 | }); 14 | } 15 | } 16 | 17 | var main = new Vue({ 18 | el: '#main', 19 | data: { 20 | list: [], 21 | search: '', 22 | seen: false, 23 | visibility: 'all' 24 | }, 25 | methods: { 26 | refresh: function() { 27 | this.value = '0'; 28 | } 29 | }, 30 | computed: { 31 | filteredWindows: function () { 32 | var searchLowerCase = this.search.toLowerCase(); 33 | return filters[this.visibility](this.list.filter(function(item) { 34 | return item.text.toLowerCase().indexOf(searchLowerCase) >= 0; 35 | })); 36 | } 37 | } 38 | }); 39 | 40 | function addWindows(list, clean) { 41 | main.list = clean ? list : main.list.concat(list); 42 | main.list.sort(function(a, b) { 43 | return a.text === b.text ? 0 : (a.text > b.text ? 1 : -1); 44 | }) 45 | } 46 | 47 | // sample data 48 | /* 49 | main.list = [ 50 | {handle: 123, text: 'Title', visible: false, width: 100, height: 100,left: 0, top: 0}, 51 | {handle: 123, text: 'Hidden title', visible: true, width: 100, height: 100,left: 0, top: 0}, 52 | {handle: 456, text: 'long long long long long long long long long long long long long long long long title', visible: true, width: 100, height: 100,left: 0, top: 0} 53 | ]; 54 | */ -------------------------------------------------------------------------------- /examples/htdocs/winexp.lua: -------------------------------------------------------------------------------- 1 | local winapiLib = require('winapi') 2 | 3 | local viewportLeft, viewportTop = -10240, -10240 4 | local viewportRight, viewportBottom = 10240, 10240 5 | local minimizedLeft, minimizedTop = -32000, -32000 6 | local minWidth, minHeight = 5, 5 7 | 8 | local currentWindow 9 | 10 | local WINDOW_TEXT = 'Window Explorer' 11 | local HIDDEN_WINDOW_TEXT = 'Hidden '..WINDOW_TEXT 12 | 13 | local winexp = {} 14 | 15 | local function isViewableWindow(w) 16 | local width, height = w:get_bounds() 17 | if width > minWidth and height > minHeight then 18 | local left, top = w:get_position() 19 | if left == minimizedLeft and top == minimizedTop then -- special posision when minimized 20 | return true 21 | end 22 | local right, bottom = left + width, top + height 23 | return left < viewportRight and top < viewportBottom and right > viewportLeft and bottom > viewportTop 24 | end 25 | return false 26 | end 27 | 28 | local function isTopLevelWindow(w) 29 | local pw = w:get_parent() 30 | return pw and pw:get_handle() 31 | end 32 | 33 | local function isUserWindow(w) 34 | return isTopLevelWindow(w) and isViewableWindow(w) and w:get_text() ~= nil 35 | end 36 | 37 | local function isVisibleWindow(w) 38 | return w:is_visible() and isViewableWindow(w) 39 | end 40 | 41 | local function getWindowInfos(w) 42 | if not w then 43 | return nil 44 | end 45 | local width, height = w:get_bounds() 46 | local left, top = w:get_position() 47 | local text = w:get_text() 48 | if text and #text > 128 then 49 | text = string.sub(text, 1, 125)..'...' 50 | end 51 | local p = w:get_process() 52 | return { 53 | handle = w:get_handle(), 54 | text = text, 55 | visible = w:is_visible(), 56 | width = width, 57 | height = height, 58 | left = left, 59 | top = top, 60 | pid = p and p:get_pid(), 61 | } 62 | end 63 | 64 | function winexp.printWindow(w) 65 | if w then 66 | local i = getWindowInfos(w) 67 | print(i.handle, string.format('0x%08x', i.pid), i.visible, string.format('%dx%d', i.left, i.top), string.format('%dx%d', i.width, i.height), i.text) 68 | end 69 | end 70 | 71 | function winexp.findWindow(m) 72 | return winapiLib.find_window_ex(m or isViewableWindow) 73 | end 74 | 75 | function winexp.listWindows(m, t) 76 | if not m then 77 | m = isViewableWindow 78 | end 79 | if not t then 80 | return winapiLib.find_all_windows(m) 81 | end 82 | local list = {} 83 | winapiLib.enum_windows(function(w) 84 | if m(w) then 85 | table.insert(list, t(w)) 86 | end 87 | end) 88 | return list 89 | end 90 | 91 | function winexp.listWindowsInfos() 92 | return winexp.listWindows(isUserWindow, getWindowInfos) 93 | end 94 | 95 | local SW_HIDE = 0 -- Hides the window and activates another window 96 | local SW_SHOW = 5 -- Activates the window and displays it in its current size and position 97 | local SW_SHOWNA = 8 -- Displays the window in its current size and position 98 | 99 | function winexp.getWindowByHandle(h) 100 | return winapiLib.find_window_ex(function(w) 101 | return w:get_handle() == h 102 | end) 103 | end 104 | 105 | function winexp.toggleWindowByHandle(h) 106 | local w = winexp.getWindowByHandle(h) 107 | if w then 108 | w:show(w:is_visible() and SW_HIDE or SW_SHOWNA) 109 | end 110 | end 111 | 112 | function winexp.foregroundWindowByHandle(h) 113 | local w = winexp.getWindowByHandle(h) 114 | if w then 115 | w:set_foreground() 116 | end 117 | end 118 | 119 | function winexp.lookForCurrentWindow() 120 | if not currentWindow then 121 | -- we could not see our own window so we check the foreground one with our pid 122 | local w = winapiLib.get_foreground_window() 123 | if w then 124 | local pid = winapiLib.get_current_pid() 125 | local p = w:get_process() 126 | if p and p:get_pid() == pid then 127 | currentWindow = w 128 | end 129 | end 130 | end 131 | return currentWindow 132 | end 133 | 134 | function winexp.hideCurrentSession() 135 | local cw = winexp.lookForCurrentWindow() 136 | if cw then 137 | cw:show(SW_HIDE) 138 | cw:set_text(HIDDEN_WINDOW_TEXT) 139 | end 140 | end 141 | 142 | function winexp.restoreHiddenSession() 143 | local pw = winapiLib.find_window_ex(function(w) 144 | return not w:is_visible() and w:get_text() == HIDDEN_WINDOW_TEXT 145 | end) 146 | if not pw then 147 | return false 148 | end 149 | local cw = winexp.lookForCurrentWindow() 150 | if cw then 151 | --local width, height = cw:get_bounds() 152 | --local left, top = cw:get_position() 153 | --pw:resize(left, top, width, height) 154 | pw:set_text(WINDOW_TEXT) 155 | pw:show(SW_SHOW) 156 | cw:show(SW_HIDE) 157 | print('Restoring hidden session') 158 | return true 159 | end 160 | return false 161 | end 162 | 163 | winapiLib.set_encoding(winapiLib.CP_UTF8) 164 | 165 | local pmw = winapiLib.find_window('Progman', 'Program Manager') 166 | if pmw then 167 | local width, height = pmw:get_bounds() 168 | viewportLeft, viewportTop = pmw:get_position() 169 | viewportRight = viewportLeft + width 170 | viewportBottom = viewportTop + height 171 | end 172 | 173 | local function listWindows() 174 | callJs('addWindows', winexp.listWindowsInfos(), true) 175 | end 176 | 177 | winexp.lookForCurrentWindow() 178 | if winexp.restoreHiddenSession() then 179 | require('webview').terminate(webview, true) 180 | end 181 | 182 | expose('hideCurrentSession', winexp.hideCurrentSession) 183 | expose('toggleWindow', winexp.toggleWindowByHandle, true) 184 | expose('foregroundWindow', winexp.foregroundWindowByHandle, true) 185 | expose('listWindows', listWindows) 186 | 187 | listWindows() 188 | -------------------------------------------------------------------------------- /examples/launch.lua: -------------------------------------------------------------------------------- 1 | require('webview-launcher').launchFromArgs() -------------------------------------------------------------------------------- /examples/open.lua: -------------------------------------------------------------------------------- 1 | -- Default web content 2 | local url = [[data:text/html, 3 | 4 | 5 |

Welcome !

6 |

You could specify an URL to open as a command line argument.

7 | 8 | 9 | ]] 10 | 11 | -- Parse command line arguments 12 | local urlArg = arg[1] 13 | if urlArg and urlArg ~= '' then 14 | if urlArg == '-h' or urlArg == '/?' or urlArg == '--help' then 15 | print('Opens a WebView using the specified URL') 16 | print('Optional arguments: url title width height resizable') 17 | os.exit(0) 18 | end 19 | local protocol = string.match(urlArg, '^([^:]+):.+$') 20 | if protocol == 'http' or protocol == 'https' or protocol == 'file' or protocol == 'data' then 21 | url = urlArg 22 | elseif string.match(urlArg, '^.:\\.+$') or string.match(urlArg, '^/.+$') then 23 | url = 'file://'..tostring(urlArg) 24 | else 25 | print('Invalid URL, to open a file please use an absolute path') 26 | os.exit(22) 27 | end 28 | end 29 | local title = arg[2] or 'Web View' 30 | local width = arg[3] or 800 31 | local height = arg[4] or 600 32 | local resizable = arg[5] ~= 'false' 33 | 34 | -- Opens the web view 35 | require('webview').open(url, title, width, height, resizable) 36 | -------------------------------------------------------------------------------- /examples/simple.lua: -------------------------------------------------------------------------------- 1 | local webviewLib = require('webview') 2 | 3 | local content = [[ 4 | 5 | 6 |

It works !

7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 |
16 | 17 | 23 | 24 | ]] 25 | 26 | content = string.gsub(content, "[ %c!#$%%&'()*+,/:;=?@%[%]]", function(c) 27 | return string.format('%%%02X', string.byte(c)) 28 | end) 29 | 30 | local webview = webviewLib.new('data:text/html,'..content, 'Example', 480, 240, true, true) 31 | 32 | webviewLib.callback(webview, function(value) 33 | if value == 'print_date' then 34 | print(os.date()) 35 | elseif value == 'show_date' then 36 | webviewLib.eval(webview, 'showText("Lua date is '..os.date()..'")', true) 37 | elseif value == 'fullscreen' then 38 | webviewLib.fullscreen(webview, true) 39 | elseif value == 'exit_fullscreen' then 40 | webviewLib.fullscreen(webview, false) 41 | elseif value == 'terminate' then 42 | webviewLib.terminate(webview, true) 43 | elseif string.find(value, '^title=') then 44 | webviewLib.title(webview, string.sub(value, 7)) 45 | else 46 | print('callback received', value) 47 | end 48 | end) 49 | 50 | webviewLib.loop(webview) 51 | -------------------------------------------------------------------------------- /rock.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile for rockspec 3 | # 4 | # Install with Lua Binaries: 5 | # luarocks --lua-dir C:/bin/lua-5.3.5_Win64_bin MAKE=make CC=gcc LD=gcc install lua-webview 6 | # 7 | # Build with luaclibs: 8 | # luarocks --lua-dir ../../luaclibs/lua/src MAKE=make CC=gcc LD=gcc make 9 | # luarocks --lua-dir C:/bin/lua-5.4.2_Win64_bin MAKE=make CC=gcc LD=gcc make lua-webview-1.3-2.rockspec 10 | # 11 | 12 | CC ?= gcc 13 | 14 | PLAT ?= windows 15 | LIBNAME = webview 16 | 17 | ifdef LUA_LIBDIR 18 | LUA_LIBDIR_OPT=-L$(LUA_LIBDIR) 19 | else 20 | LUA_LIBDIR_OPT= 21 | endif 22 | 23 | #LUA_APP = $(LUA_BINDIR)/$(LUA) 24 | LUA_APP = $(LUA) 25 | LUA_VERSION = $(shell $(LUA_APP) -e "print(string.sub(_VERSION, 5))") 26 | LUA_LIBNAME = lua$(subst .,,$(LUA_VERSION)) 27 | LUA_BITS = $(shell $(LUA_APP) -e "print(string.len(string.pack('T', 0)) * 8)") 28 | 29 | WEBVIEW_ARCH = x64 30 | ifeq ($(LUA_BITS),32) 31 | WEBVIEW_ARCH = x86 32 | endif 33 | 34 | WEBVIEW_C = webview-c 35 | MS_WEBVIEW2 = $(WEBVIEW_C)/ms.webview2 36 | 37 | CFLAGS_windows = -Wall \ 38 | -Wextra \ 39 | -Wno-unused-parameter \ 40 | -Wstrict-prototypes \ 41 | -I$(WEBVIEW_C) \ 42 | -I$(MS_WEBVIEW2)/include \ 43 | -I$(LUA_INCDIR) \ 44 | -DWEBVIEW_WINAPI=1 45 | 46 | LIBFLAG_windows = -O \ 47 | -shared \ 48 | -Wl,-s \ 49 | $(LUA_LIBDIR_OPT) -l$(LUA_LIBNAME) \ 50 | -static-libgcc \ 51 | -lole32 -lcomctl32 -loleaut32 -luuid -lgdi32 52 | 53 | TARGET_windows = $(LIBNAME).dll 54 | 55 | CFLAGS_linux = -pedantic \ 56 | -Wall \ 57 | -Wextra \ 58 | -Wno-unused-parameter \ 59 | -Wstrict-prototypes \ 60 | -I$(WEBVIEW_C) \ 61 | -I$(LUA_INCDIR) \ 62 | -DWEBVIEW_GTK=1 \ 63 | $(shell pkg-config --cflags gtk+-3.0 webkit2gtk-4.0) 64 | 65 | LIBFLAG_linux= -static-libgcc \ 66 | -Wl,-s \ 67 | $(LUA_LIBDIR_OPT) \ 68 | $(shell pkg-config --libs gtk+-3.0 webkit2gtk-4.0) 69 | 70 | TARGET_linux = $(LIBNAME).so 71 | 72 | 73 | TARGET = $(TARGET_$(PLAT)) 74 | 75 | SOURCES = webview.c 76 | 77 | OBJS = webview.o 78 | 79 | lib: $(TARGET) 80 | 81 | install: install-$(PLAT) 82 | cp $(TARGET) $(INST_LIBDIR) 83 | -cp webview-launcher.lua $(INST_LUADIR) 84 | 85 | install-linux: 86 | 87 | install-windows: 88 | cp $(MS_WEBVIEW2)/$(WEBVIEW_ARCH)/WebView2Loader.dll $(INST_BINDIR) 89 | 90 | show: 91 | @echo PLAT: $(PLAT) 92 | @echo LUA_VERSION: $(LUA_VERSION) 93 | @echo LUA_LIBNAME: $(LUA_LIBNAME) 94 | @echo CFLAGS: $(CFLAGS) 95 | @echo LIBFLAG: $(LIBFLAG) 96 | @echo LUA_LIBDIR: $(LUA_LIBDIR) 97 | @echo LUA_BINDIR: $(LUA_BINDIR) 98 | @echo LUA_INCDIR: $(LUA_INCDIR) 99 | @echo LUA: $(LUA) 100 | @echo LUALIB: $(LUALIB) 101 | 102 | show-install: 103 | @echo PREFIX: $(PREFIX) or $(INST_PREFIX) 104 | @echo BINDIR: $(BINDIR) or $(INST_BINDIR) 105 | @echo LIBDIR: $(LIBDIR) or $(INST_LIBDIR) 106 | @echo LUADIR: $(LUADIR) or $(INST_LUADIR) 107 | 108 | $(TARGET): $(OBJS) 109 | $(CC) $(OBJS) $(LIBFLAG) $(LIBFLAG_$(PLAT)) -o $(TARGET) 110 | 111 | clean: 112 | -$(RM) $(OBJS) $(TARGET) 113 | 114 | $(OBJS): %.o : %.c $(SOURCES) 115 | $(CC) $(CFLAGS) $(CFLAGS_$(PLAT)) -c -o $@ $< 116 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | 2 | local webviewLauncher = require('webview-launcher') 3 | 4 | print('-- JSON --------') 5 | local values = { 6 | 'ti/ti\nta\9ta\tto\20to "tutu" ty\\ty', 7 | '', 'Hi', true, false, 123, -123, 1.23, 8 | } 9 | for _, value in ipairs(values) do 10 | local encoded = webviewLauncher.jsonLib.encode(value) 11 | local decoded = webviewLauncher.jsonLib.decode(encoded) 12 | if value == decoded then 13 | print(encoded, type(value), 'Ok') 14 | else 15 | print('>>'..tostring(value)..'<<'..type(value)) 16 | print('>>'..tostring(encoded)..'<<'..type(encoded)) 17 | print('>>'..tostring(decoded)..'<<'..type(decoded)) 18 | end 19 | end 20 | print('-- FS --------') 21 | print('currentdir:', webviewLauncher.fsLib.currentdir()) 22 | local paths = {'webview-launcher.lua', 'not a file'} 23 | for _, path in ipairs(paths) do 24 | print(path, webviewLauncher.fsLib.attributes(path) and 'exists' or 'not found') 25 | end 26 | -------------------------------------------------------------------------------- /webview-init.js: -------------------------------------------------------------------------------- 1 | /* 2 | This script triggers the webview launcher initialisation sequence. 3 | By default the initialisation is done on first load, this script provides support for navigation and page reload. 4 | Lua code in HTML must support multiple executions as well as onWebviewInitalized handler. 5 | */ 6 | (function() { 7 | var timeoutDelay = 1; 8 | 9 | function handleLoad() { 10 | if ((typeof window.external !== 'object') || (typeof window.external.invoke !== 'function')) { 11 | if (timeoutDelay > 30000) { 12 | throw 'window.external is not available'; 13 | } 14 | setTimeout(handleLoad, timeoutDelay); // Let external.invoke be registered 15 | timeoutDelay = timeoutDelay * 2; 16 | } else if (typeof window.webview !== 'object') { 17 | window.external.invoke(':init:'); 18 | } 19 | } 20 | 21 | if (document.readyState === 'complete') { 22 | handleLoad(); 23 | } else { 24 | window.addEventListener('load', handleLoad); 25 | } 26 | })(); 27 | -------------------------------------------------------------------------------- /webview-launcher.lua: -------------------------------------------------------------------------------- 1 | local webviewLib = require('webview') 2 | 3 | -- This module allows to launch a web page that could executes custom Lua code. 4 | 5 | -- The webview library may change locale to native and thus mislead the JSON libraries. 6 | if os.setlocale() == 'C' then 7 | -- Default locale is 'C' at startup, set native locale 8 | os.setlocale('') 9 | end 10 | 11 | -- Load JSON module 12 | local status, jsonLib = pcall(require, 'cjson') 13 | if not status then 14 | status, jsonLib = pcall(require, 'dkjson') 15 | if not status then 16 | -- provide a basic JSON implementation suitable for basic types 17 | local escapeMap = { ['\b'] = '\\b', ['\f'] = '\\f', ['\n'] = '\\n', ['\r'] = '\\r', ['\t'] = '\\t', ['"'] = '\\"', ['\\'] = '\\\\', ['/'] = '\\/', } 18 | local revertMap = {}; for c, s in pairs(escapeMap) do revertMap[s] = c; end 19 | jsonLib = { 20 | null = {}, 21 | decode = function(value) 22 | if string.sub(value, 1, 1) == '"' and string.sub(value, -1, -1) == '"' then 23 | return string.gsub(string.gsub(string.sub(value, 2, -2), '\\u(%x%x%x%x)', function(s) 24 | return string.char(tonumber(s, 16)) 25 | end), '\\.', function(s) 26 | return revertMap[s] or '' 27 | end) 28 | elseif string.match(value, '^%s*[%-%+]?%d[%d%.%s]*$') then 29 | return tonumber(value) 30 | elseif (value == 'true') or (value == 'false') then 31 | return value == 'true' 32 | elseif value == 'null' then 33 | return jsonLib.null 34 | end 35 | return nil 36 | end, 37 | encode = function(value) 38 | local valueType = type(value) 39 | if valueType == 'boolean' then 40 | return value and 'true' or 'false' 41 | elseif valueType == 'number' then 42 | return (string.gsub(tostring(value), ',', '.', 1)) 43 | elseif valueType == 'string' then 44 | return '"'..string.gsub(value, '[%c"/\\]', function(c) 45 | return escapeMap[c] or string.format('\\u%04X', string.byte(c)) 46 | end)..'"' 47 | elseif value == jsonLib.null then 48 | return 'null' 49 | end 50 | return 'undefined' 51 | end 52 | } 53 | end 54 | end 55 | 56 | -- OS file separator 57 | local fileSeparator = string.sub(package.config, 1, 1) or '/' 58 | 59 | -- Load file system module 60 | local fsLib 61 | status, fsLib = pcall(require, 'luv') 62 | if status then 63 | local uvLib = fsLib 64 | fsLib = { 65 | currentdir = uvLib.cwd, 66 | attributes = uvLib.fs_stat, 67 | } 68 | else 69 | status, fsLib = pcall(require, 'lfs') 70 | if not status then 71 | -- provide a basic file system implementation 72 | fsLib = { 73 | currentdir = function() 74 | local f = io.popen(fileSeparator == '\\' and 'cd' or 'pwd') 75 | if f then 76 | local d = f:read() 77 | f:close() 78 | return d 79 | end 80 | return '.' 81 | end, 82 | attributes = function(p) 83 | local f = io.open(p) 84 | if f then 85 | f:close() 86 | return {} 87 | end 88 | return nil 89 | end, 90 | } 91 | end 92 | end 93 | 94 | -- Lua code injected to provide default local variables 95 | local localContextLua = 'local evalJs, callJs, expose = context.evalJs, context.callJs, context.expose; ' 96 | 97 | local function exposeFunctionJs(name, remove) 98 | local nameJs = "'"..name.."'" 99 | if remove then 100 | return 'delete webview['..nameJs..'];\n'; 101 | end 102 | return 'webview['..nameJs..'] = function(value, callback) {'.. 103 | 'webview.invokeLua('..nameJs..', value, callback);'.. 104 | '};\n' 105 | end 106 | 107 | -- Initializes the web view and provides a global JavaScript webview object 108 | local function initializeJs(webview, functionMap, options) 109 | local jsContent = [[ 110 | if (typeof window.webview === 'object') { 111 | console.log('webview object already exists'); 112 | } else { 113 | console.log('initialize webview object'); 114 | var webview = {}; 115 | window.webview = webview; 116 | var refs = {}; 117 | var callbackToRef = function(callback, delay) { 118 | if (typeof callback === 'function') { 119 | var ref; 120 | var id = setTimeout(function() { 121 | var cb = refs[ref]; 122 | if (cb) { 123 | delete refs[ref]; 124 | cb('timeout'); 125 | } 126 | }, delay); 127 | ref = id.toString(36); 128 | refs[ref] = callback; 129 | return ref; 130 | } 131 | return null; 132 | }; 133 | webview.callbackRef = function(ref, reason, result) { 134 | var id = parseInt(ref, 36); 135 | clearTimeout(id); 136 | var callback = refs[ref]; 137 | if (callback) { 138 | delete refs[ref]; 139 | callback(reason, result); 140 | } 141 | }; 142 | webview.invokeLua = function(name, value, callback, delay) { 143 | var kind = ':', data = ''; 144 | if (typeof value === 'string') { 145 | data = value; 146 | } else if (typeof value === 'function') { 147 | delay = callback; 148 | callback = value; 149 | } else if (value !== undefined) { 150 | kind = ';'; 151 | data = JSON.stringify(value); 152 | } 153 | var message; 154 | var ref = callbackToRef(callback, delay || 30000); 155 | if (ref) { 156 | message = '#' + name + kind + ref + ';' + data; 157 | } else { 158 | message = name + kind + data; 159 | } 160 | window.external.invoke(message); 161 | }; 162 | ]] 163 | if options and options.captureError then 164 | jsContent = jsContent..[[ 165 | window.onerror = function(message, source, lineno, colno, error) { 166 | var message = '' + message; // Just "Script error." when occurs in different origin 167 | if (source) { 168 | message += '\n source: ' + source + ', line: ' + lineno + ', col: ' + colno; 169 | } 170 | if (error) { 171 | message += '\n error: ' + error; 172 | } 173 | window.external.invoke(':error:' + message); 174 | return true; 175 | }; 176 | ]] 177 | end 178 | if options and options.useJsTitle then 179 | jsContent = jsContent..[[ 180 | if (document.title) { 181 | window.external.invoke('title:' + document.title); 182 | } 183 | ]] 184 | end 185 | if functionMap then 186 | for name in pairs(functionMap) do 187 | jsContent = jsContent..exposeFunctionJs(name) 188 | end 189 | end 190 | if options and options.luaScript then 191 | jsContent = jsContent..[[ 192 | var evalLuaScripts = function() { 193 | var scripts = document.getElementsByTagName('script'); 194 | for (var i = 0; i < scripts.length; i++) { 195 | var script = scripts[i]; 196 | if (script.getAttribute('type') === 'text/lua') { 197 | var src = script.getAttribute('src'); 198 | if (src) { 199 | window.external.invoke('evalLuaSrc:' + src); 200 | } else { 201 | window.external.invoke('evalLua:' + script.text); 202 | } 203 | } 204 | } 205 | }; 206 | if (document.readyState !== 'loading') { 207 | evalLuaScripts(); 208 | } else { 209 | document.addEventListener('DOMContentLoaded', evalLuaScripts); 210 | } 211 | ]] 212 | end 213 | jsContent = jsContent..[[ 214 | var completeInitialization = function() { 215 | if (typeof window.onWebviewInitalized === 'function') { 216 | webview.evalJs("window.onWebviewInitalized(window.webview);"); 217 | } 218 | }; 219 | if (document.readyState === 'complete') { 220 | completeInitialization(); 221 | } else { 222 | window.addEventListener('load', completeInitialization); 223 | } 224 | } 225 | ]] 226 | webviewLib.eval(webview, jsContent, true) 227 | end 228 | 229 | -- Prints error message to the error stream 230 | local function printError(value) 231 | io.stderr:write('WebView Launcher - '..tostring(value)..'\n') 232 | end 233 | 234 | local function callbackJs(webview, ref, reason, result) 235 | webviewLib.eval(webview, 'if (webview) {'.. 236 | 'webview.callbackRef("'..ref..'", '..jsonLib.encode(reason)..', '..jsonLib.encode(result)..');'.. 237 | '}', true) 238 | end 239 | 240 | local function handleCallback(callback, reason, result) 241 | if callback then 242 | callback(reason, result) 243 | elseif reason then 244 | printError(reason) 245 | end 246 | end 247 | 248 | -- Executes the specified Lua code 249 | local function evalLua(value, callback, context, webview) 250 | local f, err = load('local callback, context, webview = ...; '..localContextLua..value) 251 | if f then 252 | f(callback, context, webview) 253 | else 254 | handleCallback(callback, 'Error '..tostring(err)..' while loading '..tostring(value)) 255 | end 256 | end 257 | 258 | -- Toggles the web view full screen on/off 259 | local function fullscreen(value, callback, _, webview) 260 | webviewLib.fullscreen(webview, value == 'true') 261 | handleCallback(callback) 262 | end 263 | 264 | -- Sets the web view title 265 | local function setTitle(value, callback, _, webview) 266 | webviewLib.title(webview, value) 267 | handleCallback(callback) 268 | end 269 | 270 | -- Terminates the web view 271 | local function terminate(_, callback, _, webview) 272 | webviewLib.terminate(webview, true) 273 | handleCallback(callback) 274 | end 275 | 276 | -- Executes the specified Lua file relative to the URL 277 | local function evalLuaSrc(value, callback, context, webview) 278 | local content 279 | if context.luaSrcPath then 280 | local path = context.luaSrcPath..fileSeparator..string.gsub(value, '[/\\]+', fileSeparator) 281 | local file = io.open(path) 282 | if file then 283 | content = file:read('a') 284 | file:close() 285 | end 286 | end 287 | if content then 288 | evalLua(content, callback, context, webview) 289 | else 290 | handleCallback(callback, 'Cannot load Lua file from src "'..tostring(value)..'"') 291 | end 292 | end 293 | 294 | -- Evaluates the specified JS code 295 | local function evalJs(value, callback, _, webview) 296 | webviewLib.eval(webview, value, true) 297 | handleCallback(callback) 298 | end 299 | 300 | -- Calls the specified JS function name, 301 | -- the arguments are JSON encoded then passed to the JS function 302 | local function callJs(webview, functionName, ...) 303 | local argCount = select('#', ...) 304 | local args = {...} 305 | for i = 1, argCount do 306 | args[i] = jsonLib.encode(args[i]) 307 | end 308 | local jsString = functionName..'('..table.concat(args, ',')..')' 309 | webviewLib.eval(webview, jsString, true) 310 | end 311 | 312 | -- Creates the webview context and sets the callback and default functions 313 | local function createContext(webview, options) 314 | local initialized = false 315 | 316 | -- Named requests callable from JS using window.external.invoke('name:value') 317 | -- Custom request can be registered using window.external.invoke('+name:Lua code') 318 | -- The Lua code has access to the string value, the evalJs() and callJs() functions 319 | local functionMap = { 320 | fullscreen = fullscreen, 321 | title = setTitle, 322 | terminate = terminate, 323 | evalLua = evalLua, 324 | evalLuaSrc = evalLuaSrc, 325 | evalJs = evalJs, 326 | } 327 | 328 | -- Defines the context that will be shared across Lua calls 329 | local context = { 330 | expose = function(name, fn) 331 | functionMap[name] = fn 332 | if initialized then 333 | webviewLib.eval(webview, exposeFunctionJs(name, not fn), true) 334 | end 335 | end, 336 | exposeAll = function(fnMap) 337 | local jsContent = '' 338 | for name, fn in pairs(fnMap) do 339 | functionMap[name] = fn 340 | jsContent = jsContent..exposeFunctionJs(name, not fn) 341 | end 342 | if initialized then 343 | webviewLib.eval(webview, jsContent, true) 344 | end 345 | 346 | end, 347 | -- Setup a Lua function to evaluates JS code 348 | evalJs = function(value) 349 | webviewLib.eval(webview, value, true) 350 | end, 351 | callJs = function(functionName, ...) 352 | callJs(webview, functionName, ...) 353 | end, 354 | callbackJs = function(ref, reason, result) 355 | callbackJs(webview, ref, reason, result) 356 | end, 357 | terminate = function() 358 | webviewLib.terminate(webview, true) 359 | end, 360 | } 361 | 362 | if options and type(options.expose) == 'table' then 363 | context.exposeAll(options.expose) 364 | end 365 | 366 | if options and type(options.context) == 'table' then 367 | for name, value in pairs(options.context) do 368 | context[name] = value 369 | end 370 | end 371 | 372 | -- Creates the web view callback that handles the JS requests coming from window.external.invoke() 373 | local handler = function(request) 374 | local flag, name, kind, value = string.match(request, '^(%A?)(%a%w*)([:;])(.*)$') 375 | if name then 376 | if flag == '' or flag == '#' then 377 | -- Look for the specified function 378 | local fn = functionMap[name] 379 | local callback, cbRef 380 | if fn then 381 | if flag == '#' then 382 | local ref, val = string.match(value, '^(%w+);(.*)$') 383 | if ref and val then 384 | value = val 385 | cbRef = ref 386 | callback = function(reason, result) 387 | callbackJs(webview, ref, reason, result) 388 | end 389 | else 390 | printError('Invalid reference request '..request) 391 | return 392 | end 393 | end 394 | local s, r 395 | if kind == ';' then 396 | s, r = pcall(jsonLib.decode, value) 397 | if s then 398 | value = r 399 | else 400 | handleCallback(callback, 'Fail to parse '..name..' JSON value "'..tostring(value)..'" due to '..tostring(r)) 401 | return 402 | end 403 | end 404 | s, r = pcall(fn, value, callback, context, webview, cbRef) 405 | if not s then 406 | handleCallback(callback, 'Fail to execute '..name..' due to '..tostring(r)) 407 | end 408 | else 409 | printError('Unknown function '..name) 410 | end 411 | elseif flag == '-' then 412 | context.expose(name) 413 | elseif flag == '+' then 414 | -- Registering the new function using the specified Lua code 415 | local injected = 'local value, callback, context, webview = ...; ' 416 | local fn, err = load(injected..localContextLua..value) 417 | if fn then 418 | context.expose(name, fn) 419 | else 420 | printError('Error '..tostring(err)..' while loading '..tostring(value)) 421 | end 422 | elseif name == 'error' and flag == ':' then 423 | printError(value) 424 | elseif name == 'init' and flag == ':' then 425 | initialized = true 426 | initializeJs(webview, functionMap, options) 427 | else 428 | printError('Invalid flag '..flag..' for name '..name) 429 | end 430 | else 431 | printError('Invalid request #'..tostring(request and #request)..' "'..tostring(request)..'"') 432 | end 433 | end 434 | 435 | if options and options.initialize then 436 | initialized = true 437 | initializeJs(webview, functionMap, options) 438 | end 439 | 440 | return handler 441 | end 442 | 443 | local function escapeUrl(value) 444 | return string.gsub(value, "[ %c!#$%%&'()*+,/:;=?@%[%]]", function(c) 445 | return string.format('%%%02X', string.byte(c)) 446 | end) 447 | end 448 | 449 | local function parseArgs(args) 450 | -- Default web content 451 | local url = 'data:text/html,'..escapeUrl([[ 452 | 453 | 454 | Welcome WebView 455 | 456 | 459 | 460 |

Welcome !

461 |

You could specify an HTML file to launch as a command line argument.

462 | 463 | 464 | 465 | ]]) 466 | 467 | local title 468 | local width = 800 469 | local height = 600 470 | local resizable = true 471 | local debug = false 472 | local eventMode = nil 473 | local initialize = true 474 | local luaScript = true 475 | local captureError = true 476 | local luaPath = false 477 | 478 | -- Parse command line arguments 479 | args = args or arg or {} 480 | local ctxArgs = {} 481 | local luaSrcPath = nil 482 | local urlArg 483 | 484 | for i = 1, #args do 485 | local name, value = string.match(args[i], '^%-%-wv%-([^=]+)=?(.*)$') 486 | if not name then 487 | if urlArg then 488 | table.insert(ctxArgs, args[i]) 489 | else 490 | urlArg = args[i] 491 | end 492 | elseif name == 'size' and value then 493 | local w, h = string.match(value, '^(%d+)[xX-/](%d+)$') 494 | width = tonumber(w) 495 | height = tonumber(h) 496 | elseif name == 'title' and value then 497 | title = value 498 | elseif name == 'width' and tonumber(value) then 499 | width = tonumber(value) 500 | elseif name == 'height' and tonumber(value) then 501 | height = tonumber(value) 502 | elseif name == 'resizable' then 503 | resizable = value ~= 'false' 504 | elseif name == 'debug' then 505 | debug = value == 'true' 506 | elseif name == 'event' and (value == 'open' or value == 'main' or value == 'thread' or value == 'http') then 507 | eventMode = value 508 | elseif name == 'initialize' then 509 | initialize = value ~= 'false' 510 | elseif name == 'script' then 511 | luaScript = value ~= 'false' 512 | elseif name == 'captureError' then 513 | captureError = value ~= 'false' 514 | elseif name == 'luaPath' then 515 | luaPath = value == 'true' 516 | else 517 | print('Invalid argument', args[i]) 518 | os.exit(22) 519 | end 520 | end 521 | 522 | -- Process URL argument 523 | if urlArg and urlArg ~= '' then 524 | if urlArg == '-h' or urlArg == '/?' or urlArg == '--help' then 525 | print('Launchs a WebView using the specified URL') 526 | print('Optional arguments: url --wv-title= --wv-width='..tostring(width)..' --wv-height='..tostring(height)..' --wv-resizable='..tostring(resizable)) 527 | os.exit(0) 528 | end 529 | local protocol = string.match(urlArg, '^([^:]+):.+$') 530 | if protocol == 'http' or protocol == 'https' or protocol == 'file' or protocol == 'data' then 531 | url = urlArg 532 | else 533 | local filePath 534 | if string.match(urlArg, '^.:\\.+$') or string.match(urlArg, '^/.+$') then 535 | filePath = urlArg 536 | elseif fsLib then 537 | filePath = fsLib.currentdir()..fileSeparator..urlArg 538 | end 539 | if not filePath then 540 | print('Invalid URL, to launch a file please use an absolute path') 541 | os.exit(22) 542 | end 543 | luaSrcPath = string.match(filePath, '^(.*)[/\\][^/\\]+$') 544 | url = 'file://'..filePath 545 | end 546 | end 547 | 548 | if luaSrcPath and luaPath then 549 | package.path = package.path..';'..luaSrcPath..'/?.lua' 550 | end 551 | 552 | return url, { 553 | title = title or 'Web View', 554 | width = width, 555 | height = height, 556 | resizable = resizable, 557 | debug = debug 558 | }, { 559 | eventMode = eventMode, 560 | initialize = initialize, 561 | useJsTitle = not title, 562 | luaScript = luaScript, 563 | luaPath = luaPath, 564 | captureError = captureError, 565 | context = { 566 | args = ctxArgs, 567 | luaSrcPath = luaSrcPath, 568 | }, 569 | } 570 | end 571 | 572 | local function createContextAndPath(wv, opts) 573 | if opts.luaPath and opts.context and opts.context.luaSrcPath then 574 | package.path = package.path..';'..opts.context.luaSrcPath..'/?.lua' 575 | end 576 | return createContext(wv, opts) 577 | end 578 | 579 | local function launchWithOptions(url, wvOptions, options) 580 | wvOptions = wvOptions or {} 581 | options = options or wvOptions 582 | if options.eventMode then 583 | local event = require('jls.lang.event') 584 | local WebView = require('jls.util.WebView') 585 | if options.eventMode == 'thread' then 586 | --webviewLib.open('data:text/html,Close to start', 'Close to start', 320, 200) 587 | WebView.open(url, wvOptions):next(function(webview) 588 | local callback = createContextAndPath(webview._webview, options) 589 | webview:callback(callback) 590 | end) 591 | elseif options.eventMode == 'http' then 592 | if not options.context.luaSrcPath then 593 | error('Please specify a file path as URL') 594 | end 595 | local FileHttpHandler = require('jls.net.http.handler.FileHttpHandler') 596 | options.callback = true 597 | options.contexts = { 598 | ['/(.*)'] = FileHttpHandler:new(options.context.luaSrcPath or '.') 599 | } 600 | local filename = string.match(url, '^.*[/\\]([^/\\]+)$') 601 | WebView.open('http://localhost:0/'..filename, options):next(function(webview) 602 | local callback = createContextAndPath(webview._webview, options) 603 | webview:callback(callback) 604 | end) 605 | else 606 | local open = options.eventMode == 'main' and WebView.openSync or WebView.open 607 | wvOptions.fn = function(webview, data) 608 | local webviewLauncher = require('webview-launcher') 609 | local opts = webviewLauncher.jsonLib.decode(data) 610 | local callback = webviewLauncher.createContextAndPath(webview._webview, opts) 611 | webview:callback(callback) 612 | end 613 | wvOptions.data = jsonLib.encode(options) 614 | open(url, wvOptions) 615 | end 616 | event:loop() 617 | else 618 | local webview = webviewLib.new(url, wvOptions.title, wvOptions.width, wvOptions.height, wvOptions.resizable, wvOptions.debug) 619 | local callback = createContext(webview, options) 620 | webviewLib.callback(webview, callback) 621 | webviewLib.loop(webview) 622 | end 623 | end 624 | 625 | return { 626 | initializeJs = initializeJs, 627 | createContext = createContext, 628 | createContextAndPath = createContextAndPath, 629 | escapeUrl = escapeUrl, 630 | launchFromArgs = function(args, ...) 631 | if type(args) == 'string' then 632 | args = {args, ...} 633 | elseif type(args) ~= 'table' then 634 | args = arg 635 | end 636 | launchWithOptions(parseArgs(args)) 637 | end, 638 | launchWithOptions = launchWithOptions, 639 | jsonLib = jsonLib, 640 | fsLib = fsLib, 641 | } 642 | -------------------------------------------------------------------------------- /webview.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define WEBVIEW_IMPLEMENTATION 5 | 6 | // wget https://raw.githubusercontent.com/zserge/webview/master/webview.h 7 | #include "webview.h" 8 | 9 | /* 10 | ******************************************************************************** 11 | * Lua 5.1 compatibility 12 | ******************************************************************************** 13 | */ 14 | 15 | #if LUA_VERSION_NUM < 502 16 | // From Lua 5.3 lauxlib.c 17 | LUALIB_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { 18 | luaL_checkstack(L, nup, "too many upvalues"); 19 | for (; l->name != NULL; l++) { /* fill the table with given functions */ 20 | int i; 21 | for (i = 0; i < nup; i++) /* copy upvalues to the top */ 22 | lua_pushvalue(L, -nup); 23 | lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ 24 | lua_setfield(L, -(nup + 2), l->name); 25 | } 26 | lua_pop(L, nup); /* remove upvalues */ 27 | } 28 | LUALIB_API void *luaL_testudata (lua_State *L, int ud, const char *tname) { 29 | void *p = lua_touserdata(L, ud); 30 | if (p != NULL) { /* value is a userdata? */ 31 | if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ 32 | luaL_getmetatable(L, tname); /* get correct metatable */ 33 | if (!lua_rawequal(L, -1, -2)) /* not the same? */ 34 | p = NULL; /* value is a userdata with wrong metatable */ 35 | lua_pop(L, 2); /* remove both metatables */ 36 | return p; 37 | } 38 | } 39 | return NULL; /* value is not a userdata with a metatable */ 40 | } 41 | #endif 42 | 43 | /* 44 | ******************************************************************************** 45 | * Lua reference structure and functions 46 | ******************************************************************************** 47 | */ 48 | 49 | typedef struct LuaReferenceStruct { 50 | lua_State *state; 51 | int ref; 52 | } LuaReference; 53 | 54 | static void initLuaReference(LuaReference *r) { 55 | if (r != NULL) { 56 | r->state = NULL; 57 | r->ref = LUA_NOREF; 58 | } 59 | } 60 | 61 | static void registerLuaReference(LuaReference *r, lua_State *l) { 62 | if ((r != NULL) && (l != NULL)) { 63 | if ((r->state != NULL) && (r->ref != LUA_NOREF)) { 64 | luaL_unref(r->state, LUA_REGISTRYINDEX, r->ref); 65 | } 66 | r->state = l; 67 | r->ref = luaL_ref(l, LUA_REGISTRYINDEX); 68 | } 69 | } 70 | 71 | static void unregisterLuaReference(LuaReference *r, lua_State *l) { 72 | if ((r != NULL) && (r->state != NULL) && (r->state == l) && (r->ref != LUA_NOREF)) { 73 | luaL_unref(r->state, LUA_REGISTRYINDEX, r->ref); 74 | r->state = NULL; 75 | r->ref = LUA_NOREF; 76 | } 77 | } 78 | 79 | /* 80 | ******************************************************************************** 81 | * Lua webview structure 82 | ******************************************************************************** 83 | */ 84 | 85 | typedef struct LuaWebViewStruct { 86 | LuaReference cbFn; 87 | lua_State *initState; 88 | struct webview webview; 89 | } LuaWebView; 90 | 91 | #define WEBVIEW_PTR(_cp) \ 92 | ((LuaWebView *) ((char *) (_cp) - offsetof(LuaWebView, webview))) 93 | 94 | /* 95 | ******************************************************************************** 96 | * Lua webview functions 97 | ******************************************************************************** 98 | */ 99 | 100 | static int lua_webview_open(lua_State *l) { 101 | const char *url = luaL_checkstring(l, 1); 102 | const char *title = luaL_optstring(l, 2, "Lua Web View"); 103 | lua_Integer width = luaL_optinteger(l, 3, 800); 104 | lua_Integer height = luaL_optinteger(l, 4, 600); 105 | lua_Integer resizable = lua_toboolean(l, 5); 106 | webview_run(title, url, width, height, resizable); 107 | return 0; 108 | } 109 | 110 | static LuaWebView *lua_webview_asudata(lua_State *l, int ud) { 111 | if (lua_islightuserdata(l, ud)) { 112 | return lua_touserdata(l, ud); 113 | } 114 | return (LuaWebView *)luaL_checkudata(l, ud, "webview"); 115 | } 116 | 117 | static LuaWebView * lua_webview_newuserdata(lua_State *l) { 118 | size_t urlLen; 119 | size_t titleLen; 120 | const char *url = luaL_optlstring(l, 1, "", &urlLen); 121 | const char *title = luaL_optlstring(l, 2, "Lua Web View", &titleLen); 122 | lua_Integer width = luaL_optinteger(l, 3, 800); 123 | lua_Integer height = luaL_optinteger(l, 4, 600); 124 | lua_Integer resizable = lua_toboolean(l, 5); 125 | lua_Integer debug = lua_toboolean(l, 6); 126 | LuaWebView *lwv = (LuaWebView *)lua_newuserdata(l, sizeof(LuaWebView) + titleLen + 1 + urlLen + 1); 127 | const char *titleCopy = ((char *)lwv) + sizeof(LuaWebView); 128 | const char *urlCopy = ((char *)lwv) + sizeof(LuaWebView) + titleLen + 1; 129 | memset(lwv, 0, sizeof(LuaWebView)); 130 | memcpy(titleCopy, title, titleLen + 1); 131 | memcpy(urlCopy, url, urlLen + 1); 132 | lwv->initState = NULL; 133 | lwv->webview.title = titleCopy; 134 | lwv->webview.url = urlCopy; 135 | lwv->webview.width = width; 136 | lwv->webview.height = height; 137 | lwv->webview.resizable = resizable; 138 | lwv->webview.debug = debug; 139 | initLuaReference(&lwv->cbFn); 140 | return lwv; 141 | } 142 | 143 | static int lua_webview_new(lua_State *l) { 144 | LuaWebView *lwv = lua_webview_newuserdata(l); 145 | int r = webview_init(&lwv->webview); 146 | if (r != 0) { 147 | return 0; 148 | } 149 | lwv->initState = l; 150 | luaL_getmetatable(l, "webview"); 151 | lua_setmetatable(l, -2); 152 | return 1; 153 | } 154 | 155 | static int lua_webview_allocate(lua_State *l) { 156 | (void) lua_webview_newuserdata(l); 157 | luaL_getmetatable(l, "webview"); 158 | lua_setmetatable(l, -2); 159 | return 1; 160 | } 161 | 162 | static int lua_webview_init(lua_State *l) { 163 | LuaWebView *lwv = (LuaWebView *)lua_webview_asudata(l, 1); 164 | int initialized = 0; 165 | if (lwv != NULL && lwv->initState == NULL) { 166 | int r = webview_init(&lwv->webview); 167 | if (r == 0) { 168 | initialized = 1; 169 | lwv->initState = l; 170 | } 171 | } 172 | lua_pushboolean(l, initialized); 173 | return 1; 174 | } 175 | 176 | static int lua_webview_initialized(lua_State *l) { 177 | LuaWebView *lwv = (LuaWebView *)lua_webview_asudata(l, 1); 178 | lua_pushboolean(l, lwv != NULL && lwv->initState != NULL); 179 | return 1; 180 | } 181 | 182 | static const char *const lua_webview_loop_modes[] = { 183 | "default", "once", "nowait", NULL 184 | }; 185 | 186 | static int lua_webview_loop(lua_State *l) { 187 | LuaWebView *lwv = (LuaWebView *)lua_webview_asudata(l, 1); 188 | int mode = luaL_checkoption(l, 2, "default", lua_webview_loop_modes); 189 | int r = 0; 190 | if (l == lwv->initState) { 191 | do { 192 | r = webview_loop(&lwv->webview, mode != 2); 193 | } while ((mode == 0) && (r == 0)); 194 | } else { 195 | webview_debug("loop and init states differs"); 196 | } 197 | lua_pushboolean(l, r); 198 | return 1; 199 | } 200 | 201 | static void invoke_callback(struct webview *w, const char *arg) { 202 | if ((w != NULL) && (arg != NULL)) { 203 | LuaWebView *lwv = WEBVIEW_PTR(w); 204 | lua_State *l = lwv->cbFn.state; 205 | int ref = lwv->cbFn.ref; 206 | if ((l != NULL) && (ref != LUA_NOREF)) { 207 | if (l == lwv->initState) { 208 | lua_rawgeti(l, LUA_REGISTRYINDEX, ref); 209 | lua_pushstring(l, arg); 210 | lua_pcall(l, 1, 0, 0); 211 | } else { 212 | webview_debug("callback and init states differs"); 213 | } 214 | } 215 | } 216 | } 217 | 218 | static int lua_webview_callback(lua_State *l) { 219 | LuaWebView *lwv = (LuaWebView *)lua_webview_asudata(l, 1); 220 | if (lua_isfunction(l, 2)) { 221 | lua_pushvalue(l, 2); 222 | registerLuaReference(&lwv->cbFn, l); 223 | lwv->webview.external_invoke_cb = &invoke_callback; 224 | } else { 225 | unregisterLuaReference(&lwv->cbFn, l); 226 | lwv->webview.external_invoke_cb = NULL; 227 | } 228 | return 0; 229 | } 230 | 231 | static void dispatched_eval(struct webview *w, void *arg) { 232 | webview_eval(w, (const char *) arg); 233 | } 234 | 235 | static int lua_webview_eval(lua_State *l) { 236 | LuaWebView *lwv = (LuaWebView *)lua_webview_asudata(l, 1); 237 | const char *js = luaL_checkstring(l, 2); 238 | int dispatch = lua_toboolean(l, 3); 239 | if (dispatch) { 240 | // do we need to register the js code to dispatch? 241 | webview_dispatch(&lwv->webview, dispatched_eval, (void *)js); 242 | return 0; 243 | } 244 | int r = webview_eval(&lwv->webview, js); 245 | lua_pushboolean(l, r); 246 | return 1; 247 | } 248 | 249 | static int lua_webview_title(lua_State *l) { 250 | LuaWebView *lwv = (LuaWebView *)lua_webview_asudata(l, 1); 251 | const char *title = luaL_checkstring(l, 2); 252 | webview_set_title(&lwv->webview, title); 253 | return 0; 254 | } 255 | 256 | static int lua_webview_fullscreen(lua_State *l) { 257 | LuaWebView *lwv = (LuaWebView *)lua_webview_asudata(l, 1); 258 | int fullscreen = lua_toboolean(l, 2); 259 | webview_set_fullscreen(&lwv->webview, fullscreen); 260 | return 0; 261 | } 262 | 263 | static void dispatched_terminate(struct webview *w, void *arg) { 264 | webview_terminate(w); 265 | } 266 | 267 | static int lua_webview_terminate(lua_State *l) { 268 | LuaWebView *lwv = (LuaWebView *)lua_webview_asudata(l, 1); 269 | int dispatch = lua_toboolean(l, 2); 270 | if (dispatch) { 271 | webview_dispatch(&lwv->webview, dispatched_terminate, NULL); 272 | return 0; 273 | } 274 | webview_terminate(&lwv->webview); 275 | return 0; 276 | } 277 | 278 | static void clean_webview(lua_State *l, LuaWebView *lwv) { 279 | if (lwv != NULL) { 280 | unregisterLuaReference(&lwv->cbFn, l); 281 | if (lwv->initState == l) { 282 | //webview_debug("clean_webview()"); 283 | webview_exit(&lwv->webview); 284 | lwv->initState = NULL; 285 | } 286 | } 287 | } 288 | 289 | static int lua_webview_clean(lua_State *l) { 290 | LuaWebView *lwv = (LuaWebView *)lua_webview_asudata(l, 1); 291 | clean_webview(l, lwv); 292 | return 0; 293 | } 294 | 295 | static int lua_webview_lighten(lua_State *l) { 296 | LuaWebView *lwv = (LuaWebView *)luaL_checkudata(l, 1, "webview"); 297 | lua_pushlightuserdata(l, lwv); 298 | return 1; 299 | } 300 | 301 | static int lua_webview_asstring(lua_State *l) { 302 | LuaWebView *lwv = (LuaWebView *)luaL_checkudata(l, 1, "webview"); 303 | lua_pushlstring(l, (const char *) &lwv, sizeof(void *)); 304 | return 1; 305 | } 306 | 307 | static int lua_webview_fromstring(lua_State *l) { 308 | size_t len = 0; 309 | const char *udata = luaL_optlstring(l, 1, NULL, &len); 310 | if (len == sizeof(void *)) { 311 | lua_pushlightuserdata(l, *((void **) udata)); 312 | return 1; 313 | } 314 | return 0; 315 | } 316 | 317 | static int lua_webview_gc(lua_State *l) { 318 | LuaWebView *lwv = (LuaWebView *)luaL_testudata(l, 1, "webview"); 319 | clean_webview(l, lwv); 320 | return 0; 321 | } 322 | 323 | #if defined(WEBVIEW2_MEMORY_MODULE) 324 | static int lua_webview_loadWebView2Dll(lua_State *l) { 325 | void *data; 326 | size_t len = 0; 327 | int status = 0; 328 | data = lua_tolstring(l, 1, &len); 329 | if (data != NULL && len > 0 && WebView2Load(data, len)) { 330 | status = 1; 331 | } 332 | lua_pushboolean(l, status); 333 | return 1; 334 | } 335 | #endif 336 | 337 | LUALIB_API int luaopen_webview(lua_State *l) { 338 | luaL_newmetatable(l, "webview"); 339 | lua_pushstring(l, "__gc"); 340 | lua_pushcfunction(l, lua_webview_gc); 341 | lua_settable(l, -3); 342 | 343 | luaL_Reg reg[] = { 344 | { "open", lua_webview_open }, 345 | { "new", lua_webview_new }, 346 | { "allocate", lua_webview_allocate }, 347 | { "clean", lua_webview_clean }, 348 | { "init", lua_webview_init }, 349 | { "initialized", lua_webview_initialized }, 350 | { "loop", lua_webview_loop }, 351 | { "eval", lua_webview_eval }, 352 | { "callback", lua_webview_callback }, 353 | { "terminate", lua_webview_terminate }, 354 | { "fullscreen", lua_webview_fullscreen }, 355 | { "title", lua_webview_title }, 356 | { "lighten", lua_webview_lighten }, 357 | { "asstring", lua_webview_asstring }, 358 | { "fromstring", lua_webview_fromstring }, 359 | #if defined(WEBVIEW2_MEMORY_MODULE) 360 | { "loadWebView2Dll", lua_webview_loadWebView2Dll }, 361 | #endif 362 | { NULL, NULL } 363 | }; 364 | lua_newtable(l); 365 | luaL_setfuncs(l, reg, 0); 366 | lua_pushliteral(l, "Lua webview"); 367 | lua_setfield(l, -2, "_NAME"); 368 | lua_pushliteral(l, "1.0"); 369 | lua_setfield(l, -2, "_VERSION"); 370 | return 1; 371 | } 372 | --------------------------------------------------------------------------------