forked from cory/tildefriends
Compare commits
112 Commits
Author | SHA1 | Date | |
---|---|---|---|
72369ab745 | |||
b62a05f627 | |||
03eb8e7fae | |||
a5a00b6987 | |||
542162c78a | |||
8cfe0fb7d2 | |||
49c831cb62 | |||
2c79e03094 | |||
21e6cf10b6 | |||
dc655bb359 | |||
b9987580ee | |||
cb2dfc696d | |||
7f0643f9c0 | |||
14a4117aff | |||
55fb5dce1a | |||
923d6f9835 | |||
08b5ade8ec | |||
91f41c7497 | |||
fa06282ff9 | |||
48b967f5b6 | |||
f479165aac | |||
2f83ecc1ac | |||
01efc215fd | |||
00ba74a6c4 | |||
44b5ba1a9a | |||
843e172e56 | |||
a0df336abe | |||
0db4bb06c9 | |||
ad2b49b838 | |||
ab519342e8 | |||
1f0b9012e3 | |||
1ddad6be93 | |||
cf311003c0 | |||
64249976a8 | |||
6ecb3ccd08 | |||
4867bacb72 | |||
7d029d3d7a | |||
455befc18f | |||
6e57845512 | |||
1335a6e1e5 | |||
1eab44464c | |||
c3e2da3d51 | |||
1716f71c12 | |||
b52e99c958 | |||
86531bfd7e | |||
874a22325e | |||
2380b65853 | |||
f72e8cbd91 | |||
24e418344e | |||
2b7077ca70 | |||
10d438e723 | |||
331846ee2e | |||
dc0e58afc1 | |||
18e9252998 | |||
b2e3c04036 | |||
4fd155e68a | |||
59ac0b5f20 | |||
f4979c841a | |||
74eb74deb1 | |||
9e5e7b70d4 | |||
2384fc9fa9 | |||
576e58b1e3 | |||
a0af058f5e | |||
b40457d774 | |||
2353b43514 | |||
b11d5192c2 | |||
d38c58ce1d | |||
a0f390b7dc | |||
cb12799111 | |||
86fb5c53a1 | |||
29fc728509 | |||
0fb341f378 | |||
8a1a182479 | |||
49907bc8ee | |||
21d4a9b328 | |||
d5ede43a13 | |||
b73f5011cf | |||
32ebfa78cd | |||
39c942a205 | |||
ebc4533b10 | |||
4e5f9c86a8 | |||
d89a7a5556 | |||
8ab53f2da3 | |||
4c8eab2692 | |||
08989f54d9 | |||
c78753f3ff | |||
34a87d8b3b | |||
7516524d69 | |||
549d7ffec8 | |||
ccafc23d3c | |||
709b57d84f | |||
9ef909c9a1 | |||
d7c0ffaac4 | |||
e4cd5312f1 | |||
197fca6d3b | |||
04af1f0053 | |||
93d9b1ed93 | |||
2d73116bc0 | |||
f2f6d78790 | |||
797509fc11 | |||
6920504762 | |||
9d1476a760 | |||
c1890775dc | |||
72e5fe5b8f | |||
c81ec214e2 | |||
0dcc879eb1 | |||
4f3f4295ea | |||
d02f17a8cf | |||
2f6a92168e | |||
b6a3923b27 | |||
d556cbc835 | |||
f186806117 |
22
GNUmakefile
22
GNUmakefile
@ -3,9 +3,9 @@
|
||||
MAKEFLAGS += --warn-undefined-variables
|
||||
MAKEFLAGS += --no-builtin-rules
|
||||
|
||||
VERSION_CODE := 14
|
||||
VERSION_NUMBER := 0.0.14
|
||||
VERSION_NAME := Served on apple cider dressed winter greens.
|
||||
VERSION_CODE := 15
|
||||
VERSION_NUMBER := 0.0.15
|
||||
VERSION_NAME := Medium English breakfast tea.
|
||||
|
||||
PROJECT = tildefriends
|
||||
BUILD_DIR ?= out
|
||||
@ -159,7 +159,7 @@ $(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt
|
||||
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
|
||||
$(RELEASE_TARGETS): CFLAGS += -DNDEBUG
|
||||
$(NONANDROID_RELEASE_TARGETS): CFLAGS += -O3
|
||||
$(ANDROID_RELEASE_TARGETS): CFLAGS += -Os
|
||||
$(ANDROID_RELEASE_TARGETS): CFLAGS += -Oz
|
||||
$(WINDOWS_TARGETS): CC = x86_64-w64-mingw32-gcc-win32
|
||||
$(WINDOWS_TARGETS): AS = $(CC)
|
||||
$(WINDOWS_TARGETS): CFLAGS += \
|
||||
@ -213,6 +213,11 @@ debug: LDFLAGS += -fsanitize=address -fsanitize=undefined
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME_M),aarch64)
|
||||
debug: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
|
||||
debug: LDFLAGS += -fsanitize=address -fsanitize=undefined
|
||||
endif
|
||||
|
||||
get_objs = \
|
||||
$(foreach build_type,$(BUILD_TYPES),$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)))))) \
|
||||
$(foreach build_type,debug release,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_unix))))) \
|
||||
@ -500,7 +505,8 @@ $(filter $(BUILD_DIR)/win%,$(XOPT_OBJS)): CFLAGS += \
|
||||
-DHAVE_VASPRINTF \
|
||||
-Dvsnprintf=rpl_vsnprintf
|
||||
$(XOPT_OBJS): CFLAGS += \
|
||||
-Wno-implicit-const-int-float-conversion
|
||||
-Wno-implicit-const-int-float-conversion \
|
||||
-Wno-pointer-to-int-cast
|
||||
|
||||
QUICKJS_SOURCES := \
|
||||
deps/quickjs/cutils.c \
|
||||
@ -521,6 +527,12 @@ $(QUICKJS_OBJS): CFLAGS += \
|
||||
-Wno-unused-variable
|
||||
$(NONANDROID_TARGETS): CFLAGS += -DDUMP_LEAKS
|
||||
|
||||
ifeq ($(UNAME_S),Haiku)
|
||||
$(QUICKJS_OBJS): CFLAGS += "-Dmalloc_usable_size(x)=0"
|
||||
else ifeq ($(UNAME_S),OpenBSD)
|
||||
$(QUICKJS_OBJS): CFLAGS += "-Dmalloc_usable_size(x)=0"
|
||||
endif
|
||||
|
||||
LIBBACKTRACE_SOURCES := \
|
||||
deps/libbacktrace/atomic.c \
|
||||
deps/libbacktrace/backtrace.c \
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "💻"
|
||||
"emoji": "💻",
|
||||
"previous": "&33ngNe0YrH3JScss6krlCwddZcXl8C5szonp7DYy4qA=.sha256"
|
||||
}
|
@ -8,9 +8,42 @@ async function fetch_info(apps) {
|
||||
return result;
|
||||
}
|
||||
|
||||
async function fetch_shared_apps() {
|
||||
let messages = {};
|
||||
await ssb.sqlAsync(`
|
||||
SELECT messages.*
|
||||
FROM messages_fts('"application/tildefriends"')
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
ORDER BY timestamp
|
||||
`,
|
||||
[],
|
||||
function(row) {
|
||||
let content = JSON.parse(row.content);
|
||||
for (let mention of content.mentions) {
|
||||
if (mention?.type === 'application/tildefriends') {
|
||||
messages[JSON.stringify([row.author, mention.name])] = {
|
||||
message: row,
|
||||
blob: mention.link,
|
||||
name: mention.name,
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
let result = {};
|
||||
for (let app of Object.values(messages).sort((x, y) => y.message.timestamp - x.message.timestamp)) {
|
||||
let app_object = JSON.parse(utf8Decode(await ssb.blobGet(app.blob)));
|
||||
if (app_object) {
|
||||
app_object.blob_id = app.blob;
|
||||
result[app.name] = app_object;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
var apps = await fetch_info(await core.apps());
|
||||
var core_apps = await fetch_info(await core.apps('core'));
|
||||
let shared_apps = await fetch_shared_apps();
|
||||
var doc = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@ -40,6 +73,8 @@ async function main() {
|
||||
<body style="background: #888">
|
||||
<h1 id="apps_title">Apps</h1>
|
||||
<div id="apps" class="container"></div>
|
||||
<h1>Shared Apps</h1>
|
||||
<div id="shared_apps" class="container"></div>
|
||||
<h1>Core Apps</h1>
|
||||
<div id="core_apps" class="container"></div>
|
||||
</body>
|
||||
@ -50,18 +85,19 @@ async function main() {
|
||||
let div = list.appendChild(document.createElement('div'));
|
||||
div.classList.add('app');
|
||||
|
||||
let href = name ? '/~' + name + '/' + app + '/' : ('/' + apps[app].blob_id + '/');
|
||||
let icon_a = document.createElement('a');
|
||||
let icon = document.createElement('div');
|
||||
icon.appendChild(document.createTextNode(apps[app].emoji || '📦'));
|
||||
icon.style.fontSize = 'xxx-large';
|
||||
icon_a.appendChild(icon);
|
||||
icon_a.href = '/~' + name + '/' + app + '/';
|
||||
icon_a.href = href;
|
||||
icon_a.target = '_top';
|
||||
div.appendChild(icon_a);
|
||||
|
||||
let a = document.createElement('a');
|
||||
a.appendChild(document.createTextNode(app));
|
||||
a.href = '/~' + name + '/' + app + '/';
|
||||
a.href = href;
|
||||
a.target = '_top';
|
||||
div.appendChild(a);
|
||||
}
|
||||
@ -69,6 +105,7 @@ async function main() {
|
||||
document.getElementById('apps_title').innerText = "~${escape(core.user.credentials?.session?.name || 'guest')}'s Apps";
|
||||
populate_apps('apps', '${core.user.credentials?.session?.name}', ${JSON.stringify(apps)});
|
||||
populate_apps('core_apps', 'core', ${JSON.stringify(core_apps)});
|
||||
populate_apps('shared_apps', undefined, ${JSON.stringify(shared_apps)});
|
||||
</script>
|
||||
</html>`;
|
||||
app.setDocument(doc);
|
||||
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🛍"
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
async function get_apps() {
|
||||
let results = {};
|
||||
await ssb.sqlAsync(`
|
||||
SELECT messages.*
|
||||
FROM messages_fts('"application/tildefriends"')
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
ORDER BY timestamp
|
||||
`,
|
||||
[],
|
||||
function(row) {
|
||||
let content = JSON.parse(row.content);
|
||||
for (let mention of content.mentions) {
|
||||
if (mention?.type === 'application/tildefriends') {
|
||||
results[JSON.stringify([row.author, mention.name])] = {
|
||||
message: row,
|
||||
blob: mention.link,
|
||||
name: mention.name,
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
return Object.values(results).sort((x, y) => y.message.timestamp - x.message.timestamp);
|
||||
}
|
||||
|
||||
function render_app(app) {
|
||||
return `
|
||||
<div style="border: 2px solid white; display: inline-block; margin: 8px; padding: 8px">
|
||||
<a href="/~cory/ssb/#${app.message.author}">@</a>
|
||||
<a href="/~cory/ssb/#${app.message.id}">%</a>
|
||||
<a href="/${app.blob}/">${app.name}</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let apps = await get_apps();
|
||||
app.setDocument(`
|
||||
<html>
|
||||
<head>
|
||||
<base target="_top">
|
||||
<style>
|
||||
a:link { color: #bbf; }
|
||||
a:visited { color: #ddd; }
|
||||
a:hover { color: #ddf; }
|
||||
</style>
|
||||
</head>
|
||||
<body style="color: #fff">
|
||||
<h1>${apps.length} apps</h1>
|
||||
${apps.map(render_app).join('\n')}
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
}
|
||||
|
||||
main();
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🪵",
|
||||
"previous": "&YHBylHM7DlDDiGfuNj+g95Bf7NFxTZs/IKG14TbWnhs=.sha256"
|
||||
"previous": "&TIrBnpN3iz3O9L9MCCteAcVJZjA83EKdcfu4SCM76VE=.sha256"
|
||||
}
|
@ -8,7 +8,40 @@ function escapeAttribute(text) {
|
||||
return (text ?? '').replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''');
|
||||
}
|
||||
|
||||
function markdown(md) {
|
||||
export async function get_blog_message(id) {
|
||||
let message;
|
||||
await ssb.sqlAsync(
|
||||
'SELECT author, timestamp, content FROM messages WHERE id = ?',
|
||||
[id],
|
||||
function(row) {
|
||||
let content = JSON.parse(row.content);
|
||||
message = {
|
||||
author: row.author,
|
||||
timestamp: row.timestamp,
|
||||
blog: content?.blog,
|
||||
title: content?.title,
|
||||
};
|
||||
});
|
||||
if (message) {
|
||||
await ssb.sqlAsync(
|
||||
`
|
||||
SELECT json_extract(content, '$.name') AS name
|
||||
FROM messages
|
||||
WHERE author = ?
|
||||
AND json_extract(content, '$.type') = 'about'
|
||||
AND json_extract(content, '$.about') = author
|
||||
AND name IS NOT NULL
|
||||
ORDER BY sequence DESC LIMIT 1
|
||||
`,
|
||||
[message.author],
|
||||
function(row) {
|
||||
message.name = row.name;
|
||||
});
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
export function markdown(md) {
|
||||
let reader = new commonmark.Parser({safe: true});
|
||||
let writer = new commonmark.HtmlRenderer();
|
||||
let parsed = reader.parse(md || '');
|
||||
@ -17,10 +50,10 @@ function markdown(md) {
|
||||
while ((event = walker.next())) {
|
||||
node = event.node;
|
||||
if (event.entering) {
|
||||
if (node.type == 'image') {
|
||||
if (node.destination.startsWith('&')) {
|
||||
node.destination = '/' + node.destination + '/view';
|
||||
}
|
||||
if (node.destination?.startsWith('&')) {
|
||||
node.destination = '/' + node.destination + '/view?filename=' + node.firstChild?.literal;
|
||||
} else if (node.destination?.startsWith('@') || node.destination?.startsWith('%')) {
|
||||
node.destination = '/~core/ssb/#' + escape(node.destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -31,7 +64,12 @@ export async function render_blog_post_html(blog_post) {
|
||||
let blob = utf8Decode(await ssb.blobGet(blog_post.blog));
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>🪵Tilde Friends Blog - ${markdown(blog_post.title)}</title>
|
||||
<base target="_top">
|
||||
</head>
|
||||
<body>
|
||||
<h1><a href="./">🪵Tilde Friends Blog</a></h1>
|
||||
<div>
|
||||
<div><a href="../ssb/#${escapeAttribute(blog_post.author)}">${escape(blog_post.name)}</a> ${escape(new Date(blog_post.timestamp).toString())}</div>
|
||||
<div>${markdown(blob)}</div>
|
||||
@ -44,7 +82,7 @@ export async function render_blog_post_html(blog_post) {
|
||||
function render_blog_post(blog_post) {
|
||||
return `
|
||||
<div>
|
||||
<h2><a href="../ssb/#${escapeAttribute(blog_post.id)}">${escape(blog_post.title)}</a></h2>
|
||||
<h2><a href="/~${core.app.owner}/${core.app.name}/${escapeAttribute(blog_post.id)}">${escape(blog_post.title)}</a></h2>
|
||||
<div><a href="../ssb/#${escapeAttribute(blog_post.author)}">${escape(blog_post.name)}</a> ${escape(new Date(blog_post.timestamp).toString())}</div>
|
||||
<div>${markdown(blog_post.summary)}</div>
|
||||
</div>
|
||||
@ -55,18 +93,18 @@ export function render_html(blogs) {
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>🪵Tilde Blog</title>
|
||||
<title>🪵Tilde Friends Blog</title>
|
||||
<link href="./atom" type="application/atom+xml" rel="alternate" title="🪵Tilde Blog"/>
|
||||
<style>
|
||||
html {
|
||||
background-color: #ccc;
|
||||
}
|
||||
</style>
|
||||
<base target="_blank">
|
||||
<base target="_top">
|
||||
</head>
|
||||
<body>
|
||||
<div style="display: flex; flex-direction: row; align-items: center; gap: 1em">
|
||||
<h1>🪵Tilde Blog</h1>
|
||||
<h1>🪵Tilde Friends Blog</h1>
|
||||
<div style="font-size: xx-small; vertical-align: middle"><a href="/~cory/blog/atom">atom feed</a></div>
|
||||
</div>
|
||||
${blogs.map(blog_post => render_blog_post(blog_post)).join('\n')}
|
||||
@ -77,7 +115,7 @@ export function render_html(blogs) {
|
||||
function render_blog_post_atom(blog_post) {
|
||||
return `<entry>
|
||||
<title>${escape(blog_post.title)}</title>
|
||||
<link href="https://tildefriends.net/~cory/ssb/#${blog_post.id}" />
|
||||
<link href="/~cory/ssb/#${blog_post.id}" />
|
||||
<id>${blog_post.id}</id>
|
||||
<published>${escape(new Date(blog_post.timestamp).toString())}</published>
|
||||
<summary>${escape(blog_post.summary)}</summary>
|
||||
@ -93,9 +131,9 @@ export function render_atom(blogs) {
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>🪵Tilde Blog</title>
|
||||
<subtitle>A subtitle.</subtitle>
|
||||
<link href="https://tildefriends.net/~cory/blog/atom" rel="self"/>
|
||||
<link href="https://tildefriends.net/~cory/blog/"/>
|
||||
<id>https://www.tildefriends.net/~cory/blog/</id>
|
||||
<link href="${core.url}/atom" rel="self"/>
|
||||
<link href="${core.url}"/>
|
||||
<id>${core.url}</id>
|
||||
<updated>${new Date().toString()}</updated>
|
||||
${blogs.map(blog_post => render_blog_post_atom(blog_post)).join('\n')}
|
||||
</feed>`;
|
||||
@ -103,6 +141,7 @@ export function render_atom(blogs) {
|
||||
|
||||
export async function get_posts() {
|
||||
let blogs = [];
|
||||
let ids = await ssb.getIdentities();
|
||||
await ssb.sqlAsync(`
|
||||
WITH
|
||||
blogs AS (
|
||||
@ -139,10 +178,11 @@ export async function get_posts() {
|
||||
name IS NOT NULL)
|
||||
WHERE author_rank = 1)
|
||||
SELECT blogs.*, names.name FROM blogs
|
||||
JOIN json_each(?) AS self ON self.value = blogs.author
|
||||
JOIN public ON public.author = blogs.author
|
||||
LEFT OUTER JOIN names ON names.author = blogs.author
|
||||
ORDER BY blogs.timestamp DESC LIMIT 20
|
||||
`, [], function(row) {
|
||||
`, [JSON.stringify(ids)], function(row) {
|
||||
blogs.push(row);
|
||||
});
|
||||
return blogs;
|
||||
|
@ -1,17 +1,26 @@
|
||||
import * as blog from './blog.js';
|
||||
|
||||
async function main() {
|
||||
let blogs = await blog.get_posts();
|
||||
for (let blog_post of blogs) {
|
||||
let title = (blog_post.title || '').replaceAll(/\W/g, '_').toLowerCase();
|
||||
if (request.path === title) {
|
||||
respond({data: await blog.render_blog_post_html(blog_post), content_type: 'text/html; charset=utf-8'});
|
||||
return;
|
||||
if (request.path.startsWith('%') && request.path.endsWith('.sha256')) {
|
||||
let id = request.path.startsWith('%25') ? '%' + request.path.substring(3) : request.path;
|
||||
let message = await blog.get_blog_message(id);
|
||||
if (message) {
|
||||
respond({data: await blog.render_blog_post_html(message), content_type: 'text/html; charset=utf-8'});
|
||||
} else {
|
||||
respond({data: `Message ${id} not found.`, content_type: 'text/html; charset=utf-8'});
|
||||
}
|
||||
}
|
||||
if (request.path == 'atom') {
|
||||
} else if (request.path == 'atom') {
|
||||
let blogs = await blog.get_posts();
|
||||
respond({data: blog.render_atom(blogs), content_type: 'application/atom+xml'});
|
||||
} else {
|
||||
let blogs = await blog.get_posts();
|
||||
for (let blog_post of blogs) {
|
||||
let title = (blog_post.title || '').replaceAll(/\W/g, '_').toLowerCase();
|
||||
if (request.path === title) {
|
||||
respond({data: await blog.render_blog_post_html(blog_post), content_type: 'text/html; charset=utf-8'});
|
||||
return;
|
||||
}
|
||||
}
|
||||
respond({data: blog.render_html(blogs), content_type: 'text/html; charset=utf-8'});
|
||||
}
|
||||
}
|
||||
|
50
apps/blog/lit-all.min.js
vendored
50
apps/blog/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,4 +0,0 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "📚"
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -1,16 +0,0 @@
|
||||
# Tilde Friends Developer's Guide
|
||||
[Back to index](#index)
|
||||
|
||||
A Tilde Friends application runs on the server. To make an interesting
|
||||
application that interacts with the client, it's necessary to understand
|
||||
how the parts work together.
|
||||
|
||||
## Hello, world!
|
||||
|
||||
A simple starting point. Presents `Hello, world!` in the browser when
|
||||
visited.
|
||||
|
||||
**app.js**:
|
||||
```
|
||||
app.setDocument('<h1>Hello, world!</h1>');
|
||||
```
|
@ -1,12 +0,0 @@
|
||||
# Tilde Friends Documentation
|
||||
|
||||
Tilde Friends is a participating member of a greater social
|
||||
network, [Secure Scuttlebutt](https://scuttlebutt.nz/),
|
||||
adding a way to safely and securely write, share,
|
||||
and run code in the form of server-side web applications.
|
||||
|
||||
- [Tilde Friends Vision](#vision)
|
||||
- [Secure Scuttlebutt from Scratch](#ssb)
|
||||
- [Structure](#structure)
|
||||
- [Guide](#guide)
|
||||
- [TODO](#todo)
|
@ -1,41 +0,0 @@
|
||||
# Secure Scuttlebutt from Scratch
|
||||
[Back to index](#index)
|
||||
|
||||
This aims to be the missing reference for those who wish to create a Secure
|
||||
Scuttlebutt client from scratch.
|
||||
|
||||
## Discovery
|
||||
A good way to get started is to participate in local network discovery with a known working
|
||||
client on the same network. The
|
||||
[Scuttlebutt Programming Guide](https://ssbc.github.io/scuttlebutt-protocol-guide/#local-network)
|
||||
is a good start, here, with a few things to note:
|
||||
|
||||
1. Some clients advertise multiple addresses separated by semicolons (`;`).
|
||||
2. Some clients advertise alternative protocols than `shs` and use hostnames instead of
|
||||
IPv4 addresses.
|
||||
|
||||
So be prepared to accept variations.
|
||||
|
||||
There also an undocumented "new" style of discovery message.
|
||||
|
||||
## Secret Handshake, Box Stream, and RPC Protocol
|
||||
Now that two clients are aware of eachother, they need to complete a secret handshake.
|
||||
The [programming guide](https://ssbc.github.io/scuttlebutt-protocol-guide/#handshake)
|
||||
is once again a good reference.
|
||||
|
||||
The box stream and RPC protocol can both be implemented from the
|
||||
[same documentation](https://ssbc.github.io/scuttlebutt-protocol-guide/#box-stream)
|
||||
without surprises.
|
||||
|
||||
## Synchronizing Data
|
||||
|
||||
... `ebt.replicate` or `createHistoryStream` ...
|
||||
|
||||
## Rooms
|
||||
|
||||
TODO
|
||||
|
||||
## References
|
||||
* [https://ssbc.github.io/scuttlebutt-protocol-guide/](https://ssbc.github.io/scuttlebutt-protocol-guide/)
|
||||
* [https://dev.planetary.social/](https://dev.planetary.social/)
|
||||
* [https://dev.scuttlebutt.nz/#/golang/?id=muxrpc-endpoints](https://dev.scuttlebutt.nz/#/golang/?id=muxrpc-endpoints)
|
@ -1,65 +0,0 @@
|
||||
# Tilde Friends Structure
|
||||
[Back to index](#index)
|
||||
|
||||
Tilde Friends is a mostly-self-contained executable written in C.
|
||||
|
||||
In combines the following key components:
|
||||
- A Secure Scuttlebutt (SSB) client/server. This talks with other SSB
|
||||
instances, storing messages and blobs for anyone visible to local
|
||||
users as they are encountered and sharing anything published locally
|
||||
as appropriate.
|
||||
- An sqlite database. This is where the SSB instance stores its data.
|
||||
The general schema involves a `messages` table, storing mostly JSON,
|
||||
a `blobs` table storing arbitrary blob data, and a `properties` table,
|
||||
storing arbitrary state gleaned from `messages` and `blobs`, generally
|
||||
updated on demand and incrementally.
|
||||
- A QuickJS runtime. The core process runs stock scripts and has access
|
||||
and permission to use all resources. All other processes, which
|
||||
includes everything which runs untrusted code created by Tilde Friends
|
||||
users, are strictly sandboxed in ways similar to how web browsers run
|
||||
untrusted code. All attempts to access potentially sensitive resources
|
||||
are mediated through the core process.
|
||||
|
||||
When run with no arguments, it starts a web server on
|
||||
[http://localhost:12345/](http://localhost:12345/) and an SSB node.
|
||||
|
||||
## Web Interface
|
||||
The Tilde Friends web server provides access to Tilde Friends applications,
|
||||
which are arbitrary user-defined web applications.
|
||||
|
||||
At the top left, in addition to some basic navigation links, is an `edit`
|
||||
link. Anyone can view, modify, and run in-place the code to any Tilde
|
||||
Friends application by using the in-browser editor.
|
||||
|
||||
At the top right, one can `login` (to save work in their own space)
|
||||
or `logout` (proceeding as a guest).
|
||||
|
||||
The rest of the page is an iframe belonging to the application.
|
||||
|
||||
## Special Paths
|
||||
|
||||
- `/~user/app/` - Tilde Friends application paths take the form `/~user/app/`, where `user`
|
||||
is a username of a Tilde Friends account, and `app` is an arbitrary name
|
||||
of an application saved by the given user.
|
||||
- `/~user/app/file` - A raw file in an app.
|
||||
- `/&blobid.ed25519` - A raw blob. Content-Type is inferred for at least
|
||||
a few common image types.
|
||||
|
||||
## Communication Channels
|
||||
Web Browser <-> Core <-> Sandbox
|
||||
|
||||
Visiting an application path delivers stock HTML and JavaScript which
|
||||
establishes a WebSocket connection back to the server.
|
||||
|
||||
At this point, a new sandbox process is started in Tilde Friends, much
|
||||
as a new sandboxed process might be started for a new tab in a web
|
||||
browser. This process has a custom RPC connection to the core process
|
||||
which holds the WebSocket connection to the browser.
|
||||
|
||||
The custom RPC communication between the sandbox process and the core
|
||||
process facilitates passing and calling functions remotely. Calling a
|
||||
function in another process returns a `Promise`.
|
||||
|
||||
An application will typically call `app.setDocument()` at startup to
|
||||
populate the app's iframe in the web browser with its own client web
|
||||
application resources.
|
@ -1,63 +0,0 @@
|
||||
# Tilde Friends TODO
|
||||
[Back to index](#index)
|
||||
|
||||
## MVP3
|
||||
- Sync status (problem feeds, messages/seconds stats, ...)
|
||||
- app: wiki
|
||||
- app: public blog
|
||||
- Content-Disposition: download
|
||||
- remove SSB credentials
|
||||
- export SSB credentials
|
||||
- initial: better empty news screen
|
||||
- initial: remembered wrong user across login/logout
|
||||
- initial: bad experience when following nobody
|
||||
- make a cool independent app
|
||||
- indicate when workspace differs from installed
|
||||
- / => Something good.
|
||||
- update docs
|
||||
- audit + document API exposed to apps
|
||||
- fix weird HTTP warnings
|
||||
- channels
|
||||
- placeholder/missing images
|
||||
- no denial of service
|
||||
- package standalone executable
|
||||
- editor without app iframe
|
||||
- sequence_before_author -> flags
|
||||
- linkify ssb: links
|
||||
- perfect rooms support
|
||||
- connections 2.0
|
||||
- make a better connections API
|
||||
|
||||
## Maybe Done
|
||||
- blob_wants 2.0
|
||||
- image downsample
|
||||
- app: todo
|
||||
- app: build archive
|
||||
- update README
|
||||
- administrators config
|
||||
- apps name characters
|
||||
- initial: can't switch to account when there is only one
|
||||
- get tarball under 5MB
|
||||
- rooms
|
||||
- initial: doesn't refresh when create identity
|
||||
- tf account timeout why
|
||||
- ssb don't overflow boxes
|
||||
- jwt for session tokens
|
||||
- linkify https://...
|
||||
- emoji reaction picker
|
||||
- expose loads of stats
|
||||
- confirm posting all new messages
|
||||
- multiple identities per user, in database
|
||||
- auto-populate data on initial launch
|
||||
- make the docker image good / test it / use it
|
||||
- leaking imports / exports
|
||||
- file upload widget
|
||||
- keep working on good error feedback
|
||||
- build for windows
|
||||
- installable apps (bring back an app message?)
|
||||
- sqlStream => sqlExec or something
|
||||
- !ssb from child process?
|
||||
|
||||
## Done
|
||||
- update LICENSE
|
||||
- logging to browser
|
@ -1,62 +0,0 @@
|
||||
# Tilde Friends Vision
|
||||
[Back to index](#index)
|
||||
|
||||
Tilde Friends is a tool for making and sharing.
|
||||
|
||||
It is both a peer-to-peer social network client, participating in Secure
|
||||
Scuttlebutt, and an environment for creating and running web applications.
|
||||
|
||||
## Why
|
||||
|
||||
This is a thing that I wanted to exist and wanted to work on. No other reason.
|
||||
There is not a business model. I believe it is interesting and unique.
|
||||
|
||||
## Goals
|
||||
1. Make it **easy and fun** to run all sorts of web applications.
|
||||
|
||||
2. Provide **security** that is easy to understand and protects your data.
|
||||
|
||||
3. Make **creating and sharing** web applications accessible to anyone with a
|
||||
browser.
|
||||
|
||||
## Ways to Use Tilde Friends
|
||||
1. **Social Network User**: This is a social network first. You are just here,
|
||||
because your friends are. Or you like how we limit your message length or
|
||||
short videos or whatever the trend is. If you are ambitious, you click links
|
||||
and see interactive experiences (apps) that you wouldn't see elsewhere.
|
||||
|
||||
2. **Web Visitor**: You get links from a friend to meeting invites, polls, games,
|
||||
lists, wiki pages, ..., and you interact with them as though they were
|
||||
cloud-hosted by a megacorporation. They just work, and you don't think twice.
|
||||
|
||||
3. **Group leader**: You host or use a small public instance, installing apps for
|
||||
a group of friends to use as web visitors.
|
||||
|
||||
4. **Developer**: You like to write code and make or improve apps for fun or to
|
||||
solve problems. When you encounter a Tilde Friends app on a strange server,
|
||||
you know you can trivially modify it or download it to your own instance.
|
||||
|
||||
## Future Goals / Endgame
|
||||
1. Mobile apps. This can run on your old phone. Maybe you won't be hosting
|
||||
the web interface publicly, but you can sync, install and edit apps, and
|
||||
otherwise get the full experience from a tiny touch screen.
|
||||
|
||||
2. The universal application runtime. The web browser is the universal
|
||||
platform, but even for the simplest application that you might want to host
|
||||
for your friends, cloud hosting, containers, and complicated dependencies might
|
||||
all enter the mix. Tilde Friends, though it is yet another thing to host,
|
||||
includes everything you need out of the box to run a vast variety of interesting
|
||||
apps.
|
||||
|
||||
Tilde Friends will be built out, gradually providing safe access to host
|
||||
resources and client resources the same way web browsers extended access to
|
||||
resources like GPU, persistent storage, cameras, ... over the years.
|
||||
|
||||
Not much effort has been put forward yet to having a robust, long-lasting API,
|
||||
but since the client side longevity is already handled by web browsers, it
|
||||
seems possible that the server-side API can be managed in a similar way.
|
||||
|
||||
3. An awesome development environment. Right now it runs JavaScript from the
|
||||
first embeddable text editor I could poorly configure enough to edit code,
|
||||
but it could incorporate a debugger, source control integration a la ssb-git,
|
||||
merge tools, and transpiling from all sorts of different languages.
|
8
apps/gg/lit-all.min.js
vendored
8
apps/gg/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
5
apps/identity.json
Normal file
5
apps/identity.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🪪",
|
||||
"previous": "&kgukkyDk1RxgfzgMH6H/0QeDPIuwPZypLuAFax21ljk=.sha256"
|
||||
}
|
87
apps/identity/app.js
Normal file
87
apps/identity/app.js
Normal file
@ -0,0 +1,87 @@
|
||||
import * as tfrpc from '/tfrpc.js';
|
||||
|
||||
tfrpc.register(async function get_private_key(id) {
|
||||
return bip39Words(await ssb.getPrivateKey(id));
|
||||
});
|
||||
tfrpc.register(async function create_id(id) {
|
||||
return await ssb.createIdentity();
|
||||
});
|
||||
tfrpc.register(async function add_id(id) {
|
||||
return await ssb.addIdentity(bip39Bytes(id));
|
||||
});
|
||||
tfrpc.register(async function delete_id(id) {
|
||||
return await ssb.deleteIdentity(id);
|
||||
});
|
||||
tfrpc.register(async function reload() {
|
||||
await main();
|
||||
});
|
||||
|
||||
async function main() {
|
||||
let ids = await ssb.getIdentities();
|
||||
await app.setDocument(`<body style="color: #fff">
|
||||
<script>const handler = {};</script>
|
||||
<script type="module">
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
handler.export_id = async function export_id(event) {
|
||||
let id = event.srcElement.dataset.id;
|
||||
let element = document.createElement('textarea');
|
||||
element.value = await tfrpc.rpc.get_private_key(id);
|
||||
element.style = 'width: 100%; read-only: true';
|
||||
element.readOnly = true;
|
||||
event.srcElement.parentElement.appendChild(element);
|
||||
event.srcElement.onclick = event => handler.hide_id(event, element);
|
||||
}
|
||||
handler.add_id = async function add_id(event) {
|
||||
let id = document.getElementById('add_id').value;
|
||||
try {
|
||||
let new_id = await tfrpc.rpc.add_id(id);
|
||||
alert('Successfully imported: ' + new_id);
|
||||
await tfrpc.rpc.reload();
|
||||
} catch (e) {
|
||||
alert('Error importing identity: ' + e);
|
||||
}
|
||||
}
|
||||
handler.create_id = async function create_id(event) {
|
||||
try {
|
||||
let id = await tfrpc.rpc.create_id();
|
||||
alert('Successfully created: ' + id);
|
||||
await tfrpc.rpc.reload();
|
||||
} catch (e) {
|
||||
alert('Error creating identity: ' + e);
|
||||
}
|
||||
}
|
||||
handler.hide_id = function hide_id(event, element) {
|
||||
element.parentNode.removeChild(element);
|
||||
event.srcElement.onclick = handler.export_id;
|
||||
}
|
||||
handler.delete_id = async function delete_id(event) {
|
||||
let id = event.srcElement.dataset.id;
|
||||
try {
|
||||
if (prompt('Are you sure you want to delete "' + id + '"? It cannot be recovered without the exported phrase.\\n\\nEnter the word "DELETE" to confirm you wish to delete it.') === 'DELETE') {
|
||||
if (await tfrpc.rpc.delete_id(id)) {
|
||||
alert('Successfully deleted ID: ' + id);
|
||||
}
|
||||
await tfrpc.rpc.reload();
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Error deleting ID: ' + e);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<h1>SSB Identity Management</h1>
|
||||
<h2>Create a new identity</h2>
|
||||
<button id="create_id" onclick="handler.create_id()">Create Identity</button>
|
||||
<h2>Import an SSB Identity from 12 BIP39 English Words</h2>
|
||||
<textarea id="add_id" style="width: 100%" rows="4"></textarea><button id="add" onclick="handler.add_id(event)">Import Identity</button>
|
||||
<h2>Identities</h2>
|
||||
<ul>`+
|
||||
ids.map(id => `<li>
|
||||
<button onclick="handler.export_id(event)" data-id="${id}">Export Identity</button>
|
||||
<button onclick="handler.delete_id(event)" data-id="${id}">Delete Identity</button>
|
||||
${id}
|
||||
</li>`).join('\n')+
|
||||
` </ul>
|
||||
</body>`);
|
||||
}
|
||||
|
||||
main();
|
8
apps/issues/lit-all.min.js
vendored
8
apps/issues/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
apps/journal/lit-all.min.js
vendored
8
apps/journal/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
apps/sneaker/lit-all.min.js
vendored
8
apps/sneaker/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🐌",
|
||||
"previous": "&dO6ckMIPVv9QvSc+0TOg0S59qe+rirPo2a6p9xSHj9M=.sha256"
|
||||
"previous": "&h+PXCrnUHtHHfKyUaLW+Y1dP/JpWwG9cbRNjxOCVqw0=.sha256"
|
||||
}
|
@ -3,15 +3,15 @@
|
||||
<head>
|
||||
<title>Tilde Friends</title>
|
||||
<base target="_top">
|
||||
<link rel="stylesheet" href="tribute.css" />
|
||||
<link rel="stylesheet" href="tribute.css"/>
|
||||
<style>
|
||||
.tribute-container {
|
||||
color: #000;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<tf-app/>
|
||||
<body style="background-color: #223a5e">
|
||||
<tf-app class="w3-deep-purple"/>
|
||||
<script>window.litDisableBundleWarning = true;</script>
|
||||
<script src="filesaver.min.js"></script>
|
||||
<script src="commonmark.min.js"></script>
|
||||
|
8
apps/ssb/lit-all.min.js
vendored
8
apps/ssb/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -184,8 +184,10 @@ class TfElement extends LitElement {
|
||||
|
||||
render_id_picker() {
|
||||
return html`
|
||||
<tf-id-picker id="picker" selected=${this.whoami} .ids=${this.ids} .users=${this.users} @change=${this._handle_whoami_changed}></tf-id-picker>
|
||||
<button @click=${this.create_identity} id="create_identity">Create Identity</button>
|
||||
<div style="display: flex; gap: 8px">
|
||||
<tf-id-picker id="picker" style="flex: 1 1 auto" selected=${this.whoami} .ids=${this.ids} .users=${this.users} @change=${this._handle_whoami_changed}></tf-id-picker>
|
||||
<button class="w3-button w3-dark-grey w3-border" style="flex: 0 0 auto" @click=${this.create_identity} id="create_identity">Create Identity</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -283,13 +285,19 @@ class TfElement extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
const k_tabs = {
|
||||
'📰': 'news',
|
||||
'📡': 'connections',
|
||||
'@': 'mentions',
|
||||
'🔍': 'search',
|
||||
'👩💻': 'query',
|
||||
};
|
||||
|
||||
let tabs = html`
|
||||
<div>
|
||||
<input type="button" class="tab" value="News" ?disabled=${self.tab == 'news'} @click=${() => self.set_tab('news')}></input>
|
||||
<input type="button" class="tab" value="Connections" ?disabled=${self.tab == 'connections'} @click=${() => self.set_tab('connections')}></input>
|
||||
<input type="button" class="tab" value="Mentions" ?disabled=${self.tab == 'mentions'} @click=${() => self.set_tab('mentions')}></input>
|
||||
<input type="button" class="tab" value="Search" ?disabled=${self.tab == 'search'} @click=${() => self.set_tab('search')}></input>
|
||||
<input type="button" class="tab" value="Query" ?disabled=${self.tab == 'query'} @click=${() => self.set_tab('query')}></input>
|
||||
<div class="w3-bar w3-black">
|
||||
${Object.entries(k_tabs).map(([k, v]) => html`
|
||||
<button title=${v} class="w3-bar-item w3-padding-large w3-hover-gray tab ${self.tab == v ? 'w3-red' : 'w3-black'}" @click=${() => self.set_tab(v)}>${k}</button>
|
||||
`)}
|
||||
</div>
|
||||
`;
|
||||
let contents =
|
||||
|
@ -314,7 +314,7 @@ class TfComposeElement extends LitElement {
|
||||
return html`
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<div style="align-self: center; margin: 0.5em">
|
||||
<input type="button" value="🚮" title="Remove ${mention.name} mention" @click=${() => self.remove_mention(mention.link)}></input>
|
||||
<button class="w3-button w3-dark-grey" title="Remove ${mention.name} mention" @click=${() => self.remove_mention(mention.link)}>🚮</button>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column">
|
||||
<h3>${mention.name}</h3>
|
||||
@ -357,12 +357,12 @@ class TfComposeElement extends LitElement {
|
||||
|
||||
if (this.apps) {
|
||||
return html`
|
||||
<div>
|
||||
<select id="select">
|
||||
<div class="w3-card-4 w3-margin w3-padding">
|
||||
<select id="select" class="w3-select w3-dark-grey">
|
||||
${Object.keys(self.apps).map(app => html`<option value=${app}>${app}</option>`)}
|
||||
</select>
|
||||
<input type="button" value="Attach" @click=${attach_selected_app}></input>
|
||||
<input type="button" value="Cancel" @click=${() => this.apps = null}></input>
|
||||
<button class="w3-button w3-dark-grey" @click=${attach_selected_app}>Attach</button>
|
||||
<button class="w3-button w3-dark-grey" @click=${() => this.apps = null}>Cancel</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -374,9 +374,9 @@ class TfComposeElement extends LitElement {
|
||||
self.apps = await tfrpc.rpc.apps();
|
||||
}
|
||||
if (!this.apps) {
|
||||
return html`<input type="button" value="Attach App" @click=${attach_app}></input>`;
|
||||
return html`<button class="w3-button w3-dark-grey" @click=${attach_app}>Attach App</button>`;
|
||||
} else {
|
||||
return html`<input type="button" value="Discard App" @click=${() => this.apps = null}></input>`;
|
||||
return html`<button class="w3-button w3-dark-grey" @click=${() => this.apps = null}>Discard App</button>`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -392,15 +392,17 @@ class TfComposeElement extends LitElement {
|
||||
let draft = this.get_draft();
|
||||
if (draft.content_warning !== undefined) {
|
||||
return html`
|
||||
<div>
|
||||
<input type="checkbox" id="cw" @change=${() => self.set_content_warning(undefined)} checked></input>
|
||||
<label for="cw">CW</label>
|
||||
<input type="text" id="content_warning" @input=${this.input} @change=${this.change} value=${draft.content_warning}></input>
|
||||
<div class="w3-container w3-padding">
|
||||
<p>
|
||||
<input type="checkbox" class="w3-check w3-dark-grey" id="cw" @change=${() => self.set_content_warning(undefined)} checked="checked"></input>
|
||||
<label for="cw">CW</label>
|
||||
</p>
|
||||
<input type="text" class="w3-input w3-border w3-dark-grey" id="content_warning" placeholder="Enter a content warning here." @input=${this.input} @change=${this.change} value=${draft.content_warning}></input>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
return html`
|
||||
<input type="checkbox" id="cw" @change=${() => self.set_content_warning('')}></input>
|
||||
<input type="checkbox" class="w3-check w3-dark-grey" id="cw" @change=${() => self.set_content_warning('')}></input>
|
||||
<label for="cw">CW</label>
|
||||
`;
|
||||
}
|
||||
@ -430,13 +432,13 @@ class TfComposeElement extends LitElement {
|
||||
<div style="display: flex; flex-direction: row; width: 100%">
|
||||
<label for="encrypt_to">🔐 To:</label>
|
||||
<input type="text" id="encrypt_to" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input>
|
||||
<input type="button" value="🚮" @click=${() => this.set_encrypt(undefined)}></input>
|
||||
<button class="w3-button w3-dark-grey" @click=${() => this.set_encrypt(undefined)}>🚮</button>
|
||||
</div>
|
||||
<ul>
|
||||
${draft.encrypt_to.map(x => html`
|
||||
<li>
|
||||
<tf-user id=${x} .users=${this.users}></tf-user>
|
||||
<input type="button" value="🚮" @click=${() => this.set_encrypt(draft.encrypt_to.filter(id => id != x))}></input>
|
||||
<input type="button" class="w3-button w3-dark-grey" value="🚮" @click=${() => this.set_encrypt(draft.encrypt_to.filter(id => id != x))}></input>
|
||||
</li>`)}
|
||||
</ul>
|
||||
`;
|
||||
@ -454,28 +456,34 @@ class TfComposeElement extends LitElement {
|
||||
let draft = self.get_draft();
|
||||
let content_warning =
|
||||
draft.content_warning !== undefined ?
|
||||
html`<div id="content_warning_preview" class="content_warning">${draft.content_warning}</div>` :
|
||||
html`<div class="w3-panel w3-round-xlarge w3-blue">
|
||||
<p id="content_warning_preview">${draft.content_warning}</p>
|
||||
</div>` :
|
||||
undefined;
|
||||
let encrypt = draft.encrypt_to !== undefined ?
|
||||
undefined :
|
||||
html`<input type="button" value="🔐" @click=${() => this.set_encrypt([])}></input>`;
|
||||
html`<button class="w3-button w3-dark-grey" @click=${() => this.set_encrypt([])}>🔐</button>`;
|
||||
let result = html`
|
||||
${this.render_encrypt()}
|
||||
<div style="display: flex; flex-direction: row; width: 100%">
|
||||
<textarea id="edit" @input=${this.input} @change=${this.change} @paste=${this.paste} style="flex: 1 0 50%">${draft.text}</textarea>
|
||||
<div style="flex: 1 0 50%">
|
||||
${content_warning}
|
||||
<div id="preview"></div>
|
||||
<div class="w3-card-4 w3-blue-grey w3-padding" style="box-sizing: border-box">
|
||||
${this.render_encrypt()}
|
||||
<div style="display: flex; flex-direction: row; width: 100%; gap: 4px">
|
||||
<div style="flex: 1 0 50%">
|
||||
<p><textarea class="w3-input w3-dark-grey w3-border" style="resize: vertical" placeholder="Write a post here." id="edit" @input=${this.input} @change=${this.change} @paste=${this.paste}>${draft.text}</textarea></p>
|
||||
</div>
|
||||
<div style="flex: 1 0 50%">
|
||||
${content_warning}
|
||||
<div id="preview"></div>
|
||||
</div>
|
||||
</div>
|
||||
${Object.values(draft.mentions || {}).map(x => self.render_mention(x))}
|
||||
${this.render_attach_app()}
|
||||
${this.render_content_warning()}
|
||||
<button class="w3-button w3-dark-grey" id="submit" @click=${this.submit}>Submit</button>
|
||||
<button class="w3-button w3-dark-grey" @click=${this.attach}>Attach</button>
|
||||
${this.render_attach_app_button()}
|
||||
${encrypt}
|
||||
<button class="w3-button w3-dark-grey" @click=${this.discard}>Discard</button>
|
||||
</div>
|
||||
${Object.values(draft.mentions || {}).map(x => self.render_mention(x))}
|
||||
${this.render_content_warning()}
|
||||
${this.render_attach_app()}
|
||||
<input type="button" id="submit" value="Submit" @click=${this.submit}></input>
|
||||
<input type="button" value="Attach" @click=${this.attach}></input>
|
||||
${this.render_attach_app_button()}
|
||||
${encrypt}
|
||||
<input type="button" value="Discard" @click=${this.discard}></input>
|
||||
`;
|
||||
return result;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {LitElement, html} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
|
||||
/*
|
||||
** Provide a list of IDs, and this lets the user pick one.
|
||||
@ -13,6 +14,8 @@ class TfIdentityPickerElement extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.ids = [];
|
||||
@ -28,7 +31,7 @@ class TfIdentityPickerElement extends LitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<select @change=${this.changed} style="max-width: 100%">
|
||||
<select class="w3-select w3-dark-grey w3-padding w3-border" @change=${this.changed} style="max-width: 100%; overflow: hidden">
|
||||
${(this.ids ?? []).map(id => html`<option ?selected=${id == this.selected} value=${id}>${this.users[id]?.name ? (this.users[id]?.name + ' - ') : undefined}<small>${id}</small></option>`)}
|
||||
</select>
|
||||
`;
|
||||
|
@ -213,9 +213,9 @@ class TfMessageElement extends LitElement {
|
||||
let self = this;
|
||||
if (this.message.child_messages?.length) {
|
||||
if (!this.expanded[this.message.id]) {
|
||||
return html`<input type="button" value=${this.total_child_messages(this.message) + ' More'} @click=${() => self.set_expanded(true)}></input>`;
|
||||
return html`<button class="w3-button w3-dark-grey" @click=${() => self.set_expanded(true)}>+ ${this.total_child_messages(this.message) + ' More'}</button>`;
|
||||
} else {
|
||||
return html`<input type="button" value="Collapse" @click=${() => self.set_expanded(false)}></input>${(this.message.child_messages || []).map(x => html`<tf-message .message=${x} whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} .expanded=${this.expanded}></tf-message>`)}`;
|
||||
return html`<button class="w3-button w3-dark-grey" @click=${() => self.set_expanded(false)}>Collapse</button>${(this.message.child_messages || []).map(x => html`<tf-message .message=${x} whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} .expanded=${this.expanded}></tf-message>`)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -250,29 +250,29 @@ class TfMessageElement extends LitElement {
|
||||
switch (this.format) {
|
||||
case 'raw':
|
||||
if (content?.type == 'post' || content?.type == 'blog') {
|
||||
raw_button = html`<input type="button" value="Markdown" @click=${() => self.format = 'md'}></input>`;
|
||||
raw_button = html`<button class="w3-button w3-dark-grey" @click=${() => self.format = 'md'}>Markdown</button>`;
|
||||
} else {
|
||||
raw_button = html`<input type="button" value="Message" @click=${() => self.format = 'message'}></input>`;
|
||||
raw_button = html`<button class="w3-button w3-dark-grey" @click=${() => self.format = 'message'}>Message</button>`;
|
||||
}
|
||||
break;
|
||||
case 'md':
|
||||
raw_button = html`<input type="button" value="Message" @click=${() => self.format = 'message'}></input>`;
|
||||
raw_button = html`<button class="w3-button w3-dark-grey" @click=${() => self.format = 'message'}>Message</button>`;
|
||||
break;
|
||||
case 'decrypted':
|
||||
raw_button = html`<input type="button" value="Raw" @click=${() => self.format = 'raw'}></input>`;
|
||||
raw_button = html`<button class="w3-button w3-dark-grey" @click=${() => self.format = 'raw'}>Raw</button>`;
|
||||
break;
|
||||
default:
|
||||
if (this.message.decrypted) {
|
||||
raw_button = html`<input type="button" value="Decrypted" @click=${() => self.format = 'decrypted'}></input>`;
|
||||
raw_button = html`<button class="w3-button w3-dark-grey" @click=${() => self.format = 'decrypted'}>Decrypted</button>`;
|
||||
} else {
|
||||
raw_button = html`<input type="button" value="Raw" @click=${() => self.format = 'raw'}></input>`;
|
||||
raw_button = html`<button class="w3-button w3-dark-grey" @click=${() => self.format = 'raw'}>Raw</button>`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
function small_frame(inner) {
|
||||
let body;
|
||||
return html`
|
||||
<div style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; display: inline-block; overflow-wrap: anywhere">
|
||||
<div class="w3-card-4" style="background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; display: inline-block; overflow-wrap: anywhere">
|
||||
<tf-user id=${self.message.author} .users=${self.users}></tf-user>
|
||||
<span style="padding-right: 8px"><a tfarget="_top" href=${'#' + self.message.id}>%</a> ${new Date(self.message.timestamp).toLocaleString()}</span>
|
||||
${raw_button}
|
||||
@ -283,14 +283,14 @@ class TfMessageElement extends LitElement {
|
||||
}
|
||||
if (this.message?.type === 'contact_group') {
|
||||
return html`
|
||||
<div style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; overflow-wrap: anywhere">
|
||||
<div class="w3-card-4" style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; overflow-wrap: anywhere">
|
||||
${this.message.messages.map(x =>
|
||||
html`<tf-message .message=${x} whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} .expanded=${this.expanded}></tf-message>`
|
||||
)}
|
||||
</div>`;
|
||||
} else if (this.message.placeholder) {
|
||||
return html`
|
||||
<div style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; overflow-wrap: anywhere">
|
||||
<div class="w3-card-4" style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; overflow-wrap: anywhere">
|
||||
<a target="_top" href=${'#' + this.message.id}>${this.message.id}</a> (placeholder)
|
||||
<div>${this.render_votes()}</div>
|
||||
${(this.message.child_messages || []).map(x => html`
|
||||
@ -351,7 +351,7 @@ class TfMessageElement extends LitElement {
|
||||
.drafts=${this.drafts}
|
||||
@tf-discard=${this.discard_reply}></tf-compose>
|
||||
` : html`
|
||||
<input type="button" value="Reply" @click=${this.show_reply}></input>
|
||||
<button class="w3-button w3-dark-grey" @click=${this.show_reply}>Reply</button>
|
||||
`;
|
||||
let self = this;
|
||||
let body;
|
||||
@ -367,7 +367,7 @@ class TfMessageElement extends LitElement {
|
||||
break;
|
||||
}
|
||||
let content_warning = html`
|
||||
<div class="content_warning" @click=${x => this.toggle_expanded(':cw')}>${content.contentWarning}</div>
|
||||
<div class="w3-panel w3-round-xlarge w3-blue" style="cursor: pointer" @click=${x => this.toggle_expanded(':cw')}><p>${content.contentWarning}</p></div>
|
||||
`;
|
||||
let content_html =
|
||||
html`
|
||||
@ -401,7 +401,7 @@ class TfMessageElement extends LitElement {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<div style="border: 1px solid black; background-color: ${style_background}; margin-top: 8px; padding: 16px">
|
||||
<div class="w3-card-4" style="border: 1px solid black; background-color: ${style_background}; margin-top: 8px; padding: 16px">
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
|
||||
${is_encrypted}
|
||||
@ -411,10 +411,10 @@ class TfMessageElement extends LitElement {
|
||||
</div>
|
||||
${payload}
|
||||
${this.render_votes()}
|
||||
<div>
|
||||
<p>
|
||||
${reply}
|
||||
<input type="button" value="React" @click=${this.react}></input>
|
||||
</div>
|
||||
<button class="w3-button w3-dark-grey" @click=${this.react}>React</button>
|
||||
</p>
|
||||
${this.render_children()}
|
||||
</div>
|
||||
`;
|
||||
@ -436,7 +436,7 @@ class TfMessageElement extends LitElement {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<div style="border: 1px solid black; background-color: ${style_background}; margin-top: 8px; padding: 16px">
|
||||
<div class="w3-card-4" style="border: 1px solid black; background-color: ${style_background}; margin-top: 8px; padding: 16px">
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
|
||||
${is_encrypted}
|
||||
@ -446,9 +446,9 @@ class TfMessageElement extends LitElement {
|
||||
</div>
|
||||
${content.text}
|
||||
${this.render_votes()}
|
||||
<div>
|
||||
<input type="button" value="React" @click=${this.react}></input>
|
||||
</div>
|
||||
<p>
|
||||
<button class="w3-button w3-dark-grey" @click=${this.react}>React</button>
|
||||
</p>
|
||||
${this.render_children()}
|
||||
</div>
|
||||
`;
|
||||
@ -484,6 +484,17 @@ class TfMessageElement extends LitElement {
|
||||
`;
|
||||
break;
|
||||
}
|
||||
let reply = (this.drafts[this.message?.id] !== undefined) ? html`
|
||||
<tf-compose
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
root=${this.message.content.root || this.message.id}
|
||||
branch=${this.message.id}
|
||||
.drafts=${this.drafts}
|
||||
@tf-discard=${this.discard_reply}></tf-compose>
|
||||
` : html`
|
||||
<button class="w3-button w3-dark-grey" @click=${this.show_reply}>Reply</button>
|
||||
`;
|
||||
return html`
|
||||
<style>
|
||||
code {
|
||||
@ -499,7 +510,7 @@ class TfMessageElement extends LitElement {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<div style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px">
|
||||
<div class="w3-card-4" style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px">
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
|
||||
<span style="flex: 1"></span>
|
||||
@ -509,7 +520,12 @@ class TfMessageElement extends LitElement {
|
||||
|
||||
<div>${body}</div>
|
||||
${this.render_mentions()}
|
||||
<div>
|
||||
${reply}
|
||||
<button class="w3-button w3-dark-grey" @click=${this.react}>React</button>
|
||||
</div>
|
||||
${this.render_votes()}
|
||||
${this.render_children()}
|
||||
</div>
|
||||
`;
|
||||
} else if (content.type === 'pub') {
|
||||
|
@ -184,47 +184,49 @@ class TfProfileElement extends LitElement {
|
||||
if (this.editing) {
|
||||
let server_follow;
|
||||
if (this.server_follows_me === true) {
|
||||
server_follow = html`<input type="button" value="Server, Stop Following Me" @click=${() => this.server_follow_me(false)}></input>`;
|
||||
server_follow = html`<button class="w3-button w3-dark-grey" @click=${() => this.server_follow_me(false)}>Server, Stop Following Me</button>`;
|
||||
} else if (this.server_follows_me === false) {
|
||||
server_follow = html`<input type="button" value="Server, Follow Me" @click=${() => this.server_follow_me(true)}></input>`;
|
||||
server_follow = html`<button class="w3-button w3-dark-grey" @click=${() => this.server_follow_me(true)}>Server, Follow Me</button>`;
|
||||
}
|
||||
edit = html`
|
||||
<input type="button" value="Save Profile" @click=${this.save_edits}></input>
|
||||
<input type="button" value="Discard" @click=${this.discard_edits}></input>
|
||||
<button class="w3-button w3-dark-grey" @click=${this.save_edits}>Save Profile</button>
|
||||
<button class="w3-button w3-dark-grey" @click=${this.discard_edits}>Discard</button>
|
||||
${server_follow}
|
||||
`;
|
||||
} else {
|
||||
edit = html`<input type="button" value="Edit Profile" @click=${this.edit}></input>`;
|
||||
edit = html`<button class="w3-button w3-dark-grey" @click=${this.edit}>Edit Profile</button>`;
|
||||
}
|
||||
}
|
||||
if (this.id !== this.whoami &&
|
||||
this.following !== undefined) {
|
||||
follow =
|
||||
this.following ?
|
||||
html`<input type="button" value="Unfollow" @click=${this.unfollow}></input>` :
|
||||
html`<input type="button" value="Follow" @click=${this.follow}></input>`;
|
||||
html`<button class="w3-button w3-dark-grey" @click=${this.unfollow}>Unfollow</button>` :
|
||||
html`<button class="w3-button w3-dark-grey" @click=${this.follow}>Follow</button>`;
|
||||
}
|
||||
if (this.id !== this.whoami &&
|
||||
this.blocking !== undefined) {
|
||||
block =
|
||||
this.blocking ?
|
||||
html`<input type="button" value="Unblock" @click=${this.unblock}></input>` :
|
||||
html`<input type="button" value="Block" @click=${this.block}></input>`;
|
||||
html`<button class="w3-button w3-dark-grey" @click=${this.unblock}>Unblock</button>` :
|
||||
html`<button class="w3-button w3-dark-grey" @click=${this.block}>Block</button>`;
|
||||
}
|
||||
let edit_profile = this.editing ? html`
|
||||
<div style="flex: 1 0 50%; display: flex; flex-direction: column">
|
||||
<div>
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name" value=${this.editing.name} @input=${event => this.editing = Object.assign({}, this.editing, {name: event.srcElement.value})}></input>
|
||||
</div>
|
||||
<div><label for="description">Description:</label></div>
|
||||
<textarea style="flex: 1 0" id="description" @input=${event => this.editing = Object.assign({}, this.editing, {description: event.srcElement.value})}>${this.editing.description}</textarea>
|
||||
<div>
|
||||
<label for="public_web_hosting">Public Web Hosting:</label>
|
||||
<input type="checkbox" id="public_web_hosting" ?checked=${this.editing.publicWebHosting} @input=${event => self.editing = Object.assign({}, self.editing, {publicWebHosting: event.srcElement.checked})}></input>
|
||||
</div>
|
||||
<div>
|
||||
<input type="button" value="Attach Image" @click=${this.attach_image}></input>
|
||||
<div style="flex: 1 0 50%; display: flex; flex-direction: column; gap: 8px">
|
||||
<div class="w3-container">
|
||||
<div>
|
||||
<label for="name">Name:</label>
|
||||
<input class="w3-input w3-dark-grey" type="text" id="name" value=${this.editing.name} @input=${event => this.editing = Object.assign({}, this.editing, {name: event.srcElement.value})}></input>
|
||||
</div>
|
||||
<div><label for="description">Description:</label></div>
|
||||
<textarea class="w3-input w3-dark-grey" style="resize: vertical" rows="8" id="description" @input=${event => this.editing = Object.assign({}, this.editing, {description: event.srcElement.value})}>${this.editing.description}</textarea>
|
||||
<div>
|
||||
<label for="public_web_hosting">Public Web Hosting:</label>
|
||||
<input class="w3-check w3-dark-grey" type="checkbox" id="public_web_hosting" ?checked=${this.editing.publicWebHosting} @input=${event => self.editing = Object.assign({}, self.editing, {publicWebHosting: event.srcElement.checked})}></input>
|
||||
</div>
|
||||
<div>
|
||||
<button class="w3-button w3-dark-grey" @click=${this.attach_image}>Attach Image</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>` : null;
|
||||
let image = typeof(profile.image) == 'string' ? profile.image : profile.image?.link;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {css} from './lit-all.min.js';
|
||||
|
||||
export let styles = css`
|
||||
const tf = css`
|
||||
a:link {
|
||||
color: #bbf;
|
||||
}
|
||||
@ -46,9 +46,258 @@ div.img_caption::after {
|
||||
content: ' ±';
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid #fff;
|
||||
margin-left: 0px;
|
||||
padding-left: 8px;
|
||||
code {
|
||||
background-color: #444;
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
border: 1px dotted #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
blockquote {
|
||||
background-color: #607d8b;
|
||||
border-left: 4px solid #fff;
|
||||
padding: 8px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
`;
|
||||
|
||||
const w3 = css`
|
||||
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
|
||||
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
|
||||
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
|
||||
a{background-color:transparent}a:active,a:hover{outline-width:0}
|
||||
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
|
||||
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
|
||||
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
|
||||
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
|
||||
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
|
||||
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
|
||||
button,input{overflow:visible}button,select{text-transform:none}
|
||||
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
|
||||
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
|
||||
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
|
||||
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
|
||||
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
|
||||
[type=checkbox],[type=radio]{padding:0}
|
||||
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
|
||||
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
|
||||
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
|
||||
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
|
||||
/* End extract */
|
||||
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
|
||||
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
|
||||
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
|
||||
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
|
||||
hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
|
||||
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
|
||||
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
|
||||
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
|
||||
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
|
||||
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
|
||||
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
|
||||
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
|
||||
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
|
||||
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
|
||||
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
|
||||
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
|
||||
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
|
||||
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
|
||||
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
|
||||
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
|
||||
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
|
||||
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
|
||||
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
|
||||
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
|
||||
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
|
||||
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
|
||||
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
|
||||
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
|
||||
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
|
||||
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
|
||||
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
|
||||
.w3-main,#main{transition:margin-left .4s}
|
||||
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
|
||||
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
|
||||
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
|
||||
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
|
||||
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
|
||||
.w3-bar .w3-button{white-space:normal}
|
||||
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
|
||||
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
|
||||
.w3-responsive{display:block;overflow-x:auto}
|
||||
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
|
||||
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
|
||||
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
|
||||
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
|
||||
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
|
||||
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
|
||||
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
|
||||
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
|
||||
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
|
||||
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
|
||||
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
|
||||
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
|
||||
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
|
||||
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
|
||||
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
|
||||
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
|
||||
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
|
||||
@media (max-width:1205px){.w3-auto{max-width:95%}}
|
||||
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
|
||||
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
|
||||
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
|
||||
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
|
||||
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
|
||||
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
|
||||
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
|
||||
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
|
||||
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
|
||||
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
|
||||
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
|
||||
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
|
||||
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
|
||||
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
|
||||
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
|
||||
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
||||
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
||||
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
|
||||
.w3-display-position{position:absolute}
|
||||
.w3-circle{border-radius:50%}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
|
||||
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
|
||||
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
|
||||
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
|
||||
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
|
||||
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
|
||||
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
|
||||
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
|
||||
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
|
||||
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
|
||||
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
|
||||
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
|
||||
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
|
||||
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
|
||||
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
|
||||
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
|
||||
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
|
||||
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
|
||||
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
|
||||
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
|
||||
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
|
||||
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
|
||||
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
|
||||
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
|
||||
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
|
||||
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
|
||||
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
|
||||
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
|
||||
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
|
||||
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
|
||||
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
|
||||
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
|
||||
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
|
||||
.w3-left{float:left!important}.w3-right{float:right!important}
|
||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
|
||||
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
|
||||
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
|
||||
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
|
||||
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
|
||||
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
|
||||
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
|
||||
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
|
||||
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
|
||||
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
|
||||
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
|
||||
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
|
||||
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
|
||||
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
|
||||
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
|
||||
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
|
||||
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
|
||||
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
|
||||
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
|
||||
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
|
||||
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
|
||||
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
|
||||
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
|
||||
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
|
||||
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
|
||||
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
|
||||
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
|
||||
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
|
||||
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
|
||||
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
|
||||
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
|
||||
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
|
||||
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
|
||||
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
|
||||
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
|
||||
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
|
||||
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
|
||||
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
|
||||
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
|
||||
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
|
||||
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
|
||||
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
|
||||
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
|
||||
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
|
||||
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
|
||||
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
|
||||
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
|
||||
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
|
||||
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
|
||||
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
|
||||
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
|
||||
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
|
||||
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
|
||||
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
|
||||
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
|
||||
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
|
||||
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
|
||||
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
|
||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
`;
|
||||
|
||||
export let styles = [tf, w3];
|
@ -1,5 +1,6 @@
|
||||
import {LitElement, html} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
|
||||
class TfTabConnectionsElement extends LitElement {
|
||||
static get properties() {
|
||||
@ -12,6 +13,8 @@ class TfTabConnectionsElement extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
let self = this;
|
||||
@ -55,7 +58,7 @@ class TfTabConnectionsElement extends LitElement {
|
||||
let self = this;
|
||||
return html`
|
||||
<li>
|
||||
<input type="button" @click=${() => self._tunnel(connection.tunnel.id, connection.pubkey)} value="Connect"></input>
|
||||
<button class="w3-button w3-dark-grey" @click=${() => self._tunnel(connection.tunnel.id, connection.pubkey)}>Connect</button>
|
||||
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user> 📡
|
||||
</li>
|
||||
`;
|
||||
@ -64,7 +67,7 @@ class TfTabConnectionsElement extends LitElement {
|
||||
render_broadcast(connection) {
|
||||
return html`
|
||||
<li>
|
||||
<input type="button" @click=${() => tfrpc.rpc.connect(connection)} value="Connect"></input>
|
||||
<button class="w3-button w3-dark-grey" @click=${() => tfrpc.rpc.connect(connection)}>Connect</button>
|
||||
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
|
||||
${this.render_connection_summary(connection)}
|
||||
</li>
|
||||
@ -78,7 +81,7 @@ class TfTabConnectionsElement extends LitElement {
|
||||
|
||||
render_connection(connection) {
|
||||
return html`
|
||||
<input type="button" @click=${() => tfrpc.rpc.closeConnection(connection.id)} value="Close"></input>
|
||||
<button class="w3-button w3-dark-grey" @click=${() => tfrpc.rpc.closeConnection(connection.id)}>Close</button>
|
||||
<tf-user id=${connection.id} .users=${this.users}></tf-user>
|
||||
${connection.tunnel !== undefined ? '🚇' : html`(${connection.host}:${connection.port})`}
|
||||
<ul>
|
||||
@ -91,35 +94,35 @@ class TfTabConnectionsElement extends LitElement {
|
||||
render() {
|
||||
let self = this;
|
||||
return html`
|
||||
<h2>New Connection</h2>
|
||||
<div style="display: flex; flex-direction: column">
|
||||
<textarea id="code"></textarea>
|
||||
<div class="w3-container">
|
||||
<h2>New Connection</h2>
|
||||
<textarea class="w3-input w3-dark-grey" id="code"></textarea>
|
||||
<button class="w3-button w3-dark-grey" @click=${() => tfrpc.rpc.connect(self.renderRoot.getElementById('code').value)}>Connect</button>
|
||||
<h2>Broadcasts</h2>
|
||||
<ul>
|
||||
${this.broadcasts.filter(x => x.address).map(x => self.render_broadcast(x))}
|
||||
</ul>
|
||||
<h2>Connections</h2>
|
||||
<ul>
|
||||
${this.connections.filter(x => x.tunnel === undefined).map(x => html`
|
||||
<li>${this.render_connection(x)}</li>
|
||||
`)}
|
||||
</ul>
|
||||
<h2>Stored Connections (WIP)</h2>
|
||||
<ul>
|
||||
${this.stored_connections.map(x => html`
|
||||
<li>
|
||||
<button class="w3-button w3-dark-grey" @click=${() => self.forget_stored_connection(x)}>Forget</button>
|
||||
<button class="w3-button w3-dark-grey" @click=${() => tfrpc.rpc.connect(x)}>Connect</button>
|
||||
${x.address}:${x.port} <tf-user id=${x.pubkey} .users=${self.users}></tf-user>
|
||||
</li>
|
||||
`)}
|
||||
</ul>
|
||||
<h2>Local Accounts</h2>
|
||||
<ul>
|
||||
${this.identities.map(x => html`<li><tf-user id=${x} .users=${this.users}></tf-user></li>`)}
|
||||
</ul>
|
||||
</div>
|
||||
<input type="button" @click=${() => tfrpc.rpc.connect(self.renderRoot.getElementById('code').value)} value="Connect"></input>
|
||||
<h2>Broadcasts</h2>
|
||||
<ul>
|
||||
${this.broadcasts.filter(x => x.address).map(x => self.render_broadcast(x))}
|
||||
</ul>
|
||||
<h2>Connections</h2>
|
||||
<ul>
|
||||
${this.connections.filter(x => x.tunnel === undefined).map(x => html`
|
||||
<li>${this.render_connection(x)}</li>
|
||||
`)}
|
||||
</ul>
|
||||
<h2>Stored Connections (WIP)</h2>
|
||||
<ul>
|
||||
${this.stored_connections.map(x => html`
|
||||
<li>
|
||||
<input type="button" @click=${() => self.forget_stored_connection(x)} value="Forget"></input>
|
||||
<input type="button" @click=${() => tfrpc.rpc.connect(x)} value="Connect"></input>
|
||||
${x.address}:${x.port} <tf-user id=${x.pubkey} .users=${self.users}></tf-user>
|
||||
</li>
|
||||
`)}
|
||||
</ul>
|
||||
<h2>Local Accounts</h2>
|
||||
<ul>
|
||||
${this.identities.map(x => html`<li><tf-user id=${x} .users=${this.users}></tf-user></li>`)}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -183,7 +183,9 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
let more;
|
||||
if (!this.hash.startsWith('#@') && !this.hash.startsWith('#%')) {
|
||||
more = html`
|
||||
<input type="button" value="Load More" @click=${this.load_more}></input>
|
||||
<p>
|
||||
<button class="w3-button w3-dark-grey" @click=${this.load_more}>Load More</button>
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
|
@ -66,7 +66,7 @@ class TfTabNewsElement extends LitElement {
|
||||
}
|
||||
counts[type] = (counts[type] || 0) + 1;
|
||||
}
|
||||
return 'Show New: ' + Object.keys(counts).sort().map(x => (counts[x].toString() + ' ' + x + 's')).join(', ');
|
||||
return '↻ Show New: ' + Object.keys(counts).sort().map(x => (counts[x].toString() + ' ' + x + 's')).join(', ');
|
||||
}
|
||||
|
||||
draft(event) {
|
||||
@ -106,8 +106,9 @@ class TfTabNewsElement extends LitElement {
|
||||
let profile = this.hash.startsWith('#@') ?
|
||||
html`<tf-profile id=${this.hash.substring(1)} whoami=${this.whoami} .users=${this.users}></tf-profile>` : undefined;
|
||||
return html`
|
||||
<div><input type="button" value=${this.new_messages_text()} @click=${this.show_more}></input></div>
|
||||
<a target="_top" href="#" ?hidden=${this.hash.length <= 1}>🏠Home</a>
|
||||
<p class="w3-bar">
|
||||
<button class="w3-bar-item w3-button w3-dark-grey" @click=${this.show_more}>${this.new_messages_text()}</button>
|
||||
</p>
|
||||
<div>Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!</div>
|
||||
<div><tf-compose id="tf-compose" whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} @tf-draft=${this.draft}></tf-compose></div>
|
||||
${profile}
|
||||
|
@ -99,9 +99,9 @@ class TfTabQueryElement extends LitElement {
|
||||
}
|
||||
let self = this;
|
||||
return html`
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<textarea id="search" rows=8 style="flex: 1" @keydown=${this.search_keydown}>${this.query}</textarea>
|
||||
<input type="button" value="Execute" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}></input>
|
||||
<div style="display: flex; flex-direction: row; gap: 4px">
|
||||
<textarea id="search" rows=8 class="w3-input w3-dark-grey" style="flex: 1; resize: vertical" @keydown=${this.search_keydown}>${this.query}</textarea>
|
||||
<button class="w3-button w3-dark-grey" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Execute</button>
|
||||
</div>
|
||||
<div ?hidden=${this.duration === undefined}>Took ${this.duration / 1000.0} seconds.</div>
|
||||
<div ?hidden=${this.duration !== undefined}>Executing...</div>
|
||||
|
@ -75,9 +75,9 @@ class TfTabSearchElement extends LitElement {
|
||||
}
|
||||
let self = this;
|
||||
return html`
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<input type="text" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
|
||||
<input type="button" value="Search" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}></input>
|
||||
<div style="display: flex; flex-direction: row; gap: 4px">
|
||||
<input type="text" class="w3-input w3-dark-grey" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
|
||||
<button class="w3-button w3-dark-grey" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button>
|
||||
</div>
|
||||
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} @tf-expand=${this.on_expand}></tf-news>
|
||||
`;
|
||||
|
@ -46,14 +46,14 @@ function image(node, entering) {
|
||||
}
|
||||
|
||||
export function markdown(md) {
|
||||
var reader = new commonmark.Parser({safe: true});
|
||||
var writer = new commonmark.HtmlRenderer();
|
||||
let reader = new commonmark.Parser({safe: true});
|
||||
let writer = new commonmark.HtmlRenderer();
|
||||
writer.image = image;
|
||||
var parsed = reader.parse(md || '');
|
||||
let parsed = reader.parse(md || '');
|
||||
parsed = linkify.transform(parsed);
|
||||
parsed = hashtagify.transform(parsed);
|
||||
var walker = parsed.walker();
|
||||
var event, node;
|
||||
let walker = parsed.walker();
|
||||
let event, node;
|
||||
while ((event = walker.next())) {
|
||||
node = event.node;
|
||||
if (event.entering) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "👋",
|
||||
"previous": "&xc5LDQt9bWp6wrPxZhvSGiMXjweSK1YbO2DReM7/2ic=.sha256"
|
||||
"previous": "&zFISmRDAv+SXFonfZ9/sHNhrmMe+poTU22gwZzuSkT4=.sha256"
|
||||
}
|
@ -35,7 +35,7 @@
|
||||
<i class="fa-brands fa-windows w3-xlarge"></i>
|
||||
</p>
|
||||
<a class="w3-button w3-black w3-padding-large" href="https://www.tildefriends.net/~cory/releases/"><i class="fa fa-download"></i> Download</a>
|
||||
<a class="w3-button w3-black w3-padding-large" href="https://www.tildefriends.net/~cory/apps/"><i class="fa fa-link"></i> Try Some Apps</a>
|
||||
<a class="w3-button w3-black w3-padding-large" href="https://www.tildefriends.net/~cory/apps/"><i class="fa fa-link"></i> Try It</a>
|
||||
</div>
|
||||
<div class="w3-col l4 m6">
|
||||
<img src="tildefriends.png" class="w3-image w3-right w3-hide-small">
|
||||
@ -120,7 +120,7 @@
|
||||
<i class="fa fa-database w3-text-red w3-jumbo"></i>
|
||||
<p>SQLite</p>
|
||||
</a>
|
||||
<a href="https://libuv.org/" class="w3-col s3">
|
||||
<a href="https://github.com/libuv/libuv" class="w3-col s3">
|
||||
<i class="fa fa-bolt w3-text-yellow w3-jumbo"></i>
|
||||
<p>libuv</p>
|
||||
</a>
|
||||
@ -150,11 +150,11 @@
|
||||
<i class="fa fa-keyboard w3-text-indigo w3-jumbo"></i>
|
||||
<p>CodeMirror</p>
|
||||
</a>
|
||||
<a href="https://speedscope.app/" class="w3-col s3">
|
||||
<a href="https://github.com/jlfwong/speedscope/" class="w3-col s3">
|
||||
<i class="fa fa-microscope w3-text-orange w3-jumbo"></i>
|
||||
<p>Speedscope</p>
|
||||
</a>
|
||||
<a href="https://lit.dev/" class="w3-col s3">
|
||||
<a href="https://github.com/lit/lit/" class="w3-col s3">
|
||||
<i class="fa fa-fire w3-text-cyan w3-jumbo"></i>
|
||||
<p>Lit</p>
|
||||
</a>
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "📝",
|
||||
"previous": "&JHopifgZn2TsiMCQY8HUTlDqHEDDviiu2ifvr8HHNwo=.sha256"
|
||||
"previous": "&qrcrBeaWg89ikgql9hXdr68krkg+5NZkmwTbpodEW4U=.sha256"
|
||||
}
|
@ -13,7 +13,7 @@ function markdown(md) {
|
||||
if (node.type === 'link') {
|
||||
if (node.destination.indexOf(':') == -1 &&
|
||||
node.destination.indexOf('/') == -1) {
|
||||
node.destination = `#${this.wiki?.name}/${node.destination}`;
|
||||
node.destination = `${node.destination}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
8
apps/wiki/lit-all.min.js
vendored
8
apps/wiki/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -292,13 +292,14 @@ class TfCollectionsAppElement extends LitElement {
|
||||
<div class="toc ${self.wiki?.id === wiki.id ? 'selected' : ''}" style="white-space: nowrap; cursor: pointer" @click=${() => self.on_wiki_changed({detail: {value: wiki}})}>${wiki.name}</div>
|
||||
<ul>
|
||||
${Object.values(self.wiki_docs || {}).filter(doc => doc.parent === wiki?.id).sort((x, y) => x.name.localeCompare(y.name)).map(doc => html`
|
||||
<li class="toc ${self.wiki_doc?.id === doc.id ? 'selected' : ''}" style="white-space: nowrap; cursor: pointer" @click=${() => self.on_wiki_doc_changed({detail: {value: doc}})}>${doc.name}</li>
|
||||
<li class="toc ${self.wiki_doc?.id === doc.id ? 'selected' : ''}" style="white-space: nowrap; cursor: pointer; list-style: none; text-indent: -1rem" @click=${() => self.on_wiki_doc_changed({detail: {value: doc}})}>${doc?.private ? '🔒' : '📄'} ${doc.name}</li>
|
||||
`)}
|
||||
</ul>
|
||||
`)}
|
||||
</div>
|
||||
${this.wiki_doc && this.wiki_doc.parent === this.wiki?.id ? html`
|
||||
<tf-wiki-doc
|
||||
style="width: 100%"
|
||||
whoami=${this.whoami}
|
||||
.wiki=${this.wiki}
|
||||
.value=${this.wiki_doc}></tf-wiki-doc>
|
||||
|
@ -72,9 +72,7 @@ class TfWikiDocElement extends LitElement {
|
||||
}
|
||||
|
||||
thumbnail(md) {
|
||||
//let m = md ? md.match(/\!\[image:[^\]]+]\((\&.{44}\.sha256)\)/) : undefined;
|
||||
let m = md ? md.match(/.*\((\&.{44}\.sha256)\).*/) : undefined;
|
||||
console.log('thumb', m);
|
||||
let m = md ? md.match(/\!\[image:[^\]]+\]\((\&.{44}\.sha256)\).*/) : undefined;
|
||||
return m ? m[1] : undefined;
|
||||
}
|
||||
|
||||
@ -171,11 +169,22 @@ class TfWikiDocElement extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
humanSize(value) {
|
||||
let units = ['B', 'kB', 'MB', 'GB'];
|
||||
let i = 0;
|
||||
while (i < units.length - 1 && value >= 1024) {
|
||||
value /= 1024;
|
||||
i++;
|
||||
}
|
||||
return `${Math.round(value * 10) / 10} ${units[i]}`;
|
||||
}
|
||||
|
||||
async add_file(editor, file) {
|
||||
try {
|
||||
let self = this;
|
||||
let buffer = await file.arrayBuffer();
|
||||
let type = file.type;
|
||||
let insert;
|
||||
if (type.startsWith('image/')) {
|
||||
let best_buffer;
|
||||
let best_type;
|
||||
@ -188,12 +197,16 @@ class TfWikiDocElement extends LitElement {
|
||||
}
|
||||
buffer = best_buffer;
|
||||
type = best_type;
|
||||
let id = await tfrpc.rpc.store_blob(buffer);
|
||||
let name = type.split('/')[0] + ':' + file.name;
|
||||
insert = `\n`;
|
||||
} else {
|
||||
buffer = Array.from(new Uint8Array(buffer));
|
||||
let id = await tfrpc.rpc.store_blob(buffer);
|
||||
let name = file.name;
|
||||
insert = `\n[${name}](${id}) (${this.humanSize(buffer.length)})`;
|
||||
}
|
||||
let id = await tfrpc.rpc.store_blob(buffer);
|
||||
let name = type.split('/')[0] + ':' + file.name;
|
||||
editor.value += `\n`;
|
||||
document.execCommand('insertText', false, insert);
|
||||
self.on_edit({srcElement: editor});
|
||||
} catch(e) {
|
||||
alert(e?.message);
|
||||
@ -203,13 +216,10 @@ class TfWikiDocElement extends LitElement {
|
||||
paste(event) {
|
||||
let self = this;
|
||||
for (let item of event.clipboardData.items) {
|
||||
if (item.type?.startsWith('image/')) {
|
||||
let file = item.getAsFile();
|
||||
if (!file) {
|
||||
continue;
|
||||
}
|
||||
let file = item.getAsFile();
|
||||
if (file) {
|
||||
self.add_file(event.srcElement, file);
|
||||
break;
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -225,6 +235,12 @@ class TfWikiDocElement extends LitElement {
|
||||
let self = this;
|
||||
let thumbnail_ref = this.thumbnail(this.blob);
|
||||
return html`
|
||||
<style>
|
||||
a:link { color: #268bd2 }
|
||||
a:visited { color: #6c71c4 }
|
||||
a:hover { color: #859900 }
|
||||
a:active { color: #2aa198 }
|
||||
</style>
|
||||
<div style="display: inline-flex; flex-direction: row">
|
||||
<button ?disabled=${!this.whoami || this.is_editing} @click=${() => self.is_editing = true}>Edit</button>
|
||||
<button ?disabled=${this.blob == this.blob_original} @click=${this.on_save_draft}>Save Draft</button>
|
||||
|
17
core/app.js
17
core/app.js
@ -67,11 +67,8 @@ function socket(request, response, client) {
|
||||
if (process && process.task) {
|
||||
process.task.kill();
|
||||
}
|
||||
}
|
||||
|
||||
response.onError = async function(error) {
|
||||
if (process && process.task) {
|
||||
process.task.kill();
|
||||
if (process) {
|
||||
process.timeout = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,7 +154,7 @@ function socket(request, response, client) {
|
||||
process.lastPing = now;
|
||||
}
|
||||
|
||||
if (again) {
|
||||
if (again && process.timeout) {
|
||||
setTimeout(ping, process.timeout);
|
||||
}
|
||||
}
|
||||
@ -202,11 +199,9 @@ function socket(request, response, client) {
|
||||
}
|
||||
}
|
||||
|
||||
if (refresh) {
|
||||
return {
|
||||
'Set-Cookie': `session=${refresh.token}; path=/; Max-Age=${refresh.interval}; Secure; SameSite=Strict`,
|
||||
};
|
||||
}
|
||||
response.upgrade(100, refresh ? {
|
||||
'Set-Cookie': `session=${refresh.token}; path=/; Max-Age=${refresh.interval}; Secure; SameSite=Strict`,
|
||||
} : {});
|
||||
}
|
||||
|
||||
export { socket, App };
|
||||
|
@ -45,12 +45,12 @@ function readSession(session) {
|
||||
let jwt_parts = session?.split('.');
|
||||
if (jwt_parts?.length === 3) {
|
||||
let [header, payload, signature] = jwt_parts;
|
||||
header = JSON.parse(base64Decode(unb64url(header)));
|
||||
header = JSON.parse(utf8Decode(base64Decode(unb64url(header))));
|
||||
if (header.typ === 'JWT' && header.alg === 'HS256') {
|
||||
signature = unb64url(signature);
|
||||
let id = ssb.getIdentities(':auth');
|
||||
if (id?.length && ssb.hmacsha256verify(id[0], payload, signature)) {
|
||||
let result = JSON.parse(base64Decode(unb64url(payload)));
|
||||
let result = JSON.parse(utf8Decode(base64Decode(unb64url(payload))));
|
||||
let now = new Date().valueOf()
|
||||
if (now < result.exp) {
|
||||
print(`JWT valid for another ${(result.exp - now) / 1000} seconds.`);
|
||||
@ -64,8 +64,6 @@ function readSession(session) {
|
||||
} else {
|
||||
print('Invalid JWT header.');
|
||||
}
|
||||
} else {
|
||||
print('No session JWT.');
|
||||
}
|
||||
}
|
||||
|
||||
|
311
core/client.js
311
core/client.js
@ -1,5 +1,6 @@
|
||||
import {LitElement, html, css, svg} from '/static/lit/lit-all.min.js';
|
||||
|
||||
let cm6;
|
||||
let gSocket;
|
||||
|
||||
let gCurrentFile;
|
||||
@ -104,28 +105,23 @@ class TfNavigationElement extends LitElement {
|
||||
render_permissions() {
|
||||
if (this.show_permissions) {
|
||||
return html`
|
||||
<link type="text/css" rel="stylesheet" href="/static/w3.css">
|
||||
<div style="position: absolute; top: 0; padding: 0; margin: 0; z-index: 100; display: flex; justify-content: center; width: 100%">
|
||||
<div style="background-color: #444; padding: 1em; margin: 0 auto; border-left: 4px solid #fff; border-right: 4px solid #fff; border-bottom: 4px solid #fff">
|
||||
<div>This app has the following permissions:</div>
|
||||
${Object.keys(this.permissions).map(key => html`
|
||||
<div>
|
||||
<span>${key}</span>: ${this.permissions[key] ? '✅ Allowed' : '❌ Denied'}
|
||||
<button @click=${() => this.reset_permission(key)} style='min-width: 3em; min-height: 3em">Reset</button>
|
||||
<button @click=${() => this.reset_permission(key)} class='w3-button w3-red">Reset</button>
|
||||
</div>
|
||||
`)}
|
||||
<button @click=${() => this.show_permissions = false} style="min-width: 3em; min-height: 3em">Close</button>
|
||||
<button @click=${() => this.show_permissions = false} class="w3-button w3-blue">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
set_access_key_title(event) {
|
||||
if (!event.srcElement.title) {
|
||||
event.srcElement.title = `${event.srcElement.dataset.tip} [${(event.srcElement.accessKeyLabel || event.srcElement.accessKey).toUpperCase()}]`;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
return html`
|
||||
@ -148,10 +144,10 @@ class TfNavigationElement extends LitElement {
|
||||
<div style="margin: 4px; display: flex; flex-direction: row; flex-wrap: nowrap; gap: 3px; align-items: center">
|
||||
<span style="cursor: pointer" @click=${() => this.show_version = !this.show_version}>😎</span>
|
||||
<span ?hidden=${!this.show_version} style="flex: 0 0; white-space: nowrap" title=${this.version?.name + ' ' + Object.entries(this.version || {}).filter(x => ['name', 'number'].indexOf(x[0]) == -1).map(x => `\n* ${x[0]}: ${x[1]}`)}>${this.version?.number}</span>
|
||||
<a accesskey="h" @mouseover=${this.set_access_key_title} data-tip="Open home app." href="/" style="color: #fff; white-space: nowrap">TF</a>
|
||||
<a accesskey="a" @mouseover=${this.set_access_key_title} data-tip="Open apps list." href="/~core/apps/">apps</a>
|
||||
<a accesskey="e" @mouseover=${this.set_access_key_title} data-tip="Toggle the app editor." href="#" @click=${this.toggle_edit}>edit</a>
|
||||
<a accesskey="p" @mouseover=${this.set_access_key_title} data-tip="View and change permissions." href="#" @click=${() => self.show_permissions = !self.show_permissions}>🎛️</a>
|
||||
<a accesskey="h" @mouseover=${set_access_key_title} data-tip="Open home app." href="/" style="color: #fff; white-space: nowrap">TF</a>
|
||||
<a accesskey="a" @mouseover=${set_access_key_title} data-tip="Open apps list." href="/~core/apps/">apps</a>
|
||||
<a accesskey="e" @mouseover=${set_access_key_title} data-tip="Toggle the app editor." href="#" @click=${this.toggle_edit}>edit</a>
|
||||
<a accesskey="p" @mouseover=${set_access_key_title} data-tip="View and change permissions." href="#" @click=${() => self.show_permissions = !self.show_permissions}>🎛️</a>
|
||||
<span style="display: inline-block; vertical-align: top; white-space: pre; color: ${this.status.color ?? kErrorColor}">${this.status.message}</span>
|
||||
<span id="requests"></span>
|
||||
${this.render_permissions()}
|
||||
@ -207,7 +203,7 @@ class TfFilesElement extends LitElement {
|
||||
let buffer = await file.arrayBuffer();
|
||||
let text = new TextDecoder('latin1').decode(buffer);
|
||||
gFiles[file.name] = {
|
||||
doc: new CodeMirror.Doc(text, guessMode(file.name)),
|
||||
doc: new cm6.EditorState.create({doc: text, extensions: cm6.extensions}),
|
||||
buffer: buffer,
|
||||
generation: -1,
|
||||
isNew: true,
|
||||
@ -288,32 +284,20 @@ class TfFilesPaneElement extends LitElement {
|
||||
render() {
|
||||
let self = this;
|
||||
let expander = this.expanded ?
|
||||
html`<span @click=${() => self.set_expanded(false)} class="expander">«</span>` :
|
||||
html`<span @click=${() => self.set_expanded(true)} class="expander">»</span>`;
|
||||
html`<div class="w3-button w3-bar-item w3-blue" style="flex: 0 0 auto; display: flex; flex-direction: row" @click=${() => self.set_expanded(false)}>
|
||||
<span style="flex: 1 1" font-weight: bold; text-align: center; flex: 1">Files</span>
|
||||
<span style="flex: 0 0">«</span>
|
||||
</div>` :
|
||||
html`<div class="w3-button w3-bar-item w3-blue" @click=${() => self.set_expanded(true)}>»</div>`;
|
||||
let content = html`
|
||||
<div id="files_content">
|
||||
<tf-files .files=${self.files} current=${self.current} @file_click=${event => openFile(event.detail.file)}></tf-files>
|
||||
<br>
|
||||
<div><button @click=${() => newFile()}>New File</button></div>
|
||||
<div><button @click=${() => removeFile()}>Remove File</button></div>
|
||||
</div>
|
||||
<tf-files style="flex: 1 1; overflow: auto" .files=${self.files} current=${self.current} @file_click=${event => openFile(event.detail.file)}></tf-files>
|
||||
<div><button class="w3-bar-item w3-button w3-blue" style="width: 100%; flex: 0 0" @click=${() => newFile()} accesskey="n" @mouseover=${set_access_key_title} data-tip="Add a new, empty file to the app">📄 New File</button></div>
|
||||
<div><button class="w3-bar-item w3-button w3-blue" style="width: 100%; flex: 0 0" @click=${() => removeFile()} accesskey="r" @mouseover=${set_access_key_title} data-tip="Remove the selected file from the app">🚮 Remove File</button></div>
|
||||
`;
|
||||
return html`
|
||||
<style>
|
||||
.expander {
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
right: 0;
|
||||
flex: 0;
|
||||
padding: 0.25em;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<div style="display: flex; flex-direction: row">
|
||||
${this.expanded ? html`<span style="font-weight: bold; text-align: center; flex: 1">Files</span>` : undefined}
|
||||
${expander}
|
||||
</div>
|
||||
<link type="text/css" rel="stylesheet" href="/static/w3.css">
|
||||
<div style="display: flex; flex-direction: column; height: 100%">
|
||||
${expander}
|
||||
${this.expanded ? content : undefined}
|
||||
</div>
|
||||
`;
|
||||
@ -441,7 +425,7 @@ function is_edit_only() {
|
||||
return window.location.search == '?editonly=1' || window.innerWidth < 1024;
|
||||
}
|
||||
|
||||
function edit() {
|
||||
async function edit() {
|
||||
if (editing()) {
|
||||
return;
|
||||
}
|
||||
@ -450,33 +434,17 @@ function edit() {
|
||||
document.getElementById("editPane").style.display = 'flex';
|
||||
document.getElementById('viewPane').style.display = is_edit_only() ? 'none' : 'flex';
|
||||
|
||||
ensureLoaded([
|
||||
{tagName: "script", attributes: {src: "/codemirror/codemirror.min.js"}},
|
||||
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/base16-dark.min.css"}},
|
||||
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/matchesonscrollbar.min.css"}},
|
||||
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/dialog.min.css"}},
|
||||
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/codemirror.min.css"}},
|
||||
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/lint.css"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/trailingspace.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/dialog.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/search.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/searchcursor.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/jump-to-line.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/matchesonscrollbar.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/annotatescrollbar.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/javascript.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/css.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/xml.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/htmlmixed.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/lint.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/jshint.min.js"}},
|
||||
{tagName: "script", attributes: {src: "/codemirror/javascript-lint.min.js"}},
|
||||
], function() {
|
||||
load().catch(function(error) {
|
||||
alert(error);
|
||||
closeEditor();
|
||||
});
|
||||
});
|
||||
try {
|
||||
if (!gEditor) {
|
||||
cm6 = await import('/codemirror/cm6.js');
|
||||
gEditor = cm6.TildeFriendsEditorView(document.getElementById("editor"));
|
||||
}
|
||||
gEditor.onDocChange = updateFiles;
|
||||
await load();
|
||||
} catch (error) {
|
||||
alert(`${error.message}\n\n${error.stack}`);
|
||||
closeEditor();
|
||||
}
|
||||
}
|
||||
|
||||
function trace() {
|
||||
@ -497,73 +465,49 @@ function loadFile(name, id) {
|
||||
}
|
||||
return response.text();
|
||||
}).then(function(text) {
|
||||
gFiles[name].doc = new CodeMirror.Doc(text, guessMode(name));
|
||||
gFiles[name].doc = cm6.EditorState.create({doc: text, extensions: cm6.extensions});
|
||||
gFiles[name].original = gFiles[name].doc.doc.toString();
|
||||
if (!Object.values(gFiles).some(x => !x.doc)) {
|
||||
openFile(Object.keys(gFiles).sort()[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function load(path) {
|
||||
return fetch((path || url()) + 'view').then(function(response) {
|
||||
if (!response.ok) {
|
||||
if (response.status == 404) {
|
||||
return null;
|
||||
} else {
|
||||
throw new Error(response.status + ' ' + response.statusText);
|
||||
}
|
||||
}
|
||||
return response.json();
|
||||
}).then(function(json) {
|
||||
if (!gEditor) {
|
||||
gEditor = CodeMirror.fromTextArea(document.getElementById("editor"), {
|
||||
'theme': 'base16-dark',
|
||||
'lineNumbers': true,
|
||||
'tabSize': 4,
|
||||
'indentUnit': 4,
|
||||
'indentWithTabs': true,
|
||||
'showTrailingSpace': true,
|
||||
'gutters': ['CodeMirror-lint-markers'],
|
||||
'mode': {'js': 'javascript'}[(path || url()).split('.').pop()],
|
||||
'lint': {
|
||||
'options': {
|
||||
esversion: 2021,
|
||||
varstmt: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
gEditor.on('changes', function() {
|
||||
updateFiles();
|
||||
});
|
||||
}
|
||||
gFiles = {};
|
||||
let isApp = false;
|
||||
let promises = [];
|
||||
async function load(path) {
|
||||
let response = await fetch((path || url()) + 'view');
|
||||
let json;
|
||||
if (response.ok) {
|
||||
json = await response.json();
|
||||
} else if (response.status != 404) {
|
||||
throw new Error(response.status + ' ' + response.statusText);
|
||||
}
|
||||
gFiles = {};
|
||||
let isApp = false;
|
||||
let promises = [];
|
||||
|
||||
if (json && json['type'] == 'tildefriends-app') {
|
||||
isApp = true;
|
||||
Object.keys(json['files']).forEach(function(name) {
|
||||
gFiles[name] = {};
|
||||
promises.push(loadFile(name, json['files'][name]));
|
||||
});
|
||||
if (Object.keys(json['files']).length == 0) {
|
||||
document.getElementById("editPane").style.display = 'flex';
|
||||
}
|
||||
gApp = json;
|
||||
gApp.emoji = gApp.emoji || '📦';
|
||||
document.getElementById('icon').value = gApp.emoji;
|
||||
}
|
||||
if (!isApp) {
|
||||
if (json && json['type'] == 'tildefriends-app') {
|
||||
isApp = true;
|
||||
Object.keys(json['files']).forEach(function(name) {
|
||||
gFiles[name] = {};
|
||||
promises.push(loadFile(name, json['files'][name]));
|
||||
});
|
||||
if (Object.keys(json['files']).length == 0) {
|
||||
document.getElementById("editPane").style.display = 'flex';
|
||||
let text = '// New script.\n';
|
||||
gCurrentFile = 'app.js';
|
||||
gFiles[gCurrentFile] = {
|
||||
doc: new CodeMirror.Doc(text, guessMode(gCurrentFile)),
|
||||
};
|
||||
openFile(gCurrentFile);
|
||||
}
|
||||
return Promise.all(promises);
|
||||
});
|
||||
gApp = json;
|
||||
gApp.emoji = gApp.emoji || '📦';
|
||||
document.getElementById('icon').innerHTML = gApp.emoji;
|
||||
}
|
||||
if (!isApp) {
|
||||
document.getElementById("editPane").style.display = 'flex';
|
||||
let text = '// New script.\n';
|
||||
gCurrentFile = 'app.js';
|
||||
gFiles[gCurrentFile] = {
|
||||
doc: cm6.EditorState.create({doc: text, extensions: cm6.extensions}),
|
||||
};
|
||||
openFile(gCurrentFile);
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
function closeEditor() {
|
||||
@ -579,8 +523,8 @@ function explodePath() {
|
||||
function save(save_to) {
|
||||
document.getElementById("save").disabled = true;
|
||||
if (gCurrentFile) {
|
||||
gFiles[gCurrentFile].doc = gEditor.getDoc();
|
||||
if (!gFiles[gCurrentFile].isNew && !gFiles[gCurrentFile].doc.isClean(gFiles[gCurrentFile].doc.changeGeneration())) {
|
||||
gFiles[gCurrentFile].doc = gEditor.state;
|
||||
if (!gFiles[gCurrentFile].isNew && !gFiles[gCurrentFile].doc.doc.toString() == gFiles[gCurrentFile].original) {
|
||||
delete gFiles[gCurrentFile].buffer;
|
||||
}
|
||||
}
|
||||
@ -598,7 +542,7 @@ function save(save_to) {
|
||||
let promises = [];
|
||||
for (let name of Object.keys(gFiles)) {
|
||||
let file = gFiles[name];
|
||||
if (!file.isNew && file.doc.isClean(file.generation)) {
|
||||
if (!file.isNew && file.doc.doc.toString() == file.original) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -609,7 +553,7 @@ function save(save_to) {
|
||||
headers: {
|
||||
'Content-Type': 'application/binary',
|
||||
},
|
||||
body: file.buffer ?? file.doc.getValue(),
|
||||
body: file.buffer ?? file.doc.doc.toString(),
|
||||
}).then(function(response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('Saving "' + name + '": ' + response.status + ' ' + response.statusText);
|
||||
@ -654,7 +598,7 @@ function save(save_to) {
|
||||
}).finally(function() {
|
||||
document.getElementById("save").disabled = false;
|
||||
Object.values(gFiles).forEach(function(file) {
|
||||
file.generation = file.doc.changeGeneration();
|
||||
file.original = file.doc.doc.toString();
|
||||
});
|
||||
updateFiles();
|
||||
});
|
||||
@ -664,7 +608,7 @@ function changeIcon() {
|
||||
let value = prompt('Enter a new app icon emoji:');
|
||||
if (value !== undefined) {
|
||||
gApp.emoji = value || '📦';
|
||||
document.getElementById('icon').value = gApp.emoji;
|
||||
document.getElementById('icon').innerHTML = gApp.emoji;
|
||||
}
|
||||
}
|
||||
|
||||
@ -752,6 +696,8 @@ function api_requestPermission(permission, id) {
|
||||
let check = document.createElement('input');
|
||||
check.id = 'permissions_remember_check';
|
||||
check.type = 'checkbox';
|
||||
check.classList.add('w3-check');
|
||||
check.classList.add('w3-blue');
|
||||
div.appendChild(check);
|
||||
let label = document.createElement('label');
|
||||
label.htmlFor = check.id;
|
||||
@ -777,8 +723,8 @@ function api_requestPermission(permission, id) {
|
||||
div = document.createElement('div');
|
||||
for (let option of k_options) {
|
||||
let button = document.createElement('button');
|
||||
button.style.minWidth = '3em';
|
||||
button.style.minHeight = '3em';
|
||||
button.classList.add('w3-button');
|
||||
button.classList.add('w3-blue');
|
||||
button.innerText = option.text;
|
||||
button.id = option.id;
|
||||
button.onclick = function() {
|
||||
@ -1035,11 +981,13 @@ function connectSocket(path) {
|
||||
}
|
||||
|
||||
function openFile(name) {
|
||||
let newDoc = (name && gFiles[name]) ? gFiles[name].doc : new CodeMirror.Doc("", guessMode(name));
|
||||
let oldDoc = gEditor.swapDoc(newDoc);
|
||||
let newDoc = (name && gFiles[name]) ? gFiles[name].doc : cm6.EditorState.create({doc: "", extensions: cm6.extensions});
|
||||
let oldDoc = gEditor.state;
|
||||
gEditor.setState(newDoc);
|
||||
|
||||
if (gFiles[gCurrentFile]) {
|
||||
gFiles[gCurrentFile].doc = oldDoc;
|
||||
if (!gFiles[gCurrentFile].isNew && gFiles[gCurrentFile].doc.isClean(oldDoc.generation)) {
|
||||
if (!gFiles[gCurrentFile].isNew && gFiles[gCurrentFile].doc.doc.toString() == oldDoc.doc.toString()) {
|
||||
delete gFiles[gCurrentFile].buffer;
|
||||
}
|
||||
}
|
||||
@ -1052,7 +1000,7 @@ function updateFiles() {
|
||||
let files = document.getElementsByTagName("tf-files-pane")[0];
|
||||
if (files) {
|
||||
files.files = Object.fromEntries(Object.keys(gFiles).map(file => [file, {
|
||||
clean: gFiles[file].doc.isClean(gFiles[file].generation),
|
||||
clean: (file == gCurrentFile ? gEditor.state.doc.toString() : gFiles[file].doc.doc.toString()) == gFiles[file].original,
|
||||
}]));
|
||||
files.current = gCurrentFile;
|
||||
}
|
||||
@ -1061,7 +1009,7 @@ function updateFiles() {
|
||||
|
||||
function makeNewFile(name) {
|
||||
gFiles[name] = {
|
||||
doc: new CodeMirror.Doc("", guessMode(name)),
|
||||
doc: cm6.EditorState.create({extensions: cm6.extensions}),
|
||||
generation: -1,
|
||||
};
|
||||
openFile(name);
|
||||
@ -1081,6 +1029,97 @@ function removeFile() {
|
||||
}
|
||||
}
|
||||
|
||||
async function appExport() {
|
||||
let JsZip = (await import('/static/jszip.min.js')).default;
|
||||
let owner = window.location.pathname.split('/')[1].replace('~', '');
|
||||
let name = window.location.pathname.split('/')[2];
|
||||
let zip = new JsZip();
|
||||
zip.file(`${name}.json`, JSON.stringify({
|
||||
type: "tildefriends-app",
|
||||
emoji: gApp.emoji || '📦',
|
||||
}));
|
||||
for (let file of Object.keys(gFiles)) {
|
||||
zip.file(`${name}/${file}`, gFiles[file].buffer ?? gFiles[file].doc.doc.toString());
|
||||
}
|
||||
let content = await zip.generateAsync({
|
||||
type: 'blob',
|
||||
compression: 'DEFLATE',
|
||||
});
|
||||
let a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(content);
|
||||
a.download = `${owner}_${name}.zip`;
|
||||
a.click();
|
||||
}
|
||||
|
||||
async function save_file_to_blob_id(name, file) {
|
||||
console.log(`Saving ${name}.`);
|
||||
let response = await fetch('/save', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/binary',
|
||||
},
|
||||
body: file,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Saving "' + name + '": ' + response.status + ' ' + response.statusText);
|
||||
}
|
||||
let blob_id = await response.text();
|
||||
if (blob_id.charAt(0) == '/') {
|
||||
blob_id = blob_id.substr(1);
|
||||
}
|
||||
return blob_id;
|
||||
}
|
||||
|
||||
async function appImport() {
|
||||
let JsZip = (await import('/static/jszip.min.js')).default;
|
||||
let input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.click();
|
||||
input.onchange = async function() {
|
||||
try {
|
||||
for (let file of input.files) {
|
||||
if (file.type != 'application/zip') {
|
||||
console.log('This does not look like a .zip.');
|
||||
continue;
|
||||
}
|
||||
let buffer = await file.arrayBuffer();
|
||||
let zip = new JsZip();
|
||||
await zip.loadAsync(buffer);
|
||||
let app_object;
|
||||
let app_name;
|
||||
for (let [name, object] of Object.entries(zip.files)) {
|
||||
if (name.endsWith('.json') && name.indexOf('/') == -1) {
|
||||
try {
|
||||
let parsed = JSON.parse(await object.async('text'));
|
||||
if (parsed.type == 'tildefriends-app') {
|
||||
app_object = parsed;
|
||||
app_name = name.substring(0, name.length - '.json'.length);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (app_object) {
|
||||
app_object.files = {};
|
||||
for (let [name, object] of Object.entries(zip.files)) {
|
||||
if (!name.startsWith(app_name + '/') || name.endsWith('/')) {
|
||||
continue;
|
||||
}
|
||||
app_object.files[name.substring(app_name.length + '/'.length)] = await save_file_to_blob_id(name, await object.async('arrayBuffer'));
|
||||
}
|
||||
let path = '/' + await save_file_to_blob_id(`${app_name}.json`, JSON.stringify(app_object)) + '/';
|
||||
console.log('Redirecting to:', path);
|
||||
window.location.pathname = path;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
alert(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
window.addEventListener("hashchange", hashChange);
|
||||
window.addEventListener("focus", focus);
|
||||
@ -1092,6 +1131,8 @@ window.addEventListener("load", function() {
|
||||
document.getElementById('save').addEventListener('click', () => save());
|
||||
document.getElementById('icon').addEventListener('click', () => changeIcon());
|
||||
document.getElementById('delete').addEventListener('click', () => deleteApp());
|
||||
document.getElementById('export').addEventListener('click', () => appExport());
|
||||
document.getElementById('import').addEventListener('click', () => appImport());
|
||||
document.getElementById('trace_button').addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
trace();
|
||||
|
96
core/core.js
96
core/core.js
@ -2,9 +2,7 @@ import * as app from './app.js';
|
||||
import * as auth from './auth.js';
|
||||
import * as form from './form.js';
|
||||
import * as http from './http.js';
|
||||
import * as httpd from './httpd.js';
|
||||
|
||||
let gProcessIndex = 0;
|
||||
let gProcesses = {};
|
||||
let gStatsTimer = false;
|
||||
|
||||
@ -30,15 +28,18 @@ const k_magic_bytes = [
|
||||
{bytes: [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32], type: 'audio/mpeg'},
|
||||
{bytes: [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d], type: 'video/mp4'},
|
||||
{bytes: [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32], type: 'video/mp4'},
|
||||
{bytes: [0x4d, 0x54, 0x68, 0x64], type: 'audio/midi'},
|
||||
];
|
||||
|
||||
let k_static_files = [
|
||||
{uri: '/', path: 'index.html', type: 'text/html; charset=UTF-8'},
|
||||
{uri: '/style.css', type: 'text/css; charset=UTF-8'},
|
||||
{uri: '/favicon.png', type: 'image/png'},
|
||||
{uri: '/client.js', type: 'text/javascript; charset=UTF-8'},
|
||||
{uri: '/tfrpc.js', type: 'text/javascript; charset=UTF-8', headers: {'Access-Control-Allow-Origin': 'null'}},
|
||||
{uri: '/favicon.png', type: 'image/png'},
|
||||
{uri: '/jszip.min.js', type: 'text/javascript; charset=UTF-8'},
|
||||
{uri: '/robots.txt', type: 'text/plain; charset=UTF-8'},
|
||||
{uri: '/style.css', type: 'text/css; charset=UTF-8'},
|
||||
{uri: '/tfrpc.js', type: 'text/javascript; charset=UTF-8', headers: {'Access-Control-Allow-Origin': 'null'}},
|
||||
{uri: '/w3.css', type: 'text/css; charset=UTF-8'},
|
||||
];
|
||||
|
||||
const k_global_settings = {
|
||||
@ -125,8 +126,7 @@ function invoke(handlers, argv) {
|
||||
|
||||
function broadcastEvent(eventName, argv) {
|
||||
let promises = [];
|
||||
for (let i in gProcesses) {
|
||||
let process = gProcesses[i];
|
||||
for (let process of Object.values(gProcesses)) {
|
||||
if (process.eventHandlers[eventName]) {
|
||||
promises.push(invoke(process.eventHandlers[eventName], argv));
|
||||
}
|
||||
@ -137,8 +137,7 @@ function broadcastEvent(eventName, argv) {
|
||||
function broadcast(message) {
|
||||
let sender = this;
|
||||
let promises = [];
|
||||
for (let i in gProcesses) {
|
||||
let process = gProcesses[i];
|
||||
for (let process of Object.values(gProcesses)) {
|
||||
if (process != sender
|
||||
&& process.packageOwner == sender.packageOwner
|
||||
&& process.packageName == sender.packageName) {
|
||||
@ -151,9 +150,7 @@ function broadcast(message) {
|
||||
|
||||
function getUser(caller, process) {
|
||||
return {
|
||||
name: process.userName,
|
||||
key: process.key,
|
||||
index: process.index,
|
||||
packageOwner: process.packageOwner,
|
||||
packageName: process.packageName,
|
||||
credentials: process.credentials,
|
||||
@ -208,8 +205,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
print("Creating task for " + blobId + " " + key);
|
||||
process = {};
|
||||
process.key = key;
|
||||
process.index = gProcessIndex++;
|
||||
process.userName = 'user' + process.index;
|
||||
process.credentials = options.credentials || {};
|
||||
process.task = new Task();
|
||||
process.eventHandlers = {};
|
||||
@ -320,6 +315,10 @@ async function getProcessBlob(blobId, key, options) {
|
||||
throw Error(`Permission denied: ${permission}.`);
|
||||
}
|
||||
},
|
||||
app: {
|
||||
owner: options?.packageOwner,
|
||||
name: options?.packageName,
|
||||
},
|
||||
url: options?.url,
|
||||
}
|
||||
};
|
||||
@ -396,6 +395,24 @@ async function getProcessBlob(blobId, key, options) {
|
||||
return ssb.createIdentity(process.credentials.session.name);
|
||||
}
|
||||
};
|
||||
imports.ssb.addIdentity = function(id) {
|
||||
if (process.credentials &&
|
||||
process.credentials.session &&
|
||||
process.credentials.session.name) {
|
||||
return Promise.resolve(imports.core.permissionTest('ssb_id_add')).then(function() {
|
||||
return ssb.addIdentity(process.credentials.session.name, id);
|
||||
});
|
||||
}
|
||||
};
|
||||
imports.ssb.deleteIdentity = function(id) {
|
||||
if (process.credentials &&
|
||||
process.credentials.session &&
|
||||
process.credentials.session.name) {
|
||||
return Promise.resolve(imports.core.permissionTest('ssb_id_delete')).then(function() {
|
||||
return ssb.deleteIdentity(process.credentials.session.name, id);
|
||||
});
|
||||
}
|
||||
};
|
||||
imports.ssb.getOwnerIdentities = function() {
|
||||
if (options.packageOwner) {
|
||||
return ssb.getIdentities(options.packageOwner);
|
||||
@ -408,6 +425,15 @@ async function getProcessBlob(blobId, key, options) {
|
||||
return ssb.getIdentities(process.credentials.session.name);
|
||||
}
|
||||
};
|
||||
imports.ssb.getPrivateKey = function(id) {
|
||||
if (process.credentials &&
|
||||
process.credentials.session &&
|
||||
process.credentials.session.name) {
|
||||
return Promise.resolve(imports.core.permissionTest('ssb_id_export')).then(function() {
|
||||
return ssb.getPrivateKey(process.credentials.session.name, id);
|
||||
});
|
||||
}
|
||||
};
|
||||
imports.ssb.appendMessageWithIdentity = function(id, message) {
|
||||
if (process.credentials &&
|
||||
process.credentials.session &&
|
||||
@ -952,9 +978,12 @@ function stringResponse(response, data) {
|
||||
}
|
||||
|
||||
loadSettings().then(function() {
|
||||
let httpd_impl = (tildefriends.args.httpdc ? httpdc : httpd);
|
||||
httpd_impl.all("/login", auth.handler);
|
||||
httpd_impl.all("", function(request, response) {
|
||||
if (tildefriends.https_port && gGlobalSettings.http_redirect) {
|
||||
httpd.set_http_redirect(gGlobalSettings.http_redirect);
|
||||
}
|
||||
httpd.all("/login", auth.handler);
|
||||
httpd.all("/app/socket", app.socket);
|
||||
httpd.all("", function default_http_handler(request, response) {
|
||||
let match;
|
||||
if (request.uri === "/" || request.uri === "") {
|
||||
try {
|
||||
@ -986,16 +1015,6 @@ loadSettings().then(function() {
|
||||
return staticFileHandler(request, response, null, request.uri);
|
||||
} else if (match = /^(.*)(\/(?:save|delete)?)$/.exec(request.uri)) {
|
||||
return blobHandler(request, response, match[1], match[2]);
|
||||
} else if (match = /^\/trace$/.exec(request.uri)) {
|
||||
return stringResponse(response, trace());
|
||||
} else if (match = /^\/disconnections$/.exec(request.uri)) {
|
||||
return stringResponse(response, JSON.stringify(disconnectionsDebug(), null, 2));
|
||||
} else if (match = /^\/debug$/.exec(request.uri)) {
|
||||
return stringResponse(response, JSON.stringify(getDebug(), null, 2));
|
||||
} else if (match = /^\/hitches$/.exec(request.uri)) {
|
||||
return stringResponse(response, JSON.stringify(getHitches(), null, 2));
|
||||
} else if (match = /^\/mem$/.exec(request.uri)) {
|
||||
return stringResponse(response, JSON.stringify(getAllocations(), null, 2));
|
||||
} else if ((match = /^\/.well-known\/(.*)/.exec(request.uri)) && request.uri.indexOf("..") == -1) {
|
||||
return wellKnownHandler(request, response, match[1]);
|
||||
} else {
|
||||
@ -1004,8 +1023,29 @@ loadSettings().then(function() {
|
||||
return response.end(data);
|
||||
}
|
||||
});
|
||||
httpd_impl.registerSocketHandler("/app/socket", app.socket);
|
||||
httpd_impl.start(tildefriends.http_port);
|
||||
let port = httpd.start(tildefriends.http_port);
|
||||
if (tildefriends.args.out_http_port_file) {
|
||||
print("Writing the port file.");
|
||||
File.writeFile(tildefriends.args.out_http_port_file, port.toString() + '\n').then(function(r) {
|
||||
print("Wrote the port file:", tildefriends.args.out_http_port_file, r);
|
||||
}).catch(function() {
|
||||
print("Failed to write the port file.");
|
||||
});
|
||||
}
|
||||
|
||||
if (tildefriends.https_port) {
|
||||
async function start_tls() {
|
||||
const kCertificatePath = "data/httpd/certificate.pem";
|
||||
const kPrivateKeyPath = "data/httpd/privatekey.pem";
|
||||
let privateKey = utf8Decode(await File.readFile(kPrivateKeyPath));
|
||||
let certificate = utf8Decode(await File.readFile(kCertificatePath));
|
||||
let context = new TlsContext();
|
||||
context.setPrivateKey(privateKey);
|
||||
context.setCertificate(certificate);
|
||||
httpd.start(tildefriends.https_port, context);
|
||||
}
|
||||
start_tls();
|
||||
}
|
||||
}).catch(function(error) {
|
||||
print('Failed to load settings.');
|
||||
printError({print: print}, error);
|
||||
|
598
core/httpd.js
598
core/httpd.js
@ -1,598 +0,0 @@
|
||||
import * as core from './core.js';
|
||||
|
||||
let gHandlers = [];
|
||||
let gSocketHandlers = [];
|
||||
let gBadRequests = {};
|
||||
|
||||
const kRequestTimeout = 5000;
|
||||
const kStallTimeout = 60000;
|
||||
|
||||
function logError(error) {
|
||||
print("ERROR " + error);
|
||||
if (error.stackTrace) {
|
||||
print(error.stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
function addHandler(handler) {
|
||||
let added = false;
|
||||
for (let i in gHandlers) {
|
||||
if (gHandlers[i].path == handler.path) {
|
||||
gHandlers[i] = handler;
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!added) {
|
||||
gHandlers.push(handler);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
|
||||
function all(prefix, handler) {
|
||||
addHandler({
|
||||
owner: this,
|
||||
path: prefix,
|
||||
invoke: handler,
|
||||
});
|
||||
}
|
||||
|
||||
function registerSocketHandler(prefix, handler) {
|
||||
gSocketHandlers.push({
|
||||
owner: this,
|
||||
path: prefix,
|
||||
invoke: handler,
|
||||
});
|
||||
}
|
||||
|
||||
function Request(method, uri, version, headers, body, client) {
|
||||
this.method = method;
|
||||
let index = uri.indexOf("?");
|
||||
if (index != -1) {
|
||||
this.uri = uri.slice(0, index);
|
||||
this.query = uri.slice(index + 1);
|
||||
} else {
|
||||
this.uri = uri;
|
||||
this.query = undefined;
|
||||
}
|
||||
this.version = version || '';
|
||||
this.headers = headers;
|
||||
this.client = {peerName: client.peerName, tls: client.tls};
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
function findHandler(request) {
|
||||
let matchedHandler = null;
|
||||
for (let name in gHandlers) {
|
||||
let handler = gHandlers[name];
|
||||
if (request.uri == handler.path || request.uri.slice(0, handler.path.length + 1) == handler.path + '/') {
|
||||
matchedHandler = handler;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return matchedHandler;
|
||||
}
|
||||
|
||||
function findSocketHandler(request) {
|
||||
let matchedHandler = null;
|
||||
for (let name in gSocketHandlers) {
|
||||
let handler = gSocketHandlers[name];
|
||||
if (request.uri == handler.path || request.uri.slice(0, handler.path.length + 1) == handler.path + '/') {
|
||||
matchedHandler = handler;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return matchedHandler;
|
||||
}
|
||||
|
||||
function Response(request, client) {
|
||||
let kStatusText = {
|
||||
101: "Switching Protocols",
|
||||
200: 'OK',
|
||||
303: 'See other',
|
||||
304: 'Not Modified',
|
||||
400: 'Bad Request',
|
||||
401: 'Unauthorized',
|
||||
403: 'Forbidden',
|
||||
404: 'File not found',
|
||||
500: 'Internal server error',
|
||||
};
|
||||
let _started = false;
|
||||
let _finished = false;
|
||||
let _keepAlive = false;
|
||||
let _chunked = false;
|
||||
return {
|
||||
writeHead: function(status) {
|
||||
if (_started) {
|
||||
throw new Error("Response.writeHead called multiple times.");
|
||||
}
|
||||
let reason;
|
||||
let headers;
|
||||
if (arguments.length == 3) {
|
||||
reason = arguments[1];
|
||||
headers = arguments[2];
|
||||
} else {
|
||||
reason = kStatusText[status];
|
||||
headers = arguments[1];
|
||||
}
|
||||
let lowerHeaders = {};
|
||||
let requestVersion = request.version.split("/")[1].split(".");
|
||||
let responseVersion = (requestVersion[0] >= 1 && requestVersion[0] >= 1) ? "1.1" : "1.0";
|
||||
let headerString = "HTTP/" + responseVersion + " " + status + " " + reason + "\r\n";
|
||||
headers['Server'] = `Tilde Friends/${version().number}`;
|
||||
for (let i in headers) {
|
||||
headerString += i + ": " + headers[i] + "\r\n";
|
||||
lowerHeaders[i.toLowerCase()] = headers[i];
|
||||
}
|
||||
if ("connection" in lowerHeaders) {
|
||||
_keepAlive = lowerHeaders["connection"].toLowerCase() == "keep-alive";
|
||||
} else {
|
||||
_keepAlive = ((request.version == "HTTP/1.0" && ("connection" in lowerHeaders && lowerHeaders["connection"].toLowerCase() == "keep-alive")) ||
|
||||
(request.version == "HTTP/1.1" && (!("connection" in lowerHeaders) || lowerHeaders["connection"].toLowerCase() != "close")));
|
||||
headerString += "Connection: " + (_keepAlive ? "keep-alive" : "close") + "\r\n";
|
||||
}
|
||||
_chunked = _keepAlive && !("content-length" in lowerHeaders);
|
||||
if (_chunked) {
|
||||
headerString += "Transfer-Encoding: chunked\r\n";
|
||||
}
|
||||
headerString += "\r\n";
|
||||
_started = true;
|
||||
client.write(headerString).catch(function() {});
|
||||
},
|
||||
end: function(data) {
|
||||
if (_finished) {
|
||||
throw new Error("Response.end called multiple times.");
|
||||
}
|
||||
if (data) {
|
||||
if (_chunked) {
|
||||
client.write(data.length.toString(16) + "\r\n" + data + "\r\n" + "0\r\n\r\n").catch(function() {});
|
||||
} else {
|
||||
client.write(data).catch(function() {});
|
||||
}
|
||||
} else if (_chunked) {
|
||||
client.write("0\r\n\r\n").catch(function() {});
|
||||
}
|
||||
_finished = true;
|
||||
if (!_keepAlive) {
|
||||
client.shutdown().catch(function() {});
|
||||
}
|
||||
},
|
||||
reportError: function(error) {
|
||||
if (!_started) {
|
||||
client.write("HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n").catch(function() {});
|
||||
}
|
||||
if (!_finished) {
|
||||
client.write("500 Internal Server Error\r\n\r\n" + error?.stackTrace).catch(function() {});
|
||||
}
|
||||
logError(client.peerName + " - - [" + new Date() + "] " + error);
|
||||
},
|
||||
isConnected: function() { return client.isConnected; },
|
||||
};
|
||||
}
|
||||
|
||||
function handleRequest(request, response) {
|
||||
let handler = findHandler(request);
|
||||
|
||||
print(request.client.peerName + " - - [" + new Date() + "] " + request.method + " " + request.uri + " " + request.version + " \"" + request.headers["user-agent"] + "\"");
|
||||
|
||||
if (handler) {
|
||||
try {
|
||||
Promise.resolve(handler.invoke(request, response)).catch(function(error) {
|
||||
response.reportError(error);
|
||||
request.client.close();
|
||||
});
|
||||
} catch (error) {
|
||||
response.reportError(error);
|
||||
request.client.close();
|
||||
}
|
||||
} else {
|
||||
response.writeHead(200, {"Content-Type": "text/plain; charset=utf-8"});
|
||||
response.end("No handler found for request: " + request.uri);
|
||||
}
|
||||
}
|
||||
|
||||
function handleWebSocketRequest(request, response, client) {
|
||||
let buffer = new Uint8Array(0);
|
||||
let frame;
|
||||
let frameOpCode = 0x0;
|
||||
|
||||
let handler = findSocketHandler(request);
|
||||
if (!handler) {
|
||||
client.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (client) {
|
||||
response.send = function(message, opCode) {
|
||||
if (opCode === undefined) {
|
||||
opCode = 0x2;
|
||||
}
|
||||
if (opCode == 0x1 && (typeof message == "string" || message instanceof String)) {
|
||||
message = utf8Encode(message);
|
||||
}
|
||||
let fin = true;
|
||||
let packet = [(fin ? (1 << 7) : 0) | (opCode & 0xf)];
|
||||
let mask = false;
|
||||
if (message.length < 126) {
|
||||
packet.push((mask ? (1 << 7) : 0) | message.length);
|
||||
} else if (message.length < (1 << 16)) {
|
||||
packet.push((mask ? (1 << 7) : 0) | 126);
|
||||
packet.push((message.length >> 8) & 0xff);
|
||||
packet.push(message.length & 0xff);
|
||||
} else {
|
||||
let high = 0; //(message.length / (1 ** 32)) & 0xffffffff;
|
||||
let low = message.length & 0xffffffff;
|
||||
packet.push((mask ? (1 << 7) : 0) | 127);
|
||||
packet.push((high >> 24) & 0xff);
|
||||
packet.push((high >> 16) & 0xff);
|
||||
packet.push((high >> 8) & 0xff);
|
||||
packet.push((high >> 0) & 0xff);
|
||||
packet.push((low >> 24) & 0xff);
|
||||
packet.push((low >> 16) & 0xff);
|
||||
packet.push((low >> 8) & 0xff);
|
||||
packet.push(low & 0xff);
|
||||
}
|
||||
|
||||
let array = new Uint8Array(packet.length + message.length);
|
||||
array.set(packet, 0);
|
||||
array.set(message, packet.length);
|
||||
try {
|
||||
return client.write(array);
|
||||
} catch (error) {
|
||||
client.close();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
response.onMessage = null;
|
||||
|
||||
let extra_headers = handler.invoke(request, response);
|
||||
|
||||
if (client) {
|
||||
client.read(function(data) {
|
||||
if (data) {
|
||||
let newBuffer = new Uint8Array(buffer.length + data.length);
|
||||
newBuffer.set(buffer, 0);
|
||||
newBuffer.set(data, buffer.length);
|
||||
buffer = newBuffer;
|
||||
|
||||
while (buffer.length >= 2) {
|
||||
let bits0 = buffer[0];
|
||||
let bits1 = buffer[1];
|
||||
if (bits1 & (1 << 7) == 0) {
|
||||
// Unmasked message.
|
||||
client.close();
|
||||
}
|
||||
let opCode = bits0 & 0xf;
|
||||
let fin = bits0 & (1 << 7);
|
||||
let payloadLength = bits1 & 0x7f;
|
||||
let maskStart = 2;
|
||||
|
||||
if (payloadLength == 126) {
|
||||
payloadLength = 0;
|
||||
for (let i = 0; i < 2; i++) {
|
||||
payloadLength <<= 8;
|
||||
payloadLength |= buffer[2 + i];
|
||||
}
|
||||
maskStart = 4;
|
||||
} else if (payloadLength == 127) {
|
||||
payloadLength = 0;
|
||||
for (let i = 0; i < 8; i++) {
|
||||
payloadLength <<= 8;
|
||||
payloadLength |= buffer[2 + i];
|
||||
}
|
||||
maskStart = 10;
|
||||
}
|
||||
let havePayload = buffer.length >= payloadLength + 2 + 4;
|
||||
if (havePayload) {
|
||||
let mask =
|
||||
buffer[maskStart + 0] |
|
||||
buffer[maskStart + 1] << 8 |
|
||||
buffer[maskStart + 2] << 16 |
|
||||
buffer[maskStart + 3] << 24;
|
||||
let dataStart = maskStart + 4;
|
||||
let payload = buffer.slice(dataStart, dataStart + payloadLength);
|
||||
let decoded = maskBytes(payload, mask);
|
||||
buffer = buffer.slice(dataStart + payloadLength);
|
||||
|
||||
if (frame) {
|
||||
let newBuffer = new Uint8Array(frame.length + decoded.length);
|
||||
newBuffer.set(frame, 0);
|
||||
newBuffer.set(decoded, frame.length);
|
||||
frame = newBuffer;
|
||||
} else {
|
||||
frame = decoded;
|
||||
}
|
||||
|
||||
if (opCode) {
|
||||
frameOpCode = opCode;
|
||||
}
|
||||
|
||||
if (fin) {
|
||||
if (response.onMessage) {
|
||||
response.onMessage({
|
||||
data: frameOpCode == 0x1 ? utf8Decode(frame) : frame,
|
||||
opCode: frameOpCode,
|
||||
});
|
||||
}
|
||||
frame = undefined;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
response.onClose();
|
||||
client.close();
|
||||
}
|
||||
});
|
||||
client.onError(function(error) {
|
||||
logError(client.peerName + " - - [" + new Date() + "] " + error);
|
||||
response.onError(error);
|
||||
});
|
||||
}
|
||||
|
||||
let headers = {
|
||||
"Upgrade": "websocket",
|
||||
"Connection": "Upgrade",
|
||||
"Sec-WebSocket-Accept": webSocketAcceptResponse(request.headers["sec-websocket-key"]),
|
||||
};
|
||||
if (request.headers["sec-websocket-version"] != "13") {
|
||||
headers["Sec-WebSocket-Version"] = "13";
|
||||
}
|
||||
response.writeHead(101, Object.assign({}, headers, extra_headers));
|
||||
}
|
||||
|
||||
function webSocketAcceptResponse(key) {
|
||||
let kMagic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
return base64Encode(sha1Digest(key + kMagic));
|
||||
}
|
||||
|
||||
function badRequest(client, reason) {
|
||||
let now = new Date();
|
||||
let count = 0;
|
||||
let old = gBadRequests[client.peerName];
|
||||
if (!old) {
|
||||
gBadRequests[client.peerName] = {
|
||||
expire: new Date(now.getTime() + 1 * 60 * 1000),
|
||||
count: 1,
|
||||
reason: reason,
|
||||
};
|
||||
count = 1;
|
||||
} else {
|
||||
old.count++;
|
||||
old.reason = reason;
|
||||
count = old.count;
|
||||
}
|
||||
new Response({version: '1.0'}, client).reportError(reason + ': ' + count);
|
||||
client.close();
|
||||
}
|
||||
|
||||
function allowRequest(client) {
|
||||
let old = gBadRequests[client.peerName];
|
||||
if (old) {
|
||||
let now = new Date();
|
||||
if (old.expire < now) {
|
||||
delete gBadRequests[client.peerName];
|
||||
return true;
|
||||
} else {
|
||||
return old.count < 3;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function handleConnection(client) {
|
||||
if (!allowRequest(client)) {
|
||||
print('Rejecting client for too many bad requests: ', client.peerName, gBadRequests[client.peerName].reason);
|
||||
client.info = 'rejected';
|
||||
client.close();
|
||||
return;
|
||||
}
|
||||
|
||||
client.info = 'accepted';
|
||||
let inputBuffer = new Uint8Array(0);
|
||||
let request;
|
||||
let headers = {};
|
||||
let parsing_header = true;
|
||||
let bodyToRead = -1;
|
||||
let body;
|
||||
let readCount = 0;
|
||||
let isWebsocket = false;
|
||||
|
||||
client.setActivityTimeout(kRequestTimeout);
|
||||
|
||||
function reset() {
|
||||
request = undefined;
|
||||
headers = {};
|
||||
parsing_header = true;
|
||||
bodyToRead = -1;
|
||||
body = undefined;
|
||||
client.info = 'reset';
|
||||
client.setActivityTimeout(kRequestTimeout);
|
||||
}
|
||||
|
||||
function finish() {
|
||||
client.info = 'finishing';
|
||||
let requestObject = new Request(request[0], request[1], request[2], headers, body, client);
|
||||
let response = new Response(requestObject, client);
|
||||
try {
|
||||
handleRequest(requestObject, response)
|
||||
if (client.isConnected) {
|
||||
reset();
|
||||
}
|
||||
} catch (error) {
|
||||
response.reportError(error);
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
client.onError(function(error) {
|
||||
logError(client.peerName + " - - [" + new Date() + "] " + error);
|
||||
});
|
||||
|
||||
client.read(function(data) {
|
||||
readCount++;
|
||||
if (data) {
|
||||
let newBuffer = new Uint8Array(inputBuffer.length + data.length);
|
||||
newBuffer.set(inputBuffer, 0);
|
||||
newBuffer.set(data, inputBuffer.length);
|
||||
inputBuffer = newBuffer;
|
||||
|
||||
if (parsing_header)
|
||||
{
|
||||
let result = parseHttpRequest(inputBuffer, inputBuffer.length - data.length);
|
||||
if (result) {
|
||||
if (typeof result === 'number') {
|
||||
if (result == -2) {
|
||||
/* More. */
|
||||
} else {
|
||||
badRequest(client, `Bad request(parse=${result}, length=${inputBuffer.length - data.length}).`);
|
||||
return;
|
||||
}
|
||||
} else if (typeof result === 'object') {
|
||||
client.setActivityTimeout(kStallTimeout);
|
||||
request = [
|
||||
result.method,
|
||||
result.path,
|
||||
`HTTP/1.${result.minor_version}`,
|
||||
];
|
||||
|
||||
headers = Object.fromEntries(Object.entries(result.headers).map(x => [x[0].toLowerCase(), x[1]]));
|
||||
parsing_header = false;
|
||||
inputBuffer = inputBuffer.slice(result.bytes_parsed);
|
||||
|
||||
if (!client.tls && tildefriends.https_port && core.globalSettings.http_redirect && !result.path.startsWith('/.well-known/')) {
|
||||
let requestObject = new Request(request[0], request[1], request[2], headers, body, client);
|
||||
let response = new Response(requestObject, client);
|
||||
response.writeHead(303, {"Location": `${core.globalSettings.http_redirect}${result.path}`, "Content-Length": "0"});
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (headers["content-length"] != undefined) {
|
||||
bodyToRead = parseInt(headers["content-length"]);
|
||||
if (bodyToRead > 16 * 1024 * 1024) {
|
||||
badRequest(client, 'Request too large: ' + bodyToRead + '.');
|
||||
return;
|
||||
}
|
||||
body = new Uint8Array(bodyToRead);
|
||||
client.info = 'waiting for body';
|
||||
} else if (headers["connection"]
|
||||
&& headers["connection"].toLowerCase().split(",").map(x => x.trim()).indexOf("upgrade") != -1
|
||||
&& headers["upgrade"]
|
||||
&& headers["upgrade"].toLowerCase() == "websocket") {
|
||||
isWebsocket = true;
|
||||
client.info = 'websocket';
|
||||
let requestObject = new Request(request[0], request[1], request[2], headers, body, client);
|
||||
let response = new Response(requestObject, client);
|
||||
handleWebSocketRequest(requestObject, response, client);
|
||||
/* Prevent the timeout from disconnecting us. */
|
||||
client.setActivityTimeout();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!parsing_header && inputBuffer.length)
|
||||
{
|
||||
let offset = body.length - bodyToRead;
|
||||
let length = Math.min(inputBuffer.length, body.length - offset);
|
||||
if (inputBuffer.length > body.length - offset) {
|
||||
body.set(inputBuffer.slice(0, length), offset);
|
||||
inputBuffer = inputBuffer.slice(length);
|
||||
} else {
|
||||
body.set(inputBuffer, offset);
|
||||
inputBuffer = inputBuffer.slice(inputBuffer.length);
|
||||
}
|
||||
bodyToRead -= length;
|
||||
if (bodyToRead <= 0) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
client.info = 'EOF';
|
||||
client.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let kBacklog = 8;
|
||||
let kHost = platform() == 'haiku' ? 'localhost' : '::';
|
||||
|
||||
function start() {
|
||||
let socket = new Socket();
|
||||
socket.bind(kHost, tildefriends.http_port).then(function(port) {
|
||||
print("bound to", port);
|
||||
print("checking", tildefriends.args.out_http_port_file);
|
||||
if (tildefriends.args.out_http_port_file) {
|
||||
print("going to write the file");
|
||||
File.writeFile(tildefriends.args.out_http_port_file, port.toString() + '\n').then(function(r) {
|
||||
print("wrote port file", tildefriends.args.out_http_port_file, r);
|
||||
}).catch(function() {
|
||||
print("failed to write port file");
|
||||
});
|
||||
}
|
||||
let listenResult = socket.listen(kBacklog, async function() {
|
||||
try {
|
||||
let client = await socket.accept();
|
||||
client.noDelay = true;
|
||||
handleConnection(client);
|
||||
} catch (error) {
|
||||
logError("[" + new Date() + "] accept error " + error);
|
||||
}
|
||||
});
|
||||
}).catch(function(error) {
|
||||
logError("[" + new Date() + "] bind error " + error);
|
||||
});
|
||||
|
||||
if (tildefriends.https_port) {
|
||||
let tls = {};
|
||||
let secureSocket = new Socket();
|
||||
secureSocket.bind(kHost, tildefriends.https_port).then(function() {
|
||||
return secureSocket.listen(kBacklog, async function() {
|
||||
try {
|
||||
let client = await secureSocket.accept();
|
||||
client.noDelay = true;
|
||||
client.tls = true;
|
||||
const kCertificatePath = "data/httpd/certificate.pem";
|
||||
const kPrivateKeyPath = "data/httpd/privatekey.pem";
|
||||
|
||||
let stat = await Promise.all([
|
||||
await File.stat(kCertificatePath),
|
||||
await File.stat(kPrivateKeyPath),
|
||||
]);
|
||||
if (!tls.context ||
|
||||
tls.certStat.mtime != stat[0].mtime ||
|
||||
tls.certStat.size != stat[0].size ||
|
||||
tls.keyStat.mtime != stat[1].mtime ||
|
||||
tls.keyStat.size != stat[1].size) {
|
||||
print("Reloading " + kCertificatePath + " and " + kPrivateKeyPath);
|
||||
let privateKey = utf8Decode(await File.readFile(kPrivateKeyPath));
|
||||
let certificate = utf8Decode(await File.readFile(kCertificatePath));
|
||||
|
||||
tls.context = new TlsContext();
|
||||
tls.context.setPrivateKey(privateKey);
|
||||
tls.context.setCertificate(certificate);
|
||||
tls.certStat = stat[0];
|
||||
tls.keyStat = stat[1];
|
||||
}
|
||||
|
||||
let result = client.startTls(tls.context);
|
||||
handleConnection(client);
|
||||
return result;
|
||||
} catch (error) {
|
||||
logError("[" + new Date() + "] " + error);
|
||||
}
|
||||
});
|
||||
}).catch(function(error) {
|
||||
logError("[" + new Date() + "] bind error " + error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { all, start, registerSocketHandler };
|
@ -3,31 +3,39 @@
|
||||
<head>
|
||||
<title>Tilde Friends</title>
|
||||
<link type="text/css" rel="stylesheet" href="/static/style.css">
|
||||
<link type="text/css" rel="stylesheet" href="/static/w3.css">
|
||||
<link type="image/png" rel="shortcut icon" href="/static/favicon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script>
|
||||
function set_access_key_title(event) {
|
||||
if (!event.srcElement.title) {
|
||||
event.srcElement.title = `${event.srcElement.dataset.tip} [${(event.srcElement.accessKeyLabel || ('⌨️' + event.srcElement.accessKey)).toUpperCase()}]`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body style="display: flex; flex-flow: column">
|
||||
<body style="display: flex; flex-flow: column; width: 100vw; height: 100vh; position: absolute; max-width: 100%; max-height: 100%">
|
||||
<tf-navigation></tf-navigation>
|
||||
<div id="content" class="hbox" style="flex: 1 1; width: 100%">
|
||||
<div id="editPane" class="vbox" style="flex: 1 1; display: none">
|
||||
<div class="navigation hbox">
|
||||
<input type="button" id="closeEditor" name="closeEditor" value="Close">
|
||||
<input type="button" id="save" name="save" value="Save">
|
||||
<input type="button" id="icon" name="icon" value="📦">
|
||||
<input type="text" id="name" name="name" style="flex: 1 1; min-width: 1em"></input>
|
||||
<input type="button" id="delete" name="delete" value="Delete">
|
||||
<input type="button" id="trace_button" value="Trace">
|
||||
<div id="content" class="hbox" style="flex: 1 0; overflow: auto">
|
||||
<div id="editPane" class="vbox" style="flex: 0 1 100%; display: none; overflow: auto">
|
||||
<div class="navigation w3-bar" style="display: flex">
|
||||
<button class="w3-bar-item w3-button w3-blue" id="closeEditor" name="closeEditor" accesskey="c" onmouseover="set_access_key_title(event)" data-tip="Close the editor">Close</button>
|
||||
<button class="w3-bar-item w3-button w3-blue" id="save" name="save" accesskey="s" onmouseover="set_access_key_title(event)" data-tip="Save the app under the given path">Save</button>
|
||||
<button class="w3-bar-item w3-button w3-blue" id="icon" name="icon" accesskey="i" onmouseover="set_access_key_title(event)" data-tip="Set an icon/emoji for the app">📦</button>
|
||||
<button class="w3-bar-item w3-button w3-blue" id="export" name="export" accesskey="e" onmouseover="set_access_key_title(event)" data-tip="Export app to .zip file">Export</button>
|
||||
<button class="w3-bar-item w3-button w3-blue" id="import" name="import" accesskey="i" onmouseover="set_access_key_title(event)" data-tip="Import app from .zip file">Import</button>
|
||||
<input class="w3-bar-item w3-input w3-border w3-blue" type="text" id="name" name="name" style="flex: 1 1; min-width: 1em"></input>
|
||||
<button class="w3-bar-item w3-button w3-blue" id="delete" name="delete" accesskey="d" onmouseover="set_access_key_title(event)" data-tip="Delete the app">Delete</button>
|
||||
<button class="w3-bar-item w3-button w3-blue" id="trace_button" accesskey="t" onmouseover="set_access_key_title(event)" data-tip="Open a performance trace for the server">Trace</button>
|
||||
</div>
|
||||
<div class="hbox" style="height: 100%">
|
||||
<tf-files-pane></tf-files-pane>
|
||||
<div id="docPane" style="display: flex; flex: 1 1 50%; flex-flow: column">
|
||||
<div style="flex: 1 1 50%; position: relative">
|
||||
<textarea id="editor" class="main"></textarea>
|
||||
</div>
|
||||
<div class="hbox" style="flex: 1 1; overflow: auto">
|
||||
<div style="overflow: auto">
|
||||
<tf-files-pane style="overflow: auto"></tf-files-pane>
|
||||
</div>
|
||||
<div style="flex: 1 1; overflow: auto"><div id="editor" style="width: 100%; height: 100%"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="viewPane" class="vbox" style="flex: 1 1; overflow: auto">
|
||||
<div id="viewPane" class="vbox" style="flex: 0 1 100%; overflow: auto">
|
||||
<iframe id="document" sandbox="allow-forms allow-scripts allow-top-navigation allow-modals allow-popups allow-downloads" style="width: 100%; height: 100%; border: 0"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
1
core/jszip.min.js
vendored
Normal file
1
core/jszip.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -64,14 +64,8 @@ a:active {
|
||||
color: #eee8d5;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
.cm-editor {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.cm-tab {
|
||||
|
235
core/w3.css
Normal file
235
core/w3.css
Normal file
@ -0,0 +1,235 @@
|
||||
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
|
||||
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
|
||||
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
|
||||
a{background-color:transparent}a:active,a:hover{outline-width:0}
|
||||
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
|
||||
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
|
||||
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
|
||||
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
|
||||
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
|
||||
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
|
||||
button,input{overflow:visible}button,select{text-transform:none}
|
||||
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
|
||||
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
|
||||
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
|
||||
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
|
||||
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
|
||||
[type=checkbox],[type=radio]{padding:0}
|
||||
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
|
||||
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
|
||||
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
|
||||
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
|
||||
/* End extract */
|
||||
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
|
||||
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
|
||||
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
|
||||
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
|
||||
hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
|
||||
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
|
||||
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
|
||||
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
|
||||
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
|
||||
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
|
||||
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
|
||||
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
|
||||
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
|
||||
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
|
||||
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
|
||||
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
|
||||
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
|
||||
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
|
||||
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
|
||||
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
|
||||
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
|
||||
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
|
||||
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
|
||||
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
|
||||
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
|
||||
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
|
||||
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
|
||||
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
|
||||
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
|
||||
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
|
||||
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
|
||||
.w3-main,#main{transition:margin-left .4s}
|
||||
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
|
||||
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
|
||||
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
|
||||
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
|
||||
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
|
||||
.w3-bar .w3-button{white-space:normal}
|
||||
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
|
||||
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
|
||||
.w3-responsive{display:block;overflow-x:auto}
|
||||
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
|
||||
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
|
||||
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
|
||||
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
|
||||
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
|
||||
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
|
||||
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
|
||||
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
|
||||
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
|
||||
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
|
||||
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
|
||||
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
|
||||
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
|
||||
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
|
||||
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
|
||||
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
|
||||
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
|
||||
@media (max-width:1205px){.w3-auto{max-width:95%}}
|
||||
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
|
||||
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
|
||||
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
|
||||
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
|
||||
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
|
||||
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
|
||||
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
|
||||
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
|
||||
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
|
||||
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
|
||||
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
|
||||
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
|
||||
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
|
||||
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
|
||||
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
|
||||
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
||||
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
||||
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
|
||||
.w3-display-position{position:absolute}
|
||||
.w3-circle{border-radius:50%}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
|
||||
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
|
||||
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
|
||||
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
|
||||
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
|
||||
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
|
||||
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
|
||||
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
|
||||
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
|
||||
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
|
||||
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
|
||||
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
|
||||
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
|
||||
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
|
||||
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
|
||||
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
|
||||
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
|
||||
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
|
||||
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
|
||||
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
|
||||
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
|
||||
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
|
||||
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
|
||||
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
|
||||
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
|
||||
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
|
||||
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
|
||||
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
|
||||
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
|
||||
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
|
||||
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
|
||||
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
|
||||
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
|
||||
.w3-left{float:left!important}.w3-right{float:right!important}
|
||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
|
||||
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
|
||||
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
|
||||
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
|
||||
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
|
||||
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
|
||||
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
|
||||
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
|
||||
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
|
||||
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
|
||||
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
|
||||
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
|
||||
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
|
||||
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
|
||||
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
|
||||
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
|
||||
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
|
||||
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
|
||||
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
|
||||
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
|
||||
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
|
||||
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
|
||||
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
|
||||
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
|
||||
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
|
||||
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
|
||||
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
|
||||
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
|
||||
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
|
||||
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
|
||||
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
|
||||
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
|
||||
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
|
||||
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
|
||||
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
|
||||
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
|
||||
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
|
||||
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
|
||||
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
|
||||
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
|
||||
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
|
||||
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
|
||||
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
|
||||
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
|
||||
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
|
||||
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
|
||||
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
|
||||
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
|
||||
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
|
||||
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
|
||||
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
|
||||
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
|
||||
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
|
||||
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
|
||||
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
|
||||
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
|
||||
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
|
||||
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
|
||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
1
deps/codemirror/annotatescrollbar.min.js
vendored
1
deps/codemirror/annotatescrollbar.min.js
vendored
@ -1 +0,0 @@
|
||||
!function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],t):t(CodeMirror)}(function(t){"use strict";function e(t,e){function i(t){clearTimeout(n.doRedraw),n.doRedraw=setTimeout(function(){n.redraw()},t)}this.cm=t,this.options=e,this.buttonHeight=e.scrollButtonHeight||t.getOption("scrollButtonHeight"),this.annotations=[],this.doRedraw=this.doUpdate=null,this.div=t.getWrapperElement().appendChild(document.createElement("div")),this.div.style.cssText="position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none",this.computeScale();var n=this;t.on("refresh",this.resizeHandler=function(){clearTimeout(n.doUpdate),n.doUpdate=setTimeout(function(){n.computeScale()&&i(20)},100)}),t.on("markerAdded",this.resizeHandler),t.on("markerCleared",this.resizeHandler),!1!==e.listenForChanges&&t.on("changes",this.changeHandler=function(){i(250)})}t.defineExtension("annotateScrollbar",function(t){return new e(this,t="string"==typeof t?{className:t}:t)}),t.defineOption("scrollButtonHeight",0),e.prototype.computeScale=function(){var t=this.cm,t=(t.getWrapperElement().clientHeight-t.display.barHeight-2*this.buttonHeight)/t.getScrollerElement().scrollHeight;if(t!=this.hScale)return this.hScale=t,!0},e.prototype.update=function(t){this.annotations=t,this.redraw()},e.prototype.redraw=function(t){!1!==t&&this.computeScale();var n=this.cm,e=this.hScale,i=document.createDocumentFragment(),o=this.annotations,r=n.getOption("lineWrapping"),a=r&&1.5*n.defaultTextHeight(),s=null,h=null;function l(t,e){var i;return s!=t.line&&(s=t.line,h=n.getLineHandle(t.line),(i=n.getLineHandleVisualStart(h))!=h&&(s=n.getLineNumber(i),h=i)),h.widgets&&h.widgets.length||r&&h.height>a?n.charCoords(t,"local")[e?"top":"bottom"]:n.heightAtLine(h,"local")+(e?0:h.height)}var d=n.lastLine();if(n.display.barWidth)for(var c,p=0;p<o.length;p++){var u=o[p];if(!(u.to.line>d)){for(var m,f,g=c||l(u.from,!0)*e,H=l(u.to,!1)*e;p<o.length-1&&!(o[p+1].to.line>d)&&!(H+.9<(c=l(o[p+1].from,!0)*e));)H=l((u=o[++p]).to,!1)*e;H!=g&&(m=Math.max(H-g,3),(f=i.appendChild(document.createElement("div"))).style.cssText="position: absolute; right: 0px; width: "+Math.max(n.display.barWidth-1,2)+"px; top: "+(g+this.buttonHeight)+"px; height: "+m+"px",f.className=this.options.className,u.id&&f.setAttribute("annotation-id",u.id))}}this.div.textContent="",this.div.appendChild(i)},e.prototype.clear=function(){this.cm.off("refresh",this.resizeHandler),this.cm.off("markerAdded",this.resizeHandler),this.cm.off("markerCleared",this.resizeHandler),this.changeHandler&&this.cm.off("changes",this.changeHandler),this.div.parentNode.removeChild(this.div)}});
|
1
deps/codemirror/base16-dark.min.css
vendored
1
deps/codemirror/base16-dark.min.css
vendored
@ -1 +0,0 @@
|
||||
.cm-s-base16-dark.CodeMirror{background:#151515;color:#e0e0e0}.cm-s-base16-dark div.CodeMirror-selected{background:#303030}.cm-s-base16-dark .CodeMirror-line::selection,.cm-s-base16-dark .CodeMirror-line>span::selection,.cm-s-base16-dark .CodeMirror-line>span>span::selection{background:rgba(48,48,48,.99)}.cm-s-base16-dark .CodeMirror-line::-moz-selection,.cm-s-base16-dark .CodeMirror-line>span::-moz-selection,.cm-s-base16-dark .CodeMirror-line>span>span::-moz-selection{background:rgba(48,48,48,.99)}.cm-s-base16-dark .CodeMirror-gutters{background:#151515;border-right:0}.cm-s-base16-dark .CodeMirror-guttermarker{color:#ac4142}.cm-s-base16-dark .CodeMirror-guttermarker-subtle{color:#505050}.cm-s-base16-dark .CodeMirror-linenumber{color:#505050}.cm-s-base16-dark .CodeMirror-cursor{border-left:1px solid #b0b0b0}.cm-s-base16-dark.cm-fat-cursor .CodeMirror-cursor{background-color:#8e8d8875!important}.cm-s-base16-dark .cm-animate-fat-cursor{background-color:#8e8d8875!important}.cm-s-base16-dark span.cm-comment{color:#8f5536}.cm-s-base16-dark span.cm-atom{color:#aa759f}.cm-s-base16-dark span.cm-number{color:#aa759f}.cm-s-base16-dark span.cm-attribute,.cm-s-base16-dark span.cm-property{color:#90a959}.cm-s-base16-dark span.cm-keyword{color:#ac4142}.cm-s-base16-dark span.cm-string{color:#f4bf75}.cm-s-base16-dark span.cm-variable{color:#90a959}.cm-s-base16-dark span.cm-variable-2{color:#6a9fb5}.cm-s-base16-dark span.cm-def{color:#d28445}.cm-s-base16-dark span.cm-bracket{color:#e0e0e0}.cm-s-base16-dark span.cm-tag{color:#ac4142}.cm-s-base16-dark span.cm-link{color:#aa759f}.cm-s-base16-dark span.cm-error{background:#ac4142;color:#b0b0b0}.cm-s-base16-dark .CodeMirror-activeline-background{background:#202020}.cm-s-base16-dark .CodeMirror-matchingbracket{text-decoration:underline;color:#fff!important}
|
1
deps/codemirror/cm6.js
vendored
Normal file
1
deps/codemirror/cm6.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
deps/codemirror/codemirror.min.css
vendored
1
deps/codemirror/codemirror.min.css
vendored
File diff suppressed because one or more lines are too long
1
deps/codemirror/codemirror.min.js
vendored
1
deps/codemirror/codemirror.min.js
vendored
File diff suppressed because one or more lines are too long
1
deps/codemirror/css.min.js
vendored
1
deps/codemirror/css.min.js
vendored
File diff suppressed because one or more lines are too long
1
deps/codemirror/dialog.min.css
vendored
1
deps/codemirror/dialog.min.css
vendored
@ -1 +0,0 @@
|
||||
.CodeMirror-dialog{position:absolute;left:0;right:0;background:inherit;z-index:15;padding:.1em .8em;overflow:hidden;color:inherit}.CodeMirror-dialog-top{border-bottom:1px solid #eee;top:0}.CodeMirror-dialog-bottom{border-top:1px solid #eee;bottom:0}.CodeMirror-dialog input{border:none;outline:0;background:0 0;width:20em;color:inherit;font-family:monospace}.CodeMirror-dialog button{font-size:70%}
|
1
deps/codemirror/dialog.min.js
vendored
1
deps/codemirror/dialog.min.js
vendored
@ -1 +0,0 @@
|
||||
!function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(s){function f(e,o,n){var e=e.getWrapperElement(),t=e.appendChild(document.createElement("div"));return t.className=n?"CodeMirror-dialog CodeMirror-dialog-bottom":"CodeMirror-dialog CodeMirror-dialog-top","string"==typeof o?t.innerHTML=o:t.appendChild(o),s.addClass(e,"dialog-opened"),t}function p(e,o){e.state.currentNotificationClose&&e.state.currentNotificationClose(),e.state.currentNotificationClose=o}s.defineExtension("openDialog",function(e,o,n){n=n||{},p(this,null);var t=f(this,e,n.bottom),i=!1,r=this;function u(e){"string"==typeof e?l.value=e:i||(i=!0,s.rmClass(t.parentNode,"dialog-opened"),t.parentNode.removeChild(t),r.focus(),n.onClose&&n.onClose(t))}var l=t.getElementsByTagName("input")[0];return l?(l.focus(),n.value&&(l.value=n.value,!1!==n.selectValueOnOpen&&l.select()),n.onInput&&s.on(l,"input",function(e){n.onInput(e,l.value,u)}),n.onKeyUp&&s.on(l,"keyup",function(e){n.onKeyUp(e,l.value,u)}),s.on(l,"keydown",function(e){n&&n.onKeyDown&&n.onKeyDown(e,l.value,u)||((27==e.keyCode||!1!==n.closeOnEnter&&13==e.keyCode)&&(l.blur(),s.e_stop(e),u()),13==e.keyCode&&o(l.value,e))}),!1!==n.closeOnBlur&&s.on(t,"focusout",function(e){null!==e.relatedTarget&&u()})):(e=t.getElementsByTagName("button")[0])&&(s.on(e,"click",function(){u(),r.focus()}),!1!==n.closeOnBlur&&s.on(e,"blur",u),e.focus()),u}),s.defineExtension("openConfirm",function(e,o,n){p(this,null);var t=f(this,e,n&&n.bottom),i=t.getElementsByTagName("button"),r=!1,u=this,l=1;function a(){r||(r=!0,s.rmClass(t.parentNode,"dialog-opened"),t.parentNode.removeChild(t),u.focus())}i[0].focus();for(var c=0;c<i.length;++c){var d=i[c];!function(o){s.on(d,"click",function(e){s.e_preventDefault(e),a(),o&&o(u)})}(o[c]),s.on(d,"blur",function(){--l,setTimeout(function(){l<=0&&a()},200)}),s.on(d,"focus",function(){++l})}}),s.defineExtension("openNotification",function(e,o){p(this,r);var n,t=f(this,e,o&&o.bottom),i=!1,e=o&&void 0!==o.duration?o.duration:5e3;function r(){i||(i=!0,clearTimeout(n),s.rmClass(t.parentNode,"dialog-opened"),t.parentNode.removeChild(t))}return s.on(t,"click",function(e){s.e_preventDefault(e),r()}),e&&(n=setTimeout(r,e)),r})});
|
1
deps/codemirror/htmlmixed.min.js
vendored
1
deps/codemirror/htmlmixed.min.js
vendored
@ -1 +0,0 @@
|
||||
!function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror"),require("../xml/xml"),require("../javascript/javascript"),require("../css/css")):"function"==typeof define&&define.amd?define(["../../lib/codemirror","../xml/xml","../javascript/javascript","../css/css"],t):t(CodeMirror)}(function(m){"use strict";var l={script:[["lang",/(javascript|babel)/i,"javascript"],["type",/^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i,"javascript"],["type",/./,"text/plain"],[null,null,"javascript"]],style:[["lang",/^css$/i,"css"],["type",/^(text\/)?(x-)?(stylesheet|css)$/i,"css"],["type",/./,"text/plain"],[null,null,"css"]]};var a={};function d(t,e){e=t.match(a[t=e]||(a[t]=new RegExp("\\s+"+t+"\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*")));return e?/^\s*(.*?)\s*$/.exec(e[2])[1]:""}function g(t,e){return new RegExp((e?"^":"")+"</\\s*"+t+"\\s*>","i")}function o(t,e){for(var a in t)for(var n=e[a]||(e[a]=[]),l=t[a],o=l.length-1;0<=o;o--)n.unshift(l[o])}m.defineMode("htmlmixed",function(i,t){var c=m.getMode(i,{name:"xml",htmlMode:!0,multilineTagIndentFactor:t.multilineTagIndentFactor,multilineTagIndentPastTag:t.multilineTagIndentPastTag,allowMissingTagName:t.allowMissingTagName}),s={},e=t&&t.tags,a=t&&t.scriptTypes;if(o(l,s),e&&o(e,s),a)for(var n=a.length-1;0<=n;n--)s.script.unshift(["type",a[n].matches,a[n].mode]);function u(t,e){var a,o,r,n=c.token(t,e.htmlState),l=/\btag\b/.test(n);return l&&!/[<>\s\/]/.test(t.current())&&(a=e.htmlState.tagName&&e.htmlState.tagName.toLowerCase())&&s.hasOwnProperty(a)?e.inTag=a+" ":e.inTag&&l&&/>$/.test(t.current())?(a=/^([\S]+) (.*)/.exec(e.inTag),e.inTag=null,l=">"==t.current()&&function(t,e){for(var a=0;a<t.length;a++){var n=t[a];if(!n[0]||n[1].test(d(e,n[0])))return n[2]}}(s[a[1]],a[2]),l=m.getMode(i,l),o=g(a[1],!0),r=g(a[1],!1),e.token=function(t,e){return t.match(o,!1)?(e.token=u,e.localState=e.localMode=null):(a=t,n=r,t=e.localMode.token(t,e.localState),e=a.current(),-1<(l=e.search(n))?a.backUp(e.length-l):e.match(/<\/?$/)&&(a.backUp(e.length),a.match(n,!1)||a.match(e)),t);var a,n,l},e.localMode=l,e.localState=m.startState(l,c.indent(e.htmlState,"",""))):e.inTag&&(e.inTag+=t.current(),t.eol()&&(e.inTag+=" ")),n}return{startState:function(){return{token:u,inTag:null,localMode:null,localState:null,htmlState:m.startState(c)}},copyState:function(t){var e;return t.localState&&(e=m.copyState(t.localMode,t.localState)),{token:t.token,inTag:t.inTag,localMode:t.localMode,localState:e,htmlState:m.copyState(c,t.htmlState)}},token:function(t,e){return e.token(t,e)},indent:function(t,e,a){return!t.localMode||/^\s*<\//.test(e)?c.indent(t.htmlState,e,a):t.localMode.indent?t.localMode.indent(t.localState,e,a):m.Pass},innerMode:function(t){return{state:t.localState||t.htmlState,mode:t.localMode||c}}}},"xml","javascript","css"),m.defineMIME("text/html","htmlmixed")});
|
1
deps/codemirror/javascript-lint.min.js
vendored
1
deps/codemirror/javascript-lint.min.js
vendored
@ -1 +0,0 @@
|
||||
!function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(a){"use strict";a.registerHelper("lint","javascript",function(e,r){if(!window.JSHINT)return window.console&&window.console.error("Error: window.JSHINT not defined, CodeMirror JavaScript linting cannot run."),[];if(r.indent||(r.indent=1),JSHINT(e,r,r.globals),e=JSHINT.data().errors,r=[],e)for(var n=e,o=r,i=0;i<n.length;i++){var t,d,s,c=n[i];c&&(c.line<=0?window.console&&window.console.warn("Cannot display JSHint error (invalid line "+c.line+")",c):(t=c.character-1,d=1+t,c.evidence&&-1<(s=c.evidence.substring(t).search(/.\b/))&&(d+=s),s={message:c.reason,severity:c.code&&c.code.startsWith("W")?"warning":"error",from:a.Pos(c.line-1,t),to:a.Pos(c.line-1,d)},o.push(s)))}return r})});
|
1
deps/codemirror/javascript.min.js
vendored
1
deps/codemirror/javascript.min.js
vendored
File diff suppressed because one or more lines are too long
1
deps/codemirror/jshint.min.js
vendored
1
deps/codemirror/jshint.min.js
vendored
File diff suppressed because one or more lines are too long
1
deps/codemirror/jump-to-line.min.js
vendored
1
deps/codemirror/jump-to-line.min.js
vendored
@ -1 +0,0 @@
|
||||
!function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror"),require("../dialog/dialog")):"function"==typeof define&&define.amd?define(["../../lib/codemirror","../dialog/dialog"],e):e(CodeMirror)}(function(e){"use strict";function l(e,o){var r=Number(o);return/^[-+]/.test(o)?e.getCursor().line+r:r-1}e.defineOption("search",{bottom:!1}),e.commands.jumpToLine=function(t){var e,o,r,s,i,n=t.getCursor();i=(i=e=t).phrase("Jump to line:")+' <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">'+i.phrase("(Use line:column or scroll% syntax)")+"</span>",o=t.phrase("Jump to line:"),r=n.line+1+":"+n.ch,s=function(e){var o,r;e&&((o=/^\s*([\+\-]?\d+)\s*\:\s*(\d+)\s*$/.exec(e))?t.setCursor(l(t,o[1]),Number(o[2])):(o=/^\s*([\+\-]?\d+(\.\d+)?)\%\s*/.exec(e))?(r=Math.round(t.lineCount()*Number(o[1])/100),/^[-+]/.test(o[1])&&(r=n.line+r+1),t.setCursor(r-1,n.ch)):(o=/^\s*\:?\s*([\+\-]?\d+)\s*/.exec(e))&&t.setCursor(l(t,o[1]),n.ch))},e.openDialog?e.openDialog(i,s,{value:r,selectValueOnOpen:!0,bottom:e.options.search.bottom}):s(prompt(o,r))},e.keyMap.default["Alt-G"]="jumpToLine"});
|
80
deps/codemirror/lint.css
vendored
80
deps/codemirror/lint.css
vendored
@ -1,80 +0,0 @@
|
||||
/* The lint marker gutter */
|
||||
.CodeMirror-lint-markers {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-tooltip {
|
||||
background-color: #ffd;
|
||||
border: 1px solid black;
|
||||
border-radius: 4px 4px 4px 4px;
|
||||
color: black;
|
||||
font-family: monospace;
|
||||
font-size: 10pt;
|
||||
overflow: hidden;
|
||||
padding: 2px 5px;
|
||||
position: fixed;
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
z-index: 100;
|
||||
max-width: 600px;
|
||||
opacity: 0;
|
||||
transition: opacity .4s;
|
||||
-moz-transition: opacity .4s;
|
||||
-webkit-transition: opacity .4s;
|
||||
-o-transition: opacity .4s;
|
||||
-ms-transition: opacity .4s;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-mark {
|
||||
background-position: left bottom;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-mark-warning {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
|
||||
}
|
||||
|
||||
.CodeMirror-lint-mark-error {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==");
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker {
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-message {
|
||||
padding-left: 18px;
|
||||
background-position: top left;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=");
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker-multiple {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right bottom;
|
||||
width: 100%; height: 100%;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-line-error {
|
||||
background-color: rgba(183, 76, 81, 0.08);
|
||||
}
|
||||
|
||||
.CodeMirror-lint-line-warning {
|
||||
background-color: rgba(255, 211, 0, 0.1);
|
||||
}
|
||||
|
292
deps/codemirror/lint.js
vendored
292
deps/codemirror/lint.js
vendored
@ -1,292 +0,0 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
var GUTTER_ID = "CodeMirror-lint-markers";
|
||||
var LINT_LINE_ID = "CodeMirror-lint-line-";
|
||||
|
||||
function showTooltip(cm, e, content) {
|
||||
var tt = document.createElement("div");
|
||||
tt.className = "CodeMirror-lint-tooltip cm-s-" + cm.options.theme;
|
||||
tt.appendChild(content.cloneNode(true));
|
||||
if (cm.state.lint.options.selfContain)
|
||||
cm.getWrapperElement().appendChild(tt);
|
||||
else
|
||||
document.body.appendChild(tt);
|
||||
|
||||
function position(e) {
|
||||
if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
|
||||
tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px";
|
||||
tt.style.left = (e.clientX + 5) + "px";
|
||||
}
|
||||
CodeMirror.on(document, "mousemove", position);
|
||||
position(e);
|
||||
if (tt.style.opacity != null) tt.style.opacity = 1;
|
||||
return tt;
|
||||
}
|
||||
function rm(elt) {
|
||||
if (elt.parentNode) elt.parentNode.removeChild(elt);
|
||||
}
|
||||
function hideTooltip(tt) {
|
||||
if (!tt.parentNode) return;
|
||||
if (tt.style.opacity == null) rm(tt);
|
||||
tt.style.opacity = 0;
|
||||
setTimeout(function() { rm(tt); }, 600);
|
||||
}
|
||||
|
||||
function showTooltipFor(cm, e, content, node) {
|
||||
var tooltip = showTooltip(cm, e, content);
|
||||
function hide() {
|
||||
CodeMirror.off(node, "mouseout", hide);
|
||||
if (tooltip) { hideTooltip(tooltip); tooltip = null; }
|
||||
}
|
||||
var poll = setInterval(function() {
|
||||
if (tooltip) for (var n = node;; n = n.parentNode) {
|
||||
if (n && n.nodeType == 11) n = n.host;
|
||||
if (n == document.body) return;
|
||||
if (!n) { hide(); break; }
|
||||
}
|
||||
if (!tooltip) return clearInterval(poll);
|
||||
}, 400);
|
||||
CodeMirror.on(node, "mouseout", hide);
|
||||
}
|
||||
|
||||
function LintState(cm, conf, hasGutter) {
|
||||
this.marked = [];
|
||||
if (conf instanceof Function) conf = {getAnnotations: conf};
|
||||
if (!conf || conf === true) conf = {};
|
||||
this.options = {};
|
||||
this.linterOptions = conf.options || {};
|
||||
for (var prop in defaults) this.options[prop] = defaults[prop];
|
||||
for (var prop in conf) {
|
||||
if (defaults.hasOwnProperty(prop)) {
|
||||
if (conf[prop] != null) this.options[prop] = conf[prop];
|
||||
} else if (!conf.options) {
|
||||
this.linterOptions[prop] = conf[prop];
|
||||
}
|
||||
}
|
||||
this.timeout = null;
|
||||
this.hasGutter = hasGutter;
|
||||
this.onMouseOver = function(e) { onMouseOver(cm, e); };
|
||||
this.waitingFor = 0
|
||||
}
|
||||
|
||||
var defaults = {
|
||||
highlightLines: false,
|
||||
tooltips: true,
|
||||
delay: 500,
|
||||
lintOnChange: true,
|
||||
getAnnotations: null,
|
||||
async: false,
|
||||
selfContain: null,
|
||||
formatAnnotation: null,
|
||||
onUpdateLinting: null
|
||||
}
|
||||
|
||||
function clearMarks(cm) {
|
||||
var state = cm.state.lint;
|
||||
if (state.hasGutter) cm.clearGutter(GUTTER_ID);
|
||||
if (state.options.highlightLines) clearErrorLines(cm);
|
||||
for (var i = 0; i < state.marked.length; ++i)
|
||||
state.marked[i].clear();
|
||||
state.marked.length = 0;
|
||||
}
|
||||
|
||||
function clearErrorLines(cm) {
|
||||
cm.eachLine(function(line) {
|
||||
var has = line.wrapClass && /\bCodeMirror-lint-line-\w+\b/.exec(line.wrapClass);
|
||||
if (has) cm.removeLineClass(line, "wrap", has[0]);
|
||||
})
|
||||
}
|
||||
|
||||
function makeMarker(cm, labels, severity, multiple, tooltips) {
|
||||
var marker = document.createElement("div"), inner = marker;
|
||||
marker.className = "CodeMirror-lint-marker CodeMirror-lint-marker-" + severity;
|
||||
if (multiple) {
|
||||
inner = marker.appendChild(document.createElement("div"));
|
||||
inner.className = "CodeMirror-lint-marker CodeMirror-lint-marker-multiple";
|
||||
}
|
||||
|
||||
if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) {
|
||||
showTooltipFor(cm, e, labels, inner);
|
||||
});
|
||||
|
||||
return marker;
|
||||
}
|
||||
|
||||
function getMaxSeverity(a, b) {
|
||||
if (a == "error") return a;
|
||||
else return b;
|
||||
}
|
||||
|
||||
function groupByLine(annotations) {
|
||||
var lines = [];
|
||||
for (var i = 0; i < annotations.length; ++i) {
|
||||
var ann = annotations[i], line = ann.from.line;
|
||||
(lines[line] || (lines[line] = [])).push(ann);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function annotationTooltip(ann) {
|
||||
var severity = ann.severity;
|
||||
if (!severity) severity = "error";
|
||||
var tip = document.createElement("div");
|
||||
tip.className = "CodeMirror-lint-message CodeMirror-lint-message-" + severity;
|
||||
if (typeof ann.messageHTML != 'undefined') {
|
||||
tip.innerHTML = ann.messageHTML;
|
||||
} else {
|
||||
tip.appendChild(document.createTextNode(ann.message));
|
||||
}
|
||||
return tip;
|
||||
}
|
||||
|
||||
function lintAsync(cm, getAnnotations) {
|
||||
var state = cm.state.lint
|
||||
var id = ++state.waitingFor
|
||||
function abort() {
|
||||
id = -1
|
||||
cm.off("change", abort)
|
||||
}
|
||||
cm.on("change", abort)
|
||||
getAnnotations(cm.getValue(), function(annotations, arg2) {
|
||||
cm.off("change", abort)
|
||||
if (state.waitingFor != id) return
|
||||
if (arg2 && annotations instanceof CodeMirror) annotations = arg2
|
||||
cm.operation(function() {updateLinting(cm, annotations)})
|
||||
}, state.linterOptions, cm);
|
||||
}
|
||||
|
||||
function startLinting(cm) {
|
||||
var state = cm.state.lint;
|
||||
if (!state) return;
|
||||
var options = state.options;
|
||||
/*
|
||||
* Passing rules in `options` property prevents JSHint (and other linters) from complaining
|
||||
* about unrecognized rules like `onUpdateLinting`, `delay`, `lintOnChange`, etc.
|
||||
*/
|
||||
var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), "lint");
|
||||
if (!getAnnotations) return;
|
||||
if (options.async || getAnnotations.async) {
|
||||
lintAsync(cm, getAnnotations)
|
||||
} else {
|
||||
var annotations = getAnnotations(cm.getValue(), state.linterOptions, cm);
|
||||
if (!annotations) return;
|
||||
if (annotations.then) annotations.then(function(issues) {
|
||||
cm.operation(function() {updateLinting(cm, issues)})
|
||||
});
|
||||
else cm.operation(function() {updateLinting(cm, annotations)})
|
||||
}
|
||||
}
|
||||
|
||||
function updateLinting(cm, annotationsNotSorted) {
|
||||
var state = cm.state.lint;
|
||||
if (!state) return;
|
||||
var options = state.options;
|
||||
clearMarks(cm);
|
||||
|
||||
var annotations = groupByLine(annotationsNotSorted);
|
||||
|
||||
for (var line = 0; line < annotations.length; ++line) {
|
||||
var anns = annotations[line];
|
||||
if (!anns) continue;
|
||||
|
||||
// filter out duplicate messages
|
||||
var message = [];
|
||||
anns = anns.filter(function(item) { return message.indexOf(item.message) > -1 ? false : message.push(item.message) });
|
||||
|
||||
var maxSeverity = null;
|
||||
var tipLabel = state.hasGutter && document.createDocumentFragment();
|
||||
|
||||
for (var i = 0; i < anns.length; ++i) {
|
||||
var ann = anns[i];
|
||||
var severity = ann.severity;
|
||||
if (!severity) severity = "error";
|
||||
maxSeverity = getMaxSeverity(maxSeverity, severity);
|
||||
|
||||
if (options.formatAnnotation) ann = options.formatAnnotation(ann);
|
||||
if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann));
|
||||
|
||||
if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, {
|
||||
className: "CodeMirror-lint-mark CodeMirror-lint-mark-" + severity,
|
||||
__annotation: ann
|
||||
}));
|
||||
}
|
||||
// use original annotations[line] to show multiple messages
|
||||
if (state.hasGutter)
|
||||
cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, annotations[line].length > 1,
|
||||
options.tooltips));
|
||||
|
||||
if (options.highlightLines)
|
||||
cm.addLineClass(line, "wrap", LINT_LINE_ID + maxSeverity);
|
||||
}
|
||||
if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);
|
||||
}
|
||||
|
||||
function onChange(cm) {
|
||||
var state = cm.state.lint;
|
||||
if (!state) return;
|
||||
clearTimeout(state.timeout);
|
||||
state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay);
|
||||
}
|
||||
|
||||
function popupTooltips(cm, annotations, e) {
|
||||
var target = e.target || e.srcElement;
|
||||
var tooltip = document.createDocumentFragment();
|
||||
for (var i = 0; i < annotations.length; i++) {
|
||||
var ann = annotations[i];
|
||||
tooltip.appendChild(annotationTooltip(ann));
|
||||
}
|
||||
showTooltipFor(cm, e, tooltip, target);
|
||||
}
|
||||
|
||||
function onMouseOver(cm, e) {
|
||||
var target = e.target || e.srcElement;
|
||||
if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
|
||||
var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
|
||||
var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client"));
|
||||
|
||||
var annotations = [];
|
||||
for (var i = 0; i < spans.length; ++i) {
|
||||
var ann = spans[i].__annotation;
|
||||
if (ann) annotations.push(ann);
|
||||
}
|
||||
if (annotations.length) popupTooltips(cm, annotations, e);
|
||||
}
|
||||
|
||||
CodeMirror.defineOption("lint", false, function(cm, val, old) {
|
||||
if (old && old != CodeMirror.Init) {
|
||||
clearMarks(cm);
|
||||
if (cm.state.lint.options.lintOnChange !== false)
|
||||
cm.off("change", onChange);
|
||||
CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver);
|
||||
clearTimeout(cm.state.lint.timeout);
|
||||
delete cm.state.lint;
|
||||
}
|
||||
|
||||
if (val) {
|
||||
var gutters = cm.getOption("gutters"), hasLintGutter = false;
|
||||
for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;
|
||||
var state = cm.state.lint = new LintState(cm, val, hasLintGutter);
|
||||
if (state.options.lintOnChange)
|
||||
cm.on("change", onChange);
|
||||
if (state.options.tooltips != false && state.options.tooltips != "gutter")
|
||||
CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
|
||||
|
||||
startLinting(cm);
|
||||
}
|
||||
});
|
||||
|
||||
CodeMirror.defineExtension("performLint", function() {
|
||||
startLinting(this);
|
||||
});
|
||||
});
|
||||
|
1
deps/codemirror/matchesonscrollbar.min.css
vendored
1
deps/codemirror/matchesonscrollbar.min.css
vendored
@ -1 +0,0 @@
|
||||
.CodeMirror-search-match{background:gold;border-top:1px solid orange;border-bottom:1px solid orange;-moz-box-sizing:border-box;box-sizing:border-box;opacity:.5}
|
1
deps/codemirror/matchesonscrollbar.min.js
vendored
1
deps/codemirror/matchesonscrollbar.min.js
vendored
@ -1 +0,0 @@
|
||||
!function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror"),require("./searchcursor"),require("../scroll/annotatescrollbar")):"function"==typeof define&&define.amd?define(["../../lib/codemirror","./searchcursor","../scroll/annotatescrollbar"],t):t(CodeMirror)}(function(h){"use strict";function o(t,e,i,o){this.cm=t;var n,s={listenForChanges:!1};for(n in this.options=o)s[n]=o[n];s.className||(s.className="CodeMirror-search-match"),this.annotation=t.annotateScrollbar(s),this.query=e,this.caseFold=i,this.gap={from:t.firstLine(),to:t.lastLine()+1},this.matches=[],this.update=null,this.findMatches(),this.annotation.update(this.matches);var a=this;t.on("change",this.changeHandler=function(t,e){a.onChange(e)})}h.defineExtension("showMatchesOnScrollbar",function(t,e,i){return new o(this,t,e,i=(i="string"==typeof i?{className:i}:i)||{})});function c(t,e,i){return t<=e?t:Math.max(e,t+i)}o.prototype.findMatches=function(){if(this.gap){for(var t=0;t<this.matches.length;t++){if((e=this.matches[t]).from.line>=this.gap.to)break;e.to.line>=this.gap.from&&this.matches.splice(t--,1)}for(var e,i=this.cm.getSearchCursor(this.query,h.Pos(this.gap.from,0),{caseFold:this.caseFold,multiline:this.options.multiline}),o=this.options&&this.options.maxMatches||1e3;i.findNext();){if((e={from:i.from(),to:i.to()}).from.line>=this.gap.to)break;if(this.matches.splice(t++,0,e),this.matches.length>o)break}this.gap=null}},o.prototype.onChange=function(t){var e=t.from.line,i=h.changeEnd(t).line,o=i-t.to.line;if(this.gap?(this.gap.from=Math.min(c(this.gap.from,e,o),t.from.line),this.gap.to=Math.max(c(this.gap.to,e,o),t.from.line)):this.gap={from:t.from.line,to:i+1},o)for(var n=0;n<this.matches.length;n++){var s=this.matches[n],a=c(s.from.line,e,o),a=(a!=s.from.line&&(s.from=h.Pos(a,s.from.ch)),c(s.to.line,e,o));a!=s.to.line&&(s.to=h.Pos(a,s.to.ch))}clearTimeout(this.update);var r=this;this.update=setTimeout(function(){r.updateAfterChange()},250)},o.prototype.updateAfterChange=function(){this.findMatches(),this.annotation.update(this.matches)},o.prototype.clear=function(){this.cm.off("change",this.changeHandler),this.annotation.clear()}});
|
1
deps/codemirror/search.min.js
vendored
1
deps/codemirror/search.min.js
vendored
File diff suppressed because one or more lines are too long
1
deps/codemirror/searchcursor.min.js
vendored
1
deps/codemirror/searchcursor.min.js
vendored
File diff suppressed because one or more lines are too long
1
deps/codemirror/trailingspace.min.js
vendored
1
deps/codemirror/trailingspace.min.js
vendored
@ -1 +0,0 @@
|
||||
!function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(o){o.defineOption("showTrailingSpace",!1,function(e,i,n){(n=n==o.Init?!1:n)&&!i?e.removeOverlay("trailingspace"):!n&&i&&e.addOverlay({token:function(e){for(var i=e.string.length,n=i;n&&/\s/.test(e.string.charAt(n-1));--n);return n>e.pos?(e.pos=n,null):(e.pos=i,"trailingspace")},name:"trailingspace"})})});
|
22
deps/codemirror/update.sh
vendored
22
deps/codemirror/update.sh
vendored
@ -1,22 +0,0 @@
|
||||
LINKS="
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/dialog/dialog.min.css
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/dialog/dialog.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/edit/trailingspace.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/lint/javascript-lint.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/scroll/annotatescrollbar.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/search/matchesonscrollbar.min.css
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/search/matchesonscrollbar.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/search/search.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/search/searchcursor.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/css/css.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/htmlmixed/htmlmixed.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/javascript/javascript.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/xml/xml.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/base16-dark.min.css
|
||||
"
|
||||
|
||||
for link in $LINKS; do
|
||||
wget $link -O `basename $link`
|
||||
done
|
1
deps/codemirror/xml.min.js
vendored
1
deps/codemirror/xml.min.js
vendored
File diff suppressed because one or more lines are too long
70
deps/codemirror_src/editor.mjs
vendored
Normal file
70
deps/codemirror_src/editor.mjs
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
import {EditorState} from "@codemirror/state"
|
||||
import {EditorView} from '@codemirror/view';
|
||||
import {javascript} from "@codemirror/lang-javascript"
|
||||
import {html} from "@codemirror/lang-html"
|
||||
import {css} from "@codemirror/lang-css"
|
||||
import {search} from "@codemirror/search"
|
||||
import {oneDark} from "./theme-tf-dark.js"
|
||||
import {lineNumbers, highlightActiveLineGutter, highlightSpecialChars, highlightTrailingWhitespace, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightActiveLine, keymap, highlightWhitespace} from '@codemirror/view';
|
||||
import {foldGutter, indentUnit, indentOnInput, syntaxHighlighting, defaultHighlightStyle, bracketMatching, foldKeymap} from '@codemirror/language';
|
||||
import {history, defaultKeymap, historyKeymap, indentWithTab} from '@codemirror/commands';
|
||||
import {highlightSelectionMatches, searchKeymap} from '@codemirror/search';
|
||||
import {autocompletion, closeBracketsKeymap, completionKeymap} from '@codemirror/autocomplete';
|
||||
import {lintKeymap} from '@codemirror/lint';
|
||||
|
||||
let updateListenerExtension = EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged && update.view.onDocChange) {
|
||||
update.view.onDocChange();
|
||||
}
|
||||
});
|
||||
|
||||
const extensions = [
|
||||
lineNumbers(),
|
||||
highlightActiveLineGutter(),
|
||||
highlightSpecialChars(),
|
||||
highlightWhitespace(),
|
||||
history(),
|
||||
foldGutter(),
|
||||
drawSelection(),
|
||||
dropCursor(),
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
indentOnInput(),
|
||||
indentUnit.of('\t'),
|
||||
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
||||
bracketMatching(),
|
||||
autocompletion(),
|
||||
rectangularSelection(),
|
||||
crosshairCursor(),
|
||||
highlightActiveLine(),
|
||||
highlightSelectionMatches(),
|
||||
highlightTrailingWhitespace(),
|
||||
keymap.of([
|
||||
...defaultKeymap,
|
||||
...searchKeymap,
|
||||
...historyKeymap,
|
||||
...foldKeymap,
|
||||
...completionKeymap,
|
||||
...lintKeymap,
|
||||
indentWithTab,
|
||||
]),
|
||||
javascript(),
|
||||
html(),
|
||||
css(),
|
||||
search(),
|
||||
oneDark,
|
||||
updateListenerExtension,
|
||||
];
|
||||
|
||||
function TildeFriendsEditorView(parent) {
|
||||
return new EditorView({
|
||||
extensions: extensions,
|
||||
parent: parent,
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
TildeFriendsEditorView,
|
||||
EditorState,
|
||||
EditorView,
|
||||
extensions,
|
||||
};
|
15
deps/codemirror_src/package.json
vendored
Normal file
15
deps/codemirror_src/package.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@codemirror/lang-css": "^6.2.1",
|
||||
"@codemirror/lang-html": "^6.4.8",
|
||||
"@codemirror/lang-javascript": "^6.2.1",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"codemirror": "^6.0.1",
|
||||
"rollup": "^4.9.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-terser": "^0.4.4"
|
||||
}
|
||||
}
|
12
deps/codemirror_src/rollup.config.mjs
vendored
Normal file
12
deps/codemirror_src/rollup.config.mjs
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import {nodeResolve} from "@rollup/plugin-node-resolve"
|
||||
import terser from '@rollup/plugin-terser';
|
||||
export default {
|
||||
input: "./editor.mjs",
|
||||
output: {
|
||||
file: "../codemirror/cm6.js",
|
||||
format: "es",
|
||||
plugins: [terser()],
|
||||
},
|
||||
plugins: [nodeResolve()]
|
||||
}
|
||||
|
134
deps/codemirror_src/theme-tf-dark.js
vendored
Normal file
134
deps/codemirror_src/theme-tf-dark.js
vendored
Normal file
@ -0,0 +1,134 @@
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||
import { tags } from '@lezer/highlight';
|
||||
|
||||
// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors
|
||||
const chalky = "#e5c07b", coral = "#e06c75", cyan = "#56b6c2", invalid = "#ffffff", ivory = "#abb2bf", stone = "#7d8799", // Brightened compared to original to increase contrast
|
||||
malibu = "#61afef", sage = "#98c379", whiskey = "#d19a66", violet = "#c678dd", darkBackground = "#21252b", highlightBackground = "#2c313a", background = "#14161a", tooltipBackground = "#353a42", selection = "#3E4451", cursor = "#528bff";
|
||||
/**
|
||||
The colors used in the theme, as CSS color strings.
|
||||
*/
|
||||
const color = {
|
||||
chalky,
|
||||
coral,
|
||||
cyan,
|
||||
invalid,
|
||||
ivory,
|
||||
stone,
|
||||
malibu,
|
||||
sage,
|
||||
whiskey,
|
||||
violet,
|
||||
darkBackground,
|
||||
highlightBackground,
|
||||
background,
|
||||
tooltipBackground,
|
||||
selection,
|
||||
cursor
|
||||
};
|
||||
/**
|
||||
The editor theme styles for One Dark.
|
||||
*/
|
||||
const oneDarkTheme = /*@__PURE__*/EditorView.theme({
|
||||
"&": {
|
||||
color: ivory,
|
||||
backgroundColor: background
|
||||
},
|
||||
".cm-content": {
|
||||
caretColor: cursor
|
||||
},
|
||||
".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor },
|
||||
"&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": { backgroundColor: selection },
|
||||
".cm-panels": { backgroundColor: darkBackground, color: ivory },
|
||||
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
|
||||
".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
|
||||
".cm-searchMatch": {
|
||||
backgroundColor: "#72a1ff59",
|
||||
outline: "1px solid #457dff"
|
||||
},
|
||||
".cm-searchMatch.cm-searchMatch-selected": {
|
||||
backgroundColor: "#6199ff2f"
|
||||
},
|
||||
".cm-activeLine": { backgroundColor: "#6699ff0b" },
|
||||
".cm-selectionMatch": { backgroundColor: "#aafe661a" },
|
||||
"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": {
|
||||
backgroundColor: "#bad0f847"
|
||||
},
|
||||
".cm-gutters": {
|
||||
backgroundColor: background,
|
||||
color: stone,
|
||||
border: "none"
|
||||
},
|
||||
".cm-activeLineGutter": {
|
||||
backgroundColor: highlightBackground
|
||||
},
|
||||
".cm-foldPlaceholder": {
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
color: "#ddd"
|
||||
},
|
||||
".cm-tooltip": {
|
||||
border: "none",
|
||||
backgroundColor: tooltipBackground
|
||||
},
|
||||
".cm-tooltip .cm-tooltip-arrow:before": {
|
||||
borderTopColor: "transparent",
|
||||
borderBottomColor: "transparent"
|
||||
},
|
||||
".cm-tooltip .cm-tooltip-arrow:after": {
|
||||
borderTopColor: tooltipBackground,
|
||||
borderBottomColor: tooltipBackground
|
||||
},
|
||||
".cm-tooltip-autocomplete": {
|
||||
"& > ul > li[aria-selected]": {
|
||||
backgroundColor: highlightBackground,
|
||||
color: ivory
|
||||
}
|
||||
}
|
||||
}, { dark: true });
|
||||
/**
|
||||
The highlighting style for code in the One Dark theme.
|
||||
*/
|
||||
const oneDarkHighlightStyle = /*@__PURE__*/HighlightStyle.define([
|
||||
{ tag: tags.keyword,
|
||||
color: violet },
|
||||
{ tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName],
|
||||
color: coral },
|
||||
{ tag: [/*@__PURE__*/tags.function(tags.variableName), tags.labelName],
|
||||
color: malibu },
|
||||
{ tag: [tags.color, /*@__PURE__*/tags.constant(tags.name), /*@__PURE__*/tags.standard(tags.name)],
|
||||
color: whiskey },
|
||||
{ tag: [/*@__PURE__*/tags.definition(tags.name), tags.separator],
|
||||
color: ivory },
|
||||
{ tag: [tags.typeName, tags.className, tags.number, tags.changed, tags.annotation, tags.modifier, tags.self, tags.namespace],
|
||||
color: chalky },
|
||||
{ tag: [tags.operator, tags.operatorKeyword, tags.url, tags.escape, tags.regexp, tags.link, /*@__PURE__*/tags.special(tags.string)],
|
||||
color: cyan },
|
||||
{ tag: [tags.meta, tags.comment],
|
||||
color: stone },
|
||||
{ tag: tags.strong,
|
||||
fontWeight: "bold" },
|
||||
{ tag: tags.emphasis,
|
||||
fontStyle: "italic" },
|
||||
{ tag: tags.strikethrough,
|
||||
textDecoration: "line-through" },
|
||||
{ tag: tags.link,
|
||||
color: stone,
|
||||
textDecoration: "underline" },
|
||||
{ tag: tags.heading,
|
||||
fontWeight: "bold",
|
||||
color: coral },
|
||||
{ tag: [tags.atom, tags.bool, /*@__PURE__*/tags.special(tags.variableName)],
|
||||
color: whiskey },
|
||||
{ tag: [tags.processingInstruction, tags.string, tags.inserted],
|
||||
color: sage },
|
||||
{ tag: tags.invalid,
|
||||
color: invalid },
|
||||
]);
|
||||
/**
|
||||
Extension to enable the One Dark theme (both the editor theme and
|
||||
the highlight style).
|
||||
*/
|
||||
const oneDark = [oneDarkTheme, /*@__PURE__*/syntaxHighlighting(oneDarkHighlightStyle)];
|
||||
|
||||
export { color, oneDark, oneDarkHighlightStyle, oneDarkTheme };
|
12
deps/libsodium/.github/workflows/ci.yml
vendored
12
deps/libsodium/.github/workflows/ci.yml
vendored
@ -40,17 +40,16 @@ jobs:
|
||||
- name: Update packages list
|
||||
run: sudo apt-get update
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
curl -sL -o - https://ziglang.org/download/0.11.0/zig-linux-x86_64-0.11.0.tar.xz | tar xJ -f - -C /opt/
|
||||
sudo mv /opt/zig-* /opt/zig
|
||||
- name: Install Zig
|
||||
uses: goto-bus-stop/setup-zig@d866436887ad1b24590684f9d00480376663dd36
|
||||
with:
|
||||
version: 0.11.0
|
||||
|
||||
- name: Autogen
|
||||
run: ./autogen.sh -s
|
||||
|
||||
- name: Compilation with zig
|
||||
run: |
|
||||
export PATH=/opt/zig:$PATH
|
||||
zig build
|
||||
zig build -Dtarget=x86_64-linux
|
||||
zig build -Dtarget=aarch64-linux
|
||||
@ -177,5 +176,4 @@ jobs:
|
||||
|
||||
- name: Android compilation
|
||||
run: |
|
||||
env ANDROID_NDK_HOME=/tmp/android/ndk ./dist-build/android-x86.sh
|
||||
env ANDROID_NDK_HOME=/tmp/android/ndk ./dist-build/android-armv8-a.sh
|
||||
env ANDROID_NDK_HOME=/tmp/android/ndk ./dist-build/android-aar.sh
|
||||
|
12
deps/libsodium/.github/workflows/dotnet-core.yml
vendored
12
deps/libsodium/.github/workflows/dotnet-core.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Zig
|
||||
uses: goto-bus-stop/setup-zig@6fede2f0550d71291c0accf2834b216e69a2d67a
|
||||
uses: goto-bus-stop/setup-zig@d866436887ad1b24590684f9d00480376663dd36
|
||||
with:
|
||||
version: 0.11.0
|
||||
- uses: actions/checkout@v3
|
||||
@ -46,7 +46,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Zig
|
||||
uses: goto-bus-stop/setup-zig@6fede2f0550d71291c0accf2834b216e69a2d67a
|
||||
uses: goto-bus-stop/setup-zig@d866436887ad1b24590684f9d00480376663dd36
|
||||
with:
|
||||
version: 0.11.0
|
||||
- name: Set up emulation environment
|
||||
@ -74,7 +74,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Zig
|
||||
uses: goto-bus-stop/setup-zig@6fede2f0550d71291c0accf2834b216e69a2d67a
|
||||
uses: goto-bus-stop/setup-zig@d866436887ad1b24590684f9d00480376663dd36
|
||||
with:
|
||||
version: 0.11.0
|
||||
- name: Set up emulation environment
|
||||
@ -108,7 +108,7 @@ jobs:
|
||||
apk update
|
||||
apk add alpine-sdk ca-certificates xz
|
||||
- name: Install Zig
|
||||
uses: goto-bus-stop/setup-zig@6fede2f0550d71291c0accf2834b216e69a2d67a
|
||||
uses: goto-bus-stop/setup-zig@d866436887ad1b24590684f9d00480376663dd36
|
||||
with:
|
||||
version: 0.11.0
|
||||
- uses: actions/checkout@v3
|
||||
@ -127,7 +127,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Zig
|
||||
uses: goto-bus-stop/setup-zig@6fede2f0550d71291c0accf2834b216e69a2d67a
|
||||
uses: goto-bus-stop/setup-zig@d866436887ad1b24590684f9d00480376663dd36
|
||||
with:
|
||||
version: 0.11.0
|
||||
- uses: actions/checkout@v3
|
||||
@ -143,7 +143,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Zig
|
||||
uses: goto-bus-stop/setup-zig@6fede2f0550d71291c0accf2834b216e69a2d67a
|
||||
uses: goto-bus-stop/setup-zig@d866436887ad1b24590684f9d00480376663dd36
|
||||
with:
|
||||
version: 0.11.0
|
||||
- uses: actions/checkout@v3
|
||||
|
5
deps/libsodium/ChangeLog
vendored
5
deps/libsodium/ChangeLog
vendored
@ -1,10 +1,13 @@
|
||||
* Version 1.0.19-stable
|
||||
- Building with `zig build` now works both with Zig 0.11 and Zig 0.12.
|
||||
- Building with `zig build` now requires Zig 0.12.
|
||||
- When using the traditional build system, -O3 is used instead of -Ofast.
|
||||
- Improved detection of the compiler flags required on aarch64.
|
||||
- Improved compatibility with custom build systems on aarch64.
|
||||
- apple-xcframework: VisionOS packages are not built if Xcode doesn't
|
||||
include that SDK.
|
||||
- `crypto_kdf_hkdf_sha512_statebytes()` was added.
|
||||
- When using Visual Studio, runtime CPU feature detection is now enabled
|
||||
on Windows/aarch64.
|
||||
|
||||
* Version 1.0.19
|
||||
This release includes all the changes from 1.0.18-stable, as well as two
|
||||
|
2
deps/libsodium/LICENSE
vendored
2
deps/libsodium/LICENSE
vendored
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* ISC License
|
||||
*
|
||||
* Copyright (c) 2013-2023
|
||||
* Copyright (c) 2013-2024
|
||||
* Frank Denis <j at pureftpd dot org>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
|
4
deps/libsodium/aclocal.m4
vendored
4
deps/libsodium/aclocal.m4
vendored
@ -14,8 +14,8 @@
|
||||
m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])])
|
||||
m4_ifndef([AC_AUTOCONF_VERSION],
|
||||
[m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
|
||||
m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.71],,
|
||||
[m4_warning([this file was generated for autoconf 2.71.
|
||||
m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.72],,
|
||||
[m4_warning([this file was generated for autoconf 2.72.
|
||||
You have another version of autoconf. It may work, but is not guaranteed to.
|
||||
If you have problems, you may need to regenerate the build system entirely.
|
||||
To do so, use the procedure documented by the package, typically 'autoreconf'.])])
|
||||
|
4
deps/libsodium/azure-pipelines.yml
vendored
4
deps/libsodium/azure-pipelines.yml
vendored
@ -48,7 +48,7 @@ jobs:
|
||||
vmImage: "windows-2019"
|
||||
steps:
|
||||
- powershell: |
|
||||
(New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2023-07-18/msys2-base-x86_64-20230718.sfx.exe", "sfx.exe")
|
||||
(New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2024-01-13/msys2-base-x86_64-20230718.sfx.exe", "sfx.exe")
|
||||
.\sfx.exe -y -o\
|
||||
del sfx.exe
|
||||
displayName: Install MSYS2
|
||||
@ -87,7 +87,7 @@ jobs:
|
||||
vmImage: "windows-2019"
|
||||
steps:
|
||||
- powershell: |
|
||||
(New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2023-07-18/msys2-base-x86_64-20230718.sfx.exe", "sfx.exe")
|
||||
(New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2024-01-13/msys2-base-x86_64-20240113.sfx.exe", "sfx.exe")
|
||||
.\sfx.exe -y -o\
|
||||
del sfx.exe
|
||||
displayName: Install MSYS2
|
||||
|
4
deps/libsodium/build.zig
vendored
4
deps/libsodium/build.zig
vendored
@ -249,7 +249,7 @@ pub fn build(b: *std.build.Builder) !void {
|
||||
else => {},
|
||||
}
|
||||
|
||||
var allocator = heap.page_allocator;
|
||||
const allocator = heap.page_allocator;
|
||||
var walker = try src_dir.walk(allocator);
|
||||
while (try walker.next()) |entry| {
|
||||
const name = entry.basename;
|
||||
@ -280,7 +280,7 @@ pub fn build(b: *std.build.Builder) !void {
|
||||
fs.Dir.makePath(cwd, out_bin_path) catch {};
|
||||
const out_bin_dir = try fs.Dir.openDir(cwd, out_bin_path, .{});
|
||||
try test_dir.dir.copyFile("run.sh", out_bin_dir, "run.sh", .{});
|
||||
var allocator = heap.page_allocator;
|
||||
const allocator = heap.page_allocator;
|
||||
var walker = try test_dir.walk(allocator);
|
||||
if (build_tests) {
|
||||
while (try walker.next()) |entry| {
|
||||
|
2
deps/libsodium/builds/msvc/resource.rc
vendored
2
deps/libsodium/builds/msvc/resource.rc
vendored
@ -49,7 +49,7 @@ BEGIN
|
||||
VALUE "FileDescription", "The Sodium crypto library (libsodium)"
|
||||
VALUE "FileVersion", LIBSODIUM_VERSION_STRING
|
||||
VALUE "InternalName", "libsodium"
|
||||
VALUE "LegalCopyright", "Copyright (c) 2013-2023 The libsodium authors."
|
||||
VALUE "LegalCopyright", "Copyright (c) 2013-2024 The libsodium authors."
|
||||
VALUE "OriginalFilename", "libsodium.dll"
|
||||
VALUE "ProductName", LIBSODIUM_PRODUCT_NAME
|
||||
VALUE "ProductVersion", LIBSODIUM_VERSION_STRING
|
||||
|
4096
deps/libsodium/configure
vendored
4096
deps/libsodium/configure
vendored
File diff suppressed because it is too large
Load Diff
2
deps/libsodium/dist-build/android-build.sh
vendored
2
deps/libsodium/dist-build/android-build.sh
vendored
@ -62,7 +62,7 @@ fi
|
||||
--with-sysroot="${TOOLCHAIN_DIR}/sysroot" || exit 1
|
||||
|
||||
if [ "$NDK_PLATFORM" != "$NDK_PLATFORM_COMPAT" ]; then
|
||||
egrep '^#define ' config.log | sort -u >config-def-compat.log
|
||||
grep -E '^#define ' config.log | sort -u >config-def-compat.log
|
||||
echo
|
||||
echo "Configuring again for platform [${NDK_PLATFORM}]"
|
||||
echo
|
||||
|
4
deps/libsodium/dist-build/emscripten.sh
vendored
4
deps/libsodium/dist-build/emscripten.sh
vendored
File diff suppressed because one or more lines are too long
@ -116,3 +116,8 @@ crypto_kdf_hkdf_sha512_bytes_max(void)
|
||||
{
|
||||
return crypto_kdf_hkdf_sha512_BYTES_MAX;
|
||||
}
|
||||
|
||||
size_t crypto_kdf_hkdf_sha512_statebytes(void)
|
||||
{
|
||||
return sizeof(crypto_kdf_hkdf_sha512_state);
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ crypto_sign_ed25519_pk_to_curve25519(unsigned char *curve25519_pk,
|
||||
return -1;
|
||||
}
|
||||
fe25519_1(one_minus_y);
|
||||
/* assumes A.Z=1 */
|
||||
fe25519_sub(one_minus_y, one_minus_y, A.Y);
|
||||
fe25519_1(x);
|
||||
fe25519_add(x, x, A.Y);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user