├── .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 | Say Hello
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: '
',
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 | ' ' +
194 | ' ',
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,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Xqz8qBcJkED0BGIbcoxe1QtDor4qT80zFpNh01flYWg2e1Fo2C6Me5NnSQLFToAeWpFfITFmxbrCUdCJMeMKxyK/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 | C
30 | ←
31 | /
32 |
33 |
34 | 7
35 | 8
36 | 9
37 | *
38 |
39 |
40 | 4
41 | 5
42 | 6
43 | -
44 |
45 |
46 | 1
47 | 2
48 | 3
49 | +
50 |
51 |
52 |
53 | 0
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 |
Open
40 |
Multiple
41 |
Save
42 |
Directory
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 | Clear
10 | User Agent
11 | Show Hello
12 | Change Title
13 | Say Hello
14 | Show Lua Date
15 | Throw Error
16 | Timer
17 |
18 | Print Lua:
19 | Hello
20 | Args
21 | Locale
22 | Date
23 | Seconds
24 | Fullscreen
25 | Table
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 |
56 |
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 |
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 | Change Title
8 | Print Date
9 | Show Date
10 | User Agent
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 | Close
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 |
--------------------------------------------------------------------------------