Compare commits
229 Commits
v0.0.13
...
bfbfc01e99
Author | SHA1 | Date | |
---|---|---|---|
bfbfc01e99 | |||
8fa9d0e843 | |||
260706c172 | |||
390668ec34 | |||
f23414adaf | |||
41024ddb79 | |||
4bfd9de100 | |||
c01e00d77d | |||
825191c08f | |||
9dc6670795 | |||
1db8eee9f7 | |||
1bc50cb62c | |||
450b07fd08 | |||
12c7515ee8 | |||
ed65da4340 | |||
d9d2917cf5 | |||
ce5ca1875b | |||
4f869252a2 | |||
17b92126de | |||
6e88c44229 | |||
6c3d338c12 | |||
4ebd44cb4e | |||
75cb9f7fd2 | |||
eacca9d2ab | |||
d0e11bc68b | |||
1958623a7a | |||
498d8b6520 | |||
a12f2fec5a | |||
22bf046643 | |||
dca48fae36 | |||
9af4068bb6 | |||
2992d8ec12 | |||
33dd2560e0 | |||
aeb5c6ee25 | |||
08a2436b8f | |||
fbc3cfeda4 | |||
c8812b1add | |||
8d82e80639 | |||
ed741d53d7 | |||
685754895b | |||
e7791d38ff | |||
9f14653001 | |||
6c5a7b0751 | |||
51a327c52d | |||
5a978bb30d | |||
6801758cb3 | |||
14de3dd9e5 | |||
ed2d57fb4b | |||
e87acc6286 | |||
0de932bc9e | |||
d021d9f757 | |||
eb5da26004 | |||
6765254f43 | |||
e98802f5b2 | |||
af54b6483e | |||
96167c3167 | |||
eecfdf482f | |||
7ceb865206 | |||
b919670706 | |||
72120b8842 | |||
1e53c08d9d | |||
2d1b6a09e9 | |||
7f661d9af9 | |||
81c66bdddd | |||
4bd46a1657 | |||
244a752ae1 | |||
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 | |||
f4f560b164 | |||
14b7f9237b | |||
f3518b3d0f | |||
7964524e0a | |||
8ab8335baa | |||
cd43bf9dfa | |||
ccebf831e7 | |||
9f2f9bd8b0 | |||
adf8c14536 | |||
606e82d718 | |||
1621f1753a | |||
196ab66e14 | |||
38ab32dad9 | |||
86046e52f0 | |||
9e7c860414 | |||
7dc8b86ee2 | |||
6ecbfe3de6 | |||
f9940fc436 | |||
58e75ee276 | |||
e7771f539d | |||
c2f62cd8e0 | |||
f4b6812675 | |||
03e4b37c04 | |||
7b3a9e0f63 | |||
067f546580 | |||
2f7697b7ec | |||
1d214f89ed | |||
0b47207949 | |||
94dd573a81 | |||
6fa4896155 | |||
28c99f9d8b | |||
88fbb5f73b | |||
402c185dd4 | |||
ae2015a604 | |||
023731fc3f | |||
99998aac8a | |||
360d0bc110 | |||
817838e522 | |||
deb3cfb4b6 | |||
af61519632 | |||
b1714cf554 | |||
f0984b19f2 | |||
eb3c9cd6f3 | |||
e677b0ac3c | |||
dd909bfe53 | |||
b13b111614 | |||
5511530926 | |||
5e1ef01bc0 | |||
a060eadab7 | |||
70db31bb8f | |||
1292775a75 |
20
.clang-format
Normal file
20
.clang-format
Normal file
@ -0,0 +1,20 @@
|
||||
# Format Style Options - Created with Clang Power Tools
|
||||
---
|
||||
BasedOnStyle: WebKit
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlignOperands: DontAlign
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Allman
|
||||
ColumnLimit: 180
|
||||
ContinuationIndentWidth: 4
|
||||
IndentCaseBlocks: true
|
||||
IndentWidth: 4
|
||||
MaxEmptyLinesToKeep: 1
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCBreakBeforeNestedBlockParam: false
|
||||
SortIncludes: false
|
||||
TabWidth: 4
|
||||
UseTab: Always
|
||||
...
|
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
@ -0,0 +1,2 @@
|
||||
# Add prettier to the project
|
||||
41024ddb7961b04a5688bbc997cb74de6fab4763
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
**/node_modules
|
||||
.keys
|
||||
.zsign_cache/
|
||||
db.*
|
||||
deps/ios_toolchain/
|
||||
deps/openssl/
|
||||
dist/
|
||||
out
|
14
.prettierignore
Normal file
14
.prettierignore
Normal file
@ -0,0 +1,14 @@
|
||||
node_modules
|
||||
src
|
||||
deps
|
||||
.clang-format
|
||||
|
||||
# Minified files
|
||||
**/*.min.css
|
||||
**/*.min.js
|
||||
**/leaflet.*
|
||||
**/commonmark*
|
||||
**/w3.css
|
||||
apps/ssb/tribute.esm.js
|
||||
apps/api/app.js
|
||||
**/emojis.json
|
10
.prettierrc.yaml
Normal file
10
.prettierrc.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
trailingComma: 'es5'
|
||||
useTabs: true
|
||||
semi: true
|
||||
singleQuote: true
|
||||
bracketSpacing: false
|
||||
# overrides:
|
||||
# - files: '**/*.json'
|
||||
# options:
|
||||
# useTabs: false
|
||||
# tabWidth: 2
|
135
GNUmakefile
135
GNUmakefile
@ -3,9 +3,9 @@
|
||||
MAKEFLAGS += --warn-undefined-variables
|
||||
MAKEFLAGS += --no-builtin-rules
|
||||
|
||||
VERSION_CODE := 13
|
||||
VERSION_NUMBER := 0.0.13
|
||||
VERSION_NAME := Served on grilled naan or gluten free sweet potato flatbread.
|
||||
VERSION_CODE := 16
|
||||
VERSION_NUMBER := 0.0.16-wip
|
||||
VERSION_NAME := Medium English breakfast tea.
|
||||
|
||||
PROJECT = tildefriends
|
||||
BUILD_DIR ?= out
|
||||
@ -42,18 +42,20 @@ $(error Unexpected host platform $(UNAME_S).)
|
||||
endif
|
||||
|
||||
CFLAGS += \
|
||||
-std=gnu11 \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-Wno-unused-parameter \
|
||||
-MMD \
|
||||
-MP \
|
||||
-ffunction-sections \
|
||||
-fdata-sections \
|
||||
-fno-exceptions \
|
||||
-g
|
||||
|
||||
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0
|
||||
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-33
|
||||
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.0.10792818
|
||||
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-34
|
||||
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.1.10909125
|
||||
ANDROID_MIN_SDK_VERSION := 24
|
||||
ANDROID_TARGET_SDK_VERSION := 34
|
||||
|
||||
@ -158,7 +160,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 += \
|
||||
@ -212,6 +214,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))))) \
|
||||
@ -448,8 +455,10 @@ $(SODIUM_OBJS): CFLAGS += \
|
||||
-Wno-attributes \
|
||||
-Ideps/libsodium/builds/msvc \
|
||||
-Ideps/libsodium/src/libsodium/include/sodium
|
||||
$(SODIUM_OBJS_unix): CFLAGS += \
|
||||
ifneq ($(UNAME_S),OpenBSD)
|
||||
$(filter-out $(BUILD_DIR)/win%,$(SODIUM_OBJS)): CFLAGS += \
|
||||
-DHAVE_ALLOCA_H
|
||||
endif
|
||||
|
||||
SQLITE_SOURCES := deps/sqlite/sqlite3.c
|
||||
SQLITE_OBJS := $(call get_objs,SQLITE_SOURCES)
|
||||
@ -497,7 +506,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 \
|
||||
@ -518,6 +528,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 \
|
||||
@ -632,34 +648,34 @@ $(1): $(BUILD_DIR)/$(1)/$(PROJECT)$(if $(filter win%,$(1)),.exe)
|
||||
.PHONY: $(1)
|
||||
|
||||
$(BUILD_DIR)/$(1)/$(PROJECT)$(if $(filter win%,$(1)),.exe): $(filter $(BUILD_DIR)/$(1)/%,$(ALL_APP_OBJS))
|
||||
@echo [link] $$@
|
||||
@echo "[link] $$@"
|
||||
@$$(CC) -o $$@ $$^ $$(LDFLAGS)
|
||||
|
||||
$(BUILD_DIR)/$(1)/%.o: %.c
|
||||
@mkdir -p $$(dir $$@)
|
||||
@echo [c] $$@
|
||||
@echo "[c] $$@"
|
||||
@$$(CC) $$(CFLAGS) -c $$< -o $$@
|
||||
|
||||
$(BUILD_DIR)/$(1)/%.o: %.m
|
||||
@mkdir -p $$(dir $$@)
|
||||
@echo [m] $$@
|
||||
@echo "[m] $$@"
|
||||
@$$(CC) $$(CFLAGS) -c $$< -o $$@
|
||||
|
||||
$(BUILD_DIR)/$(1)/%.o: %.S
|
||||
@mkdir -p $$(dir $$@)
|
||||
@echo [as] $$@
|
||||
@echo "[as] $$@"
|
||||
@$$(AS) -c $$< -o $$@
|
||||
endef
|
||||
|
||||
$(foreach build_type,$(BUILD_TYPES),$(eval $(call build_rules,$(build_type))))
|
||||
|
||||
src/version.h : $(firstword $(MAKEFILE_LIST))
|
||||
@echo [version] $@
|
||||
@echo "[version] $@"
|
||||
@echo "#define VERSION_NUMBER \"$(VERSION_NUMBER)\"" > $@
|
||||
@echo "#define VERSION_NAME \"$(VERSION_NAME)\"" >> $@
|
||||
|
||||
src/android/AndroidManifest.xml : $(firstword $(MAKEFILE_LIST))
|
||||
@echo [android_version] $@
|
||||
@echo "[android_version] $@"
|
||||
@sed -i \
|
||||
-e 's/versionCode=".*"/versionCode="$(VERSION_CODE)"/' \
|
||||
-e 's/versionName=".*"/versionName="$(VERSION_NUMBER)"/' \
|
||||
@ -670,12 +686,12 @@ src/android/AndroidManifest.xml : $(firstword $(MAKEFILE_LIST))
|
||||
# Android support.
|
||||
out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml
|
||||
@mkdir -p $(dir $@)
|
||||
@echo [aapt2] $@
|
||||
@echo "[aapt2] $@"
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ src/android/res/layout/activity_main.xml
|
||||
|
||||
out/res/drawable_icon.xml.flat: src/android/res/drawable/icon.xml
|
||||
@mkdir -p $(dir $@)
|
||||
@echo [aapt2] $@
|
||||
@echo "[aapt2] $@"
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ src/android/res/drawable/icon.xml
|
||||
|
||||
out/apk/res.apk out/gen/com/unprompted/tildefriends/R.java: out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat src/android/AndroidManifest.xml
|
||||
@ -686,21 +702,22 @@ JAVA_FILES := out/gen/com/unprompted/tildefriends/R.java $(wildcard src/android/
|
||||
CLASS_FILES := $(foreach src,$(JAVA_FILES),out/classes/com/unprompted/tildefriends/$(notdir $(src:.java=.class)))
|
||||
|
||||
$(CLASS_FILES) &: $(JAVA_FILES)
|
||||
@echo [javac] $(CLASS_FILES)
|
||||
@echo "[javac] $(CLASS_FILES)"
|
||||
@javac --release 8 -Xlint:deprecation -classpath $(ANDROID_PLATFORM)/android.jar -d out/classes $(JAVA_FILES)
|
||||
|
||||
out/apk/classes.dex: $(CLASS_FILES)
|
||||
@mkdir -p $(dir $@)
|
||||
@echo [d8] $@
|
||||
@echo "[d8] $@"
|
||||
@$(ANDROID_BUILD_TOOLS)/d8 --$(BUILD_TYPE) --lib $(ANDROID_PLATFORM)/android.jar --output $(dir $@) out/classes/com/unprompted/tildefriends/*.class
|
||||
|
||||
PACKAGE_DIRS := \
|
||||
apps/ \
|
||||
core/ \
|
||||
deps/codemirror/ \
|
||||
deps/prettier/ \
|
||||
deps/lit/
|
||||
|
||||
RAW_FILES := $(filter-out apps/gg% apps/welcome% %.map, $(shell find $(PACKAGE_DIRS) -type f))
|
||||
RAW_FILES := $(filter-out apps/blog% apps/gg% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f))
|
||||
|
||||
out/apk/TildeFriends-arm-debug.unsigned.apk: BUILD_TYPE := debug
|
||||
out/apk/TildeFriends-arm-release.unsigned.apk: BUILD_TYPE := release
|
||||
@ -714,7 +731,7 @@ out/apk/TildeFriends-x86-release.unsigned.apk: out/apk/classes.dex out/androidre
|
||||
|
||||
out/apk/TildeFriends-arm-%.unsigned.apk:
|
||||
@mkdir -p $(dir $@) out/apk-arm-$(BUILD_TYPE)/lib/arm64-v8a/ out/apk-arm-$(BUILD_TYPE)/lib/armeabi-v7a/
|
||||
@echo [aapt] $@
|
||||
@echo "[aapt] $@"
|
||||
@cp out/android$(BUILD_TYPE)/tildefriends out/apk-arm-$(BUILD_TYPE)/lib/arm64-v8a/tildefriends.so
|
||||
@cp out/android$(BUILD_TYPE)-armv7a/tildefriends out/apk-arm-$(BUILD_TYPE)/lib/armeabi-v7a/tildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-arm-$(BUILD_TYPE)/lib/arm64-v8a/tildefriends.so
|
||||
@ -722,22 +739,22 @@ out/apk/TildeFriends-arm-%.unsigned.apk:
|
||||
@cp out/apk/res.apk $@
|
||||
@cp out/apk/classes.dex out/apk-arm-$(BUILD_TYPE)/
|
||||
@cd out/apk-arm-$(BUILD_TYPE) && zip -u ../../$@ -q -9 -r . && cd ../../
|
||||
@zip -u $@ -q -9 -x '*.map' --exclude=apps/gg* --exclude=apps/welcome* -r $(PACKAGE_DIRS) $(RAW_FILES)
|
||||
@zip -u $@ -q -9 $(RAW_FILES)
|
||||
|
||||
out/apk/TildeFriends-x86-%.unsigned.apk:
|
||||
@mkdir -p $(dir $@) out/apk-x86-$(BUILD_TYPE)/lib/x86_64/ out/apk-x86-$(BUILD_TYPE)/lib/x86/
|
||||
@echo [aapt] $@
|
||||
@cp out/android$(BUILD_TYPE)-x86_64/tildefriends out/apk-x86-$(BUILD_TYPE)/lib/x86_64/
|
||||
@cp out/android$(BUILD_TYPE)-x86/tildefriends out/apk-x86-$(BUILD_TYPE)/lib/x86/
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-x86-$(BUILD_TYPE)/lib/x86_64/tildefriends
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-x86-$(BUILD_TYPE)/lib/x86/tildefriends
|
||||
@echo "[aapt] $@"
|
||||
@cp out/android$(BUILD_TYPE)-x86_64/tildefriends out/apk-x86-$(BUILD_TYPE)/lib/x86_64/tildefriends.so
|
||||
@cp out/android$(BUILD_TYPE)-x86/tildefriends out/apk-x86-$(BUILD_TYPE)/lib/x86/tildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-x86-$(BUILD_TYPE)/lib/x86_64/tildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-x86-$(BUILD_TYPE)/lib/x86/tildefriends.so
|
||||
@cp out/apk/res.apk $@
|
||||
@cp out/apk/classes.dex out/apk-x86-$(BUILD_TYPE)/
|
||||
@cd out/apk-x86-$(BUILD_TYPE) && zip -u ../../$@ -q -9 -r . && cd ../../
|
||||
@zip -u $@ -q -9 -x '*.map' --exclude=apps/gg* --exclude=apps/welcome* -r $(PACKAGE_DIRS) $(RAW_FILES)
|
||||
@zip -u $@ -q -9 $(RAW_FILES)
|
||||
|
||||
out/%.apk: out/apk/%.unsigned.apk
|
||||
@echo [apksigner] $(notdir $@)
|
||||
@echo "[apksigner] $(notdir $@)"
|
||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --out $@ $<
|
||||
|
||||
release-apk: out/TildeFriends-arm-release.apk out/TildeFriends-x86-release.apk
|
||||
@ -767,7 +784,7 @@ ifeq ($(HAVE_LINUX_IOS),1)
|
||||
endif
|
||||
.SECONDARY:
|
||||
out/tildefriends-%.ipa: out/tildefriends-ios%.app/tildefriends
|
||||
@echo [ipa] $@
|
||||
@echo "[ipa] $@"
|
||||
@rm -rf $@.tmp $@
|
||||
@mkdir -p $@.tmp/Payload/tildefriends.app/
|
||||
@cp -R $(dir $<)/* $@.tmp/Payload/tildefriends.app/
|
||||
@ -795,16 +812,48 @@ apklog:
|
||||
@adb logcat *:S tildefriends
|
||||
.PHONY: apklog
|
||||
|
||||
fetchdeps:
|
||||
@echo "[fetch] libuv"
|
||||
@test -f out/deps/libuv.tar.gz || (mkdir -p out/deps/ && curl -q https://dist.libuv.org/dist/v1.48.0/libuv-v1.48.0.tar.gz -o out/deps/libuv.tar.gz)
|
||||
@test -d deps/libuv/ || (mkdir -p deps/libuv/ && tar -C deps/libuv/ -m --strip=1 -xf out/deps/libuv.tar.gz)
|
||||
@echo "[fetch] sqlite"
|
||||
@test -f out/deps/sqlite.zip || (mkdir -p out/deps/ && curl -q https://www.sqlite.org/2024/sqlite-amalgamation-3450100.zip -o out/deps/sqlite.zip)
|
||||
@test -d deps/sqlite/ || (mkdir -p deps/sqlite/ && unzip -qDj -d deps/sqlite/ out/deps/sqlite.zip)
|
||||
@echo "[fetch] prettier"
|
||||
@test -f deps/prettier/standalone.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/standalone.mjs
|
||||
@test -f deps/prettier/html.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/html.mjs
|
||||
@test -f deps/prettier/babel.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/babel.mjs
|
||||
@test -f deps/prettier/estree.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/estree.mjs
|
||||
.PHONY: fetchdeps
|
||||
|
||||
ANDROID_DEPS := deps/openssl/android/arm64-v8a/usr/local/lib/libssl.a
|
||||
$(ANDROID_DEPS):
|
||||
+@tools/ssl-android
|
||||
$(filter $(BUILD_DIR)/android%,$(APP_OBJS)): | $(ANDROID_DEPS)
|
||||
|
||||
ifeq ($(HAVE_WIN),1)
|
||||
WINDOWS_DEPS := deps/openssl/mingw64/usr/local/lib/libssl.a
|
||||
$(WINDOWS_DEPS):
|
||||
+@tools/ssl-mingw64
|
||||
$(filter $(BUILD_DIR)/win%,$(APP_OBJS)): | $(WINDOWS_DEPS)
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
IOS_DEPS := deps/openssl/ios/usr/local/lib/libssl.a
|
||||
$(IOS_DEPS):
|
||||
+@tools/ssl-ios
|
||||
$(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(IOS_DEPS)
|
||||
endif
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
.PHONY: clean
|
||||
|
||||
dist: release-apk iosrelease-ipa
|
||||
@echo "[export] $$(svn info --show-item url)"
|
||||
@rm -rf tildefriends-$(VERSION_NUMBER)
|
||||
@svn export -q . tildefriends-$(VERSION_NUMBER)
|
||||
@echo "tildefriends-$(VERSION_NUMBER): $(VERSION_NAME)" > tildefriends-$(VERSION_NUMBER)/VERSION
|
||||
@echo "[tar] tildefriends-$(VERSION_NUMBER).tar.xz"
|
||||
@echo [archive] dist/tildefriends-$(VERSION_NUMBER).tar.xz
|
||||
@rm -rf out/tildefriends-$(VERSION_NUMBER)
|
||||
@mkdir -p dist/ out/tildefriends-$(VERSION_NUMBER)
|
||||
@git archive main | tar -x -C out/tildefriends-$(VERSION_NUMBER)
|
||||
@tar \
|
||||
--exclude=apps/gg* \
|
||||
--exclude=apps/welcome* \
|
||||
@ -821,14 +870,14 @@ dist: release-apk iosrelease-ipa
|
||||
--exclude=deps/sqlite/shell.c \
|
||||
--exclude=deps/zlib/contrib/vstudio \
|
||||
--exclude=deps/zlib/doc \
|
||||
-caf tildefriends-$(VERSION_NUMBER).tar.xz tildefriends-$(VERSION_NUMBER)
|
||||
@rm -rf tildefriends-$(VERSION_NUMBER)
|
||||
-caf dist/tildefriends-$(VERSION_NUMBER).tar.xz out/tildefriends-$(VERSION_NUMBER)
|
||||
#@rm -rf out/tildefriends-$(VERSION_NUMBER)
|
||||
@echo "[cp] TildeFriends-x86-$(VERSION_NUMBER).apk"
|
||||
@cp out/TildeFriends-x86-release.apk TildeFriends-x86-$(VERSION_NUMBER).apk
|
||||
@cp out/TildeFriends-x86-release.apk dist/TildeFriends-x86-$(VERSION_NUMBER).apk
|
||||
@echo "[cp] TildeFriends-arm-$(VERSION_NUMBER).apk"
|
||||
@cp out/TildeFriends-arm-release.apk TildeFriends-arm-$(VERSION_NUMBER).apk
|
||||
@cp out/TildeFriends-arm-release.apk dist/TildeFriends-arm-$(VERSION_NUMBER).apk
|
||||
@echo "[cp] TildeFriends-$(VERSION_NUMBER).ipa"
|
||||
@cp out/tildefriends-release.ipa TildeFriends-$(VERSION_NUMBER).ipa
|
||||
@cp out/tildefriends-release.ipa dist/TildeFriends-$(VERSION_NUMBER).ipa
|
||||
.PHONY: dist
|
||||
|
||||
dist-test: dist
|
||||
@ -837,3 +886,11 @@ dist-test: dist
|
||||
@docker build tildefriends-$(VERSION_NUMBER)/
|
||||
@rm -rf tildefriends-$(VERSION_NUMBER)
|
||||
.PHONY: dist-test
|
||||
|
||||
format:
|
||||
@clang-format -i $(wildcard src/*.c src/*.h src/*.m)
|
||||
.PHONY: format
|
||||
|
||||
docs:
|
||||
@doxygen
|
||||
.PHONY: docs
|
||||
|
14
README.md
14
README.md
@ -1,6 +1,8 @@
|
||||
# Tilde Friends
|
||||
Tilde Friends is a tool for making and sharing.
|
||||
|
||||
A public instance lives at https://www.tildefriends.net/.
|
||||
|
||||
It is both a peer-to-peer social network client, participating in Secure
|
||||
Scuttlebutt, as well as a platform for writing and running web applications.
|
||||
|
||||
@ -11,14 +13,18 @@ Scuttlebutt, as well as a platform for writing and running web applications.
|
||||
browser.
|
||||
|
||||
## Building
|
||||
Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. Builds for
|
||||
all of those host platforms plus mingw64, iOS, and android.
|
||||
|
||||
1. Requires openssl (`libssl-dev`, in debian-speak). All other dependencies
|
||||
are kept up to date in the tree.
|
||||
2. To build, run `make debug` or `make release`. An executable will be
|
||||
generated in a subdirectory of `out/`.
|
||||
3. It's possible to build for Android, iOS, and Windows on Linux, if you have
|
||||
the right dependencies in the right places. `make windebug winrelease
|
||||
iosdebug-ipa iosrelease-ipa apk`.
|
||||
iosdebug-ipa iosrelease-ipa release-apk`.
|
||||
4. To build in docker, `docker build .`.
|
||||
5. `make format` will normalize formatting to the coding standard.
|
||||
|
||||
## Running
|
||||
By default, running the built `tildefriends` executable will start a web server
|
||||
@ -26,11 +32,11 @@ at <http://localhost:12345/>. `tildefriends -h` lists further options.
|
||||
|
||||
The first user to create an account and log in will be granted administrative
|
||||
privileges. Further administration can be done at
|
||||
<http://localhost:12345/~core/admin/`>.
|
||||
<http://localhost:12345/~core/admin/>.
|
||||
|
||||
## Documentation
|
||||
There are the very beginnings of developer documentation in `apps/docs/`
|
||||
that can be read in-place or at <http://localhost:12345/~core/docs/>.
|
||||
Docs are a work in progress:
|
||||
<https://www.tildefriends.net/~cory/wiki/#test-wiki/tf-app-quick-reference>.
|
||||
|
||||
## License
|
||||
All code unless otherwise noted in is provided under the
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "💻"
|
||||
"emoji": "💻",
|
||||
"previous": "&RdVEsVscZm3aWzcMrEZS8mskO5tUmvaEUihex2MMfZQ=.sha256"
|
||||
}
|
181
apps/apps/app.js
181
apps/apps/app.js
@ -1,25 +1,83 @@
|
||||
/**
|
||||
* Fetches information about the applications
|
||||
* @param apps Record<appName, blobId>
|
||||
* @returns an object including the apps' name, emoji, and blobs ids
|
||||
*/
|
||||
async function fetch_info(apps) {
|
||||
let result = {};
|
||||
|
||||
// For each app
|
||||
for (let [key, value] of Object.entries(apps)) {
|
||||
// Get it's blob and parse it
|
||||
let blob = await ssb.blobGet(value);
|
||||
blob = blob ? utf8Decode(blob) : '{}';
|
||||
|
||||
// Add it to the result object
|
||||
result[key] = JSON.parse(blob);
|
||||
}
|
||||
|
||||
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'));
|
||||
var doc = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
const apps = await fetch_info(await core.apps());
|
||||
const core_apps = await fetch_info(await core.apps('core'));
|
||||
const shared_apps = await fetch_shared_apps();
|
||||
|
||||
const stylesheet = `
|
||||
body {
|
||||
color: whitesmoke;
|
||||
font-family: sans-serif;
|
||||
margin: 16px;
|
||||
}
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, 64px);
|
||||
gap: 1em;
|
||||
justify-content: space-around;
|
||||
background-color: #ffffff10;
|
||||
border: 2px solid #073642;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.app {
|
||||
height: 96px;
|
||||
width: 64px;
|
||||
@ -34,44 +92,87 @@ async function main() {
|
||||
max-width: 64px;
|
||||
text-overflow: ellipsis ellipsis;
|
||||
overflow: hidden;
|
||||
color: whitesmoke;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="background: #888">
|
||||
<h1 id="apps_title">Apps</h1>
|
||||
<div id="apps" class="container"></div>
|
||||
<h1>Core Apps</h1>
|
||||
<div id="core_apps" class="container"></div>
|
||||
</body>
|
||||
<script>
|
||||
function populate_apps(id, name, apps) {
|
||||
var list = document.getElementById(id);
|
||||
for (let app of Object.keys(apps).sort()) {
|
||||
let div = list.appendChild(document.createElement('div'));
|
||||
div.classList.add('app');
|
||||
`;
|
||||
|
||||
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.target = '_top';
|
||||
div.appendChild(icon_a);
|
||||
const body = `
|
||||
<h1 style="text-shadow: #808080 0 0 10px;">Welcome to Tilde Friends.</h1>
|
||||
|
||||
let a = document.createElement('a');
|
||||
a.appendChild(document.createTextNode(app));
|
||||
a.href = '/~' + name + '/' + app + '/';
|
||||
a.target = '_top';
|
||||
div.appendChild(a);
|
||||
<h2>your apps</h2>
|
||||
<div id="apps" class="container"></div>
|
||||
|
||||
<h2>shared apps</h2>
|
||||
<div id="shared_apps" class="container"></div>
|
||||
|
||||
<h2>core apps</h2>
|
||||
<div id="core_apps" class="container"></div>
|
||||
`;
|
||||
|
||||
const script = `
|
||||
/*
|
||||
* Creates a list of apps
|
||||
* @param id the id of the element to populate
|
||||
* @param name (a username, 'core' or undefined)
|
||||
* @param apps Object, a list of apps
|
||||
*/
|
||||
function populate_apps(id, name, apps) {
|
||||
// Our target
|
||||
var list = document.getElementById(id);
|
||||
|
||||
// For each app in the provided list
|
||||
for (let app of Object.keys(apps).sort()) {
|
||||
|
||||
// Create the item
|
||||
let div = list.appendChild(document.createElement('div'));
|
||||
div.classList.add('app');
|
||||
|
||||
// The app's icon
|
||||
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 = href;
|
||||
icon_a.target = '_top';
|
||||
div.appendChild(icon_a);
|
||||
|
||||
// The app's name
|
||||
let a = document.createElement('a');
|
||||
a.appendChild(document.createTextNode(app));
|
||||
a.href = href;
|
||||
a.target = '_top';
|
||||
div.appendChild(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
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)});
|
||||
</script>
|
||||
</html>`;
|
||||
app.setDocument(doc);
|
||||
|
||||
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)});
|
||||
`;
|
||||
|
||||
// Build the document
|
||||
const document = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
${stylesheet}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
${body}
|
||||
</body>
|
||||
|
||||
<script>
|
||||
${script}
|
||||
</script>
|
||||
</html>`;
|
||||
|
||||
// Send it to the browser
|
||||
app.setDocument(document);
|
||||
}
|
||||
|
||||
main();
|
||||
|
@ -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();
|
5
apps/blog.json
Normal file
5
apps/blog.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🪵",
|
||||
"previous": "&TIrBnpN3iz3O9L9MCCteAcVJZjA83EKdcfu4SCM76VE=.sha256"
|
||||
}
|
8
apps/blog/app.js
Normal file
8
apps/blog/app.js
Normal file
@ -0,0 +1,8 @@
|
||||
import * as blog from './blog.js';
|
||||
|
||||
async function main() {
|
||||
let blogs = await blog.get_posts();
|
||||
await app.setDocument(blog.render_html(blogs));
|
||||
}
|
||||
|
||||
main();
|
189
apps/blog/blog.js
Normal file
189
apps/blog/blog.js
Normal file
@ -0,0 +1,189 @@
|
||||
import * as commonmark from './commonmark.min.js';
|
||||
|
||||
function escape(text) {
|
||||
return (text ?? '').replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
||||
}
|
||||
|
||||
function escapeAttribute(text) {
|
||||
return (text ?? '').replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''');
|
||||
}
|
||||
|
||||
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 || '');
|
||||
let walker = parsed.walker();
|
||||
let event, node;
|
||||
while ((event = walker.next())) {
|
||||
node = event.node;
|
||||
if (event.entering) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
return writer.render(parsed);
|
||||
}
|
||||
|
||||
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>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
function render_blog_post(blog_post) {
|
||||
return `
|
||||
<div>
|
||||
<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>
|
||||
`;
|
||||
}
|
||||
|
||||
export function render_html(blogs) {
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<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="_top">
|
||||
</head>
|
||||
<body>
|
||||
<div style="display: flex; flex-direction: row; align-items: center; gap: 1em">
|
||||
<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')}
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
function render_blog_post_atom(blog_post) {
|
||||
return `<entry>
|
||||
<title>${escape(blog_post.title)}</title>
|
||||
<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>
|
||||
<author>
|
||||
<name>${escape(blog_post.name)}</name>
|
||||
<feed>${escape(blog_post.author)}</feed>
|
||||
</author>
|
||||
</entry>`;
|
||||
}
|
||||
|
||||
export function render_atom(blogs) {
|
||||
return `<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>🪵Tilde Blog</title>
|
||||
<subtitle>A subtitle.</subtitle>
|
||||
<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>`;
|
||||
}
|
||||
|
||||
export async function get_posts() {
|
||||
let blogs = [];
|
||||
let ids = await ssb.getIdentities();
|
||||
await ssb.sqlAsync(`
|
||||
WITH
|
||||
blogs AS (
|
||||
SELECT
|
||||
messages.author,
|
||||
messages.id,
|
||||
json_extract(messages.content, '$.title') AS title,
|
||||
json_extract(messages.content, '$.summary') AS summary,
|
||||
json_extract(messages.content, '$.blog') AS blog,
|
||||
messages.timestamp
|
||||
FROM messages_fts('blog')
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
WHERE json_extract(messages.content, '$.type') = 'blog'),
|
||||
public AS (
|
||||
SELECT author FROM (
|
||||
SELECT
|
||||
messages.author,
|
||||
RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank,
|
||||
json_extract(messages.content, '$.publicWebHosting') AS is_public
|
||||
FROM messages_fts('about')
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
WHERE json_extract(messages.content, '$.type') = 'about' AND is_public IS NOT NULL)
|
||||
WHERE author_rank = 1 AND is_public),
|
||||
names AS (
|
||||
SELECT author, name FROM (
|
||||
SELECT
|
||||
messages.author,
|
||||
RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank,
|
||||
json_extract(messages.content, '$.name') AS name
|
||||
FROM messages_fts('about')
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
WHERE json_extract(messages.content, '$.type') = 'about' AND
|
||||
json_extract(messages.content, '$.about') = messages.author AND
|
||||
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
|
||||
`, [JSON.stringify(ids)], function(row) {
|
||||
blogs.push(row);
|
||||
});
|
||||
return blogs;
|
||||
}
|
1
apps/blog/commonmark.min.js
vendored
Normal file
1
apps/blog/commonmark.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
31
apps/blog/handler.js
Normal file
31
apps/blog/handler.js
Normal file
@ -0,0 +1,31 @@
|
||||
import * as blog from './blog.js';
|
||||
|
||||
async function main() {
|
||||
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'});
|
||||
}
|
||||
} 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'});
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(function(error) {
|
||||
respond({data: `<!DOCTYPE html>
|
||||
<pre style="color: #f00">${error.message}\n${error.stack}</pre>`, content_type: 'text/html'});
|
||||
});
|
120
apps/blog/lit-all.min.js
vendored
Normal file
120
apps/blog/lit-all.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
apps/blog/lit-all.min.js.map
Normal file
1
apps/blog/lit-all.min.js.map
Normal file
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.
|
22
apps/gg/lit-all.min.js
vendored
22
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();
|
22
apps/issues/lit-all.min.js
vendored
22
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
22
apps/journal/lit-all.min.js
vendored
22
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
22
apps/sneaker/lit-all.min.js
vendored
22
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": "&vIYnoUkbz97WRvyunV+ETe+Y6tJk7tTEVvgYuwkoDiM=.sha256"
|
||||
"previous": "&DUxMMCJcuhm6S9jg/eKgEyWodkITu6Tg9g5I5wgLWFU=.sha256"
|
||||
}
|
@ -39,7 +39,7 @@ function splitMatches(text, regexp) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const regex = new RegExp("(?<!\w)#[\\w-]+");
|
||||
const regex = new RegExp("(?<!\\w)#[\\w-]+");
|
||||
|
||||
function split(textNodes) {
|
||||
const text = textNodes.map(n => n.literal).join("");
|
||||
|
@ -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>
|
||||
|
22
apps/ssb/lit-all.min.js
vendored
22
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;
|
||||
@ -365,9 +365,12 @@ class TfMessageElement extends LitElement {
|
||||
case 'message':
|
||||
body = unsafeHTML(tfutils.markdown(content.text));
|
||||
break;
|
||||
case 'decrypted':
|
||||
body = html`<pre style="white-space: pre-wrap; overflow-wrap: anywhere">${JSON.stringify(content, null, 2)}</pre>`;
|
||||
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 +404,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 +414,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 +439,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 +449,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 +487,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 +513,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 +523,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') {
|
||||
|
@ -106,6 +106,7 @@ class TfProfileElement extends LitElement {
|
||||
name: original.name,
|
||||
description: original.description,
|
||||
image: original.image,
|
||||
publicWebHosting: original.publicWebHosting,
|
||||
};
|
||||
console.log(this.editing);
|
||||
}
|
||||
@ -183,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" value=${this.editing.public_web_hosting} @input=${event => this.editing = Object.assign({}, this.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 || '');
|
||||
parsed = linkify.transform(parsed);
|
||||
let parsed = reader.parse(md || '');
|
||||
parsed = hashtagify.transform(parsed);
|
||||
var walker = parsed.walker();
|
||||
var event, node;
|
||||
parsed = linkify.transform(parsed);
|
||||
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": "&r+JXDhWclHwMbeZHYjueOnSPaYCAXUlnl1Gyjgw6TxM=.sha256"
|
||||
"previous": "&/wl8HE2jZShRXTYEVYRrK3pjHwi41Wbxl9HoSJaQP6Y=.sha256"
|
||||
}
|
@ -10,10 +10,12 @@ function markdown(md) {
|
||||
while ((event = walker.next())) {
|
||||
let node = event.node;
|
||||
if (event.entering) {
|
||||
if (node.type === 'link') {
|
||||
if (node.destination?.startsWith('&')) {
|
||||
node.destination = '/' + node.destination + '/view?filename=' + node.firstChild?.literal;
|
||||
} else if (node.type === 'link') {
|
||||
if (node.destination.indexOf(':') == -1 &&
|
||||
node.destination.indexOf('/') == -1) {
|
||||
node.destination = `#${this.wiki?.name}/${node.destination}`;
|
||||
node.destination = `${node.destination}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -57,7 +59,7 @@ async function main() {
|
||||
<h1>${wiki_name}: ${wiki_doc_name}</h1>
|
||||
<div>${markdown(md)}</div>
|
||||
`,
|
||||
content_type: 'text/html',
|
||||
content_type: 'text/html; charset=utf-8',
|
||||
status_code: 200,
|
||||
});
|
||||
} else {
|
||||
|
22
apps/wiki/lit-all.min.js
vendored
22
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
@ -240,6 +240,14 @@ class TfCollectionsAppElement extends LitElement {
|
||||
render() {
|
||||
let self = this;
|
||||
return html`
|
||||
<style>
|
||||
.toc:hover {
|
||||
background-color: #0cc;
|
||||
}
|
||||
.toc.selected {
|
||||
background-color: #088;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<tf-id-picker .ids=${this.ids} selected=${this.whoami} @change=${this.on_whoami_changed} ?hidden=${!this.ids?.length}></tf-id-picker>
|
||||
</div>
|
||||
@ -278,12 +286,25 @@ class TfCollectionsAppElement extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${this.wiki_doc && this.wiki_doc.parent === this.wiki?.id ? html`
|
||||
<tf-wiki-doc
|
||||
whoami=${this.whoami}
|
||||
.wiki=${this.wiki}
|
||||
.value=${this.wiki_doc}></tf-wiki-doc>
|
||||
` : undefined}
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<div style="flex: 0 0">
|
||||
${Object.values(this.wikis || {}).sort((x, y) => x.name.localeCompare(y.name)).map(wiki => html`
|
||||
<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; 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>
|
||||
` : undefined}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ class TfWikiDocElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
whoami: {type: String},
|
||||
wiki: {type: String},
|
||||
wiki: {type: Object},
|
||||
value: {type: Object},
|
||||
blob: {type: String},
|
||||
blob_original: {type: String},
|
||||
@ -20,15 +20,17 @@ class TfWikiDocElement extends LitElement {
|
||||
}
|
||||
|
||||
markdown(md) {
|
||||
var reader = new commonmark.Parser({safe: true});
|
||||
var writer = new commonmark.HtmlRenderer();
|
||||
var parsed = reader.parse(md || '');
|
||||
let reader = new commonmark.Parser({safe: true});
|
||||
let writer = new commonmark.HtmlRenderer();
|
||||
let parsed = reader.parse(md || '');
|
||||
let walker = parsed.walker();
|
||||
let event;
|
||||
while ((event = walker.next())) {
|
||||
let node = event.node;
|
||||
if (event.entering) {
|
||||
if (node.type === 'link') {
|
||||
if (node.destination?.startsWith('&')) {
|
||||
node.destination = '/' + node.destination + '/view?filename=' + node.firstChild?.literal;
|
||||
} else if (node.type === 'link') {
|
||||
if (node.destination.indexOf(':') == -1 &&
|
||||
node.destination.indexOf('/') == -1) {
|
||||
node.destination = `#${this.wiki?.name}/${node.destination}`;
|
||||
@ -39,6 +41,39 @@ class TfWikiDocElement extends LitElement {
|
||||
return writer.render(parsed);
|
||||
}
|
||||
|
||||
title(md) {
|
||||
let lines = (md || '').split('\n');
|
||||
for (let line of lines) {
|
||||
let m = line.match(/#+ (.*)/);
|
||||
if (m) {
|
||||
return m[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
summary(md) {
|
||||
let lines = (md || '').split('\n');
|
||||
let result = [];
|
||||
let have_content = false;
|
||||
for (let line of lines) {
|
||||
if (have_content && !line.trim().length) {
|
||||
return result.join('\n');
|
||||
}
|
||||
if (!line.startsWith('#') && line.trim().length) {
|
||||
have_content = true;
|
||||
}
|
||||
if (!line.startsWith('#')) {
|
||||
result.push(line);
|
||||
}
|
||||
}
|
||||
return result.join('\n');
|
||||
}
|
||||
|
||||
thumbnail(md) {
|
||||
let m = md ? md.match(/\!\[image:[^\]]+\]\((\&.{44}\.sha256)\).*/) : undefined;
|
||||
return m ? m[1] : undefined;
|
||||
}
|
||||
|
||||
async load_blob() {
|
||||
let blob = await tfrpc.rpc.get_blob(this.value?.blob);
|
||||
if (blob.endsWith('.box')) {
|
||||
@ -62,8 +97,8 @@ class TfWikiDocElement extends LitElement {
|
||||
|
||||
async append_message(draft) {
|
||||
let blob = this.blob;
|
||||
if (draft) {
|
||||
blob = await tfrpc.rpc.encrypt(this.whoami, this.value.editors, blob);
|
||||
if (draft || this.value?.private) {
|
||||
blob = await tfrpc.rpc.encrypt(this.whoami, this.wiki.editors, blob);
|
||||
}
|
||||
let id = await tfrpc.rpc.store_blob(blob);
|
||||
let message = {
|
||||
@ -71,13 +106,13 @@ class TfWikiDocElement extends LitElement {
|
||||
key: this.value.id,
|
||||
parent: this.value.parent,
|
||||
blob: id,
|
||||
mentions: this.blob.match(/(&.{44}.sha256)/g)?.map(x => ({link: x})),
|
||||
private: this.value?.private,
|
||||
};
|
||||
if (draft) {
|
||||
message.recps = this.value.editors;
|
||||
print(message);
|
||||
message = await tfrpc.rpc.encrypt(this.whoami, this.value.editors, JSON.stringify(message));
|
||||
}
|
||||
print(message);
|
||||
await tfrpc.rpc.appendMessage(this.whoami, message);
|
||||
this.is_editing = false;
|
||||
}
|
||||
@ -90,6 +125,103 @@ class TfWikiDocElement extends LitElement {
|
||||
return this.append_message(false);
|
||||
}
|
||||
|
||||
async on_blog_publish() {
|
||||
let blob = this.blob;
|
||||
let id = await tfrpc.rpc.store_blob(blob);
|
||||
let message = {
|
||||
type: 'blog',
|
||||
key: this.value.id,
|
||||
parent: this.value.parent,
|
||||
title: this.title(blob),
|
||||
summary: this.summary(blob),
|
||||
thumbnail: this.thumbnail(blob),
|
||||
blog: id,
|
||||
mentions: this.blob.match(/(&.{44}.sha256)/g)?.map(x => ({link: x})),
|
||||
};
|
||||
await tfrpc.rpc.appendMessage(this.whoami, message);
|
||||
this.is_editing = false;
|
||||
}
|
||||
|
||||
convert_to_format(buffer, type, mime_type) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let img = new Image();
|
||||
img.onload = function() {
|
||||
let canvas = document.createElement('canvas');
|
||||
let width_scale = Math.min(img.width, 1024) / img.width;
|
||||
let height_scale = Math.min(img.height, 1024) / img.height;
|
||||
let scale = Math.min(width_scale, height_scale);
|
||||
canvas.width = img.width * scale;
|
||||
canvas.height = img.height * scale;
|
||||
let context = canvas.getContext('2d');
|
||||
context.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
let data_url = canvas.toDataURL(mime_type);
|
||||
let result = atob(data_url.split(',')[1]).split('').map(x => x.charCodeAt(0));
|
||||
resolve(result);
|
||||
};
|
||||
img.onerror = function(event) {
|
||||
reject(new Error('Failed to load image.'));
|
||||
};
|
||||
let raw = Array.from(new Uint8Array(buffer)).map(b => String.fromCharCode(b)).join('');
|
||||
let original = `data:${type};base64,${btoa(raw)}`;
|
||||
img.src = original;
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
for (let format of ['image/png', 'image/jpeg', 'image/webp']) {
|
||||
let test_buffer = await self.convert_to_format(buffer, file.type, format);
|
||||
if (!best_buffer || test_buffer.length < best_buffer.length) {
|
||||
best_buffer = test_buffer;
|
||||
best_type = format;
|
||||
}
|
||||
}
|
||||
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)})`;
|
||||
}
|
||||
document.execCommand('insertText', false, insert);
|
||||
self.on_edit({srcElement: editor});
|
||||
} catch(e) {
|
||||
alert(e?.message);
|
||||
}
|
||||
}
|
||||
|
||||
paste(event) {
|
||||
let self = this;
|
||||
for (let item of event.clipboardData.items) {
|
||||
let file = item.getAsFile();
|
||||
if (file) {
|
||||
self.add_file(event.srcElement, file);
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let value = JSON.stringify(this.value);
|
||||
if (this.blob_for_value != value) {
|
||||
@ -99,19 +231,38 @@ class TfWikiDocElement extends LitElement {
|
||||
this.load_blob();
|
||||
}
|
||||
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>
|
||||
<button ?disabled=${this.blob == this.blob_original && !this.value?.draft} @click=${this.on_publish}>Publish</button>
|
||||
<button ?disabled=${!this.is_editing} @click=${this.on_discard}>Discard</button>
|
||||
<button ?disabled=${!this.is_editing} @click=${() => self.value = Object.assign({}, self.value, {private: !self.value.private})}>${this.value?.private ? 'Make Public' : 'Make Private'}</button>
|
||||
<button ?disabled=${!this.is_editing} @click=${this.on_blog_publish}>Publish Blog</button>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<div ?hidden=${!this.value?.private} style="color: #800">🔒 document is private</div>
|
||||
<div style="display: flex; flex-direction: row; ${this.value?.private ? 'border-top: 4px solid #800' : ''}">
|
||||
<textarea
|
||||
?hidden=${!this.is_editing}
|
||||
style="flex: 1 1; min-height: 10em"
|
||||
@input=${this.on_edit} .value=${this.blob ?? ''}></textarea>
|
||||
<div style="flex: 1 1">${unsafeHTML(this.markdown(this.blob))}</div>
|
||||
style="flex: 1 1; min-height: 10em; ${this.value?.private ? 'border: 4px solid #800' : ''}"
|
||||
@input=${this.on_edit}
|
||||
@paste=${this.paste}
|
||||
.value=${this.blob ?? ''}></textarea>
|
||||
<div style="flex: 1 1">
|
||||
<div ?hidden=${!this.is_editing} style="border: 1px solid #fff; border-radius: 1em; padding: 0.5em">
|
||||
<img ?hidden=${!thumbnail_ref} style="max-width: 128px; max-height: 128px; float: right" src="/${thumbnail_ref}/view">
|
||||
<h1 ?hidden=${!this.title(this.blob)}>${unsafeHTML(this.markdown(this.title(this.blob)))}</h1>
|
||||
${unsafeHTML(this.markdown(this.summary(this.blob)))}
|
||||
</div>
|
||||
${unsafeHTML(this.markdown(this.blob))}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
48
core/app.js
48
core/app.js
@ -6,20 +6,37 @@ let g_calls = {};
|
||||
|
||||
let gSessionIndex = 0;
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
*/
|
||||
function makeSessionId() {
|
||||
return (gSessionIndex++).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
*/
|
||||
function App() {
|
||||
this._on_output = null;
|
||||
this._send_queue = [];
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} callback
|
||||
*/
|
||||
App.prototype.readOutput = function(callback) {
|
||||
this._on_output = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} api
|
||||
* @returns
|
||||
*/
|
||||
App.prototype.makeFunction = function(api) {
|
||||
let self = this;
|
||||
let result = function() {
|
||||
@ -43,6 +60,10 @@ App.prototype.makeFunction = function(api) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} message
|
||||
*/
|
||||
App.prototype.send = function(message) {
|
||||
if (this._send_queue) {
|
||||
if (this._on_output) {
|
||||
@ -55,23 +76,26 @@ App.prototype.send = function(message) {
|
||||
if (message && this._on_output) {
|
||||
this._on_output(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} request
|
||||
* @param {*} response
|
||||
* @param {*} client
|
||||
*/
|
||||
function socket(request, response, client) {
|
||||
let process;
|
||||
let options = {};
|
||||
let credentials = auth.query(request.headers);
|
||||
let refresh = auth.make_refresh(credentials);
|
||||
let refresh = auth.makeRefresh(credentials);
|
||||
|
||||
response.onClose = async function() {
|
||||
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 +181,7 @@ function socket(request, response, client) {
|
||||
process.lastPing = now;
|
||||
}
|
||||
|
||||
if (again) {
|
||||
if (again && process.timeout) {
|
||||
setTimeout(ping, process.timeout);
|
||||
}
|
||||
}
|
||||
@ -202,11 +226,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 };
|
||||
|
@ -11,7 +11,7 @@
|
||||
<tf-auth id="auth"></tf-auth>
|
||||
<script>window.litDisableBundleWarning = true;</script>
|
||||
<script type="module">
|
||||
import {LitElement, html} from '/static/lit/lit-all.min.js';
|
||||
import {LitElement, html} from '/lit/lit-all.min.js';
|
||||
let g_data = $AUTH_DATA;
|
||||
let app = document.getElementById('auth');
|
||||
Object.assign(app, g_data);
|
||||
|
117
core/auth.js
117
core/auth.js
@ -5,9 +5,15 @@ let gDatabase = new Database("auth");
|
||||
|
||||
const kRefreshInterval = 1 * 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Makes a Base64 value URL safe
|
||||
* @param {string} value
|
||||
* @returns TODOC
|
||||
*/
|
||||
function b64url(value) {
|
||||
value = value.replaceAll('+', '-').replaceAll('/', '_');
|
||||
let equals = value.indexOf('=');
|
||||
|
||||
if (equals !== -1) {
|
||||
return value.substring(0, equals);
|
||||
} else {
|
||||
@ -15,9 +21,15 @@ function b64url(value) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {string} value
|
||||
* @returns
|
||||
*/
|
||||
function unb64url(value) {
|
||||
value = value.replaceAll('-', '+').replaceAll('_', '/');
|
||||
let remainder = value.length % 4;
|
||||
|
||||
if (remainder == 3) {
|
||||
return value + '=';
|
||||
} else if (remainder == 2) {
|
||||
@ -27,31 +39,46 @@ function unb64url(value) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON Web Token
|
||||
* @param {object} payload Object: {"name": "username"}
|
||||
* @returns the JWT
|
||||
*/
|
||||
function makeJwt(payload) {
|
||||
let ids = ssb.getIdentities(':auth');
|
||||
const ids = ssb.getIdentities(':auth');
|
||||
let id;
|
||||
|
||||
if (ids?.length) {
|
||||
id = ids[0];
|
||||
} else {
|
||||
id = ssb.createIdentity(':auth');
|
||||
}
|
||||
|
||||
let final_payload = b64url(base64Encode(JSON.stringify(Object.assign({}, payload, {exp: (new Date().valueOf()) + kRefreshInterval}))));
|
||||
let jwt = [b64url(base64Encode(JSON.stringify({alg: 'HS256', typ: 'JWT'}))), final_payload, b64url(ssb.hmacsha256sign(final_payload, ':auth', id))].join('.');
|
||||
const final_payload = b64url(base64Encode(JSON.stringify(Object.assign({}, payload, {exp: (new Date().valueOf()) + kRefreshInterval}))));
|
||||
const jwt = [b64url(base64Encode(JSON.stringify({alg: 'HS256', typ: 'JWT'}))), final_payload, b64url(ssb.hmacsha256sign(final_payload, ':auth', id))].join('.');
|
||||
return jwt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a JWT ?
|
||||
* @param {*} session TODOC
|
||||
* @returns
|
||||
*/
|
||||
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 now = new Date().valueOf()
|
||||
const result = JSON.parse(utf8Decode(base64Decode(unb64url(payload))));
|
||||
const now = new Date().valueOf()
|
||||
|
||||
if (now < result.exp) {
|
||||
print(`JWT valid for another ${(result.exp - now) / 1000} seconds.`);
|
||||
return result;
|
||||
@ -64,26 +91,49 @@ function readSession(session) {
|
||||
} else {
|
||||
print('Invalid JWT header.');
|
||||
}
|
||||
} else {
|
||||
print('No session JWT.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the provided password matches the hash
|
||||
* @param {string} password
|
||||
* @param {string} hash bcrypt hash
|
||||
* @returns true if the password matches the hash
|
||||
*/
|
||||
function verifyPassword(password, hash) {
|
||||
return bCrypt.hashpw(password, hash) == hash;
|
||||
return bCrypt.hashpw(password, hash) === hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes a password
|
||||
* @param {string} password
|
||||
* @returns {string} TODOC
|
||||
*/
|
||||
function hashPassword(password) {
|
||||
let salt = bCrypt.gensalt(12);
|
||||
return bCrypt.hashpw(password, salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is an administrator on the instance
|
||||
* @returns TODOC
|
||||
*/
|
||||
function noAdministrator() {
|
||||
return !core.globalSettings || !core.globalSettings.permissions || !Object.keys(core.globalSettings.permissions).some(function(name) {
|
||||
return core.globalSettings.permissions[name].indexOf("administration") != -1;
|
||||
});
|
||||
return (
|
||||
!core.globalSettings ||
|
||||
!core.globalSettings.permissions ||
|
||||
!Object.keys(core.globalSettings.permissions).some(function (name) {
|
||||
return (
|
||||
core.globalSettings.permissions[name].indexOf('administration') != -1
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a user an administrator
|
||||
* @param {string} name the user's name
|
||||
*/
|
||||
function makeAdministrator(name) {
|
||||
if (!core.globalSettings.permissions) {
|
||||
core.globalSettings.permissions = {};
|
||||
@ -94,9 +144,15 @@ function makeAdministrator(name) {
|
||||
if (core.globalSettings.permissions[name].indexOf("administration") == -1) {
|
||||
core.globalSettings.permissions[name].push("administration");
|
||||
}
|
||||
|
||||
core.setGlobalSettings(core.globalSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} headers most likely an object
|
||||
* @returns
|
||||
*/
|
||||
function getCookies(headers) {
|
||||
let cookies = {};
|
||||
|
||||
@ -113,12 +169,25 @@ function getCookies(headers) {
|
||||
return cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a username
|
||||
* @param {string} name
|
||||
* @returns false | boolean[] ?
|
||||
*/
|
||||
function isNameValid(name) {
|
||||
// TODO(tasiaiso): convert this into a regex
|
||||
let c = name.charAt(0);
|
||||
return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) && name.split().map(x => x >= ('a' && x <= 'z') || x >= ('A' && x <= 'Z') || x >= ('0' && x <= '9'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Request handler ?
|
||||
* @param {*} request TODOC
|
||||
* @param {*} response
|
||||
* @returns
|
||||
*/
|
||||
function handler(request, response) {
|
||||
// TODO(tasiaiso): split this function
|
||||
let session = getCookies(request.headers).session;
|
||||
if (request.uri == "/login") {
|
||||
let formData = form.decodeForm(request.query);
|
||||
@ -228,6 +297,11 @@ function handler(request, response) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a user's permissions based on it's session ?
|
||||
* @param {*} session TODOC
|
||||
* @returns
|
||||
*/
|
||||
function getPermissions(session) {
|
||||
let permissions;
|
||||
let entry = readSession(session);
|
||||
@ -238,6 +312,11 @@ function getPermissions(session) {
|
||||
return permissions || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user's permissions ?
|
||||
* @param {string} userName TODOC
|
||||
* @returns
|
||||
*/
|
||||
function getPermissionsForUser(userName) {
|
||||
let permissions = {};
|
||||
if (core.globalSettings && core.globalSettings.permissions && core.globalSettings.permissions[userName]) {
|
||||
@ -248,6 +327,11 @@ function getPermissionsForUser(userName) {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} headers
|
||||
* @returns
|
||||
*/
|
||||
function query(headers) {
|
||||
let session = getCookies(headers).session;
|
||||
let entry;
|
||||
@ -260,7 +344,12 @@ function query(headers) {
|
||||
}
|
||||
}
|
||||
|
||||
function make_refresh(credentials) {
|
||||
/**
|
||||
* Refreshes a JWT ?
|
||||
* @param {*} credentials TODOC
|
||||
* @returns
|
||||
*/
|
||||
function makeRefresh(credentials) {
|
||||
if (credentials?.session?.name) {
|
||||
return {
|
||||
token: makeJwt({name: credentials.session.name}),
|
||||
@ -269,4 +358,4 @@ function make_refresh(credentials) {
|
||||
}
|
||||
}
|
||||
|
||||
export { handler, query, make_refresh };
|
||||
export {handler, query, makeRefresh};
|
||||
|
662
core/client.js
662
core/client.js
File diff suppressed because it is too large
Load Diff
349
core/core.js
349
core/core.js
@ -2,12 +2,12 @@ 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;
|
||||
|
||||
const k_content_security_policy = 'sandbox allow-downloads allow-top-navigation-by-user-activation';
|
||||
|
||||
const k_mime_types = {
|
||||
'css': 'text/css',
|
||||
'html': 'text/html',
|
||||
@ -30,15 +30,11 @@ 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: '/robots.txt', type: 'text/plain; charset=UTF-8'},
|
||||
];
|
||||
|
||||
const k_global_settings = {
|
||||
@ -47,6 +43,11 @@ const k_global_settings = {
|
||||
default_value: '/~core/apps/',
|
||||
description: 'Default path.',
|
||||
},
|
||||
index_map: {
|
||||
type: 'textarea',
|
||||
default_value: undefined,
|
||||
description: 'Mappings from hostname to redirect path, one per line, as in: "www.tildefriends.net=/~core/index/"',
|
||||
},
|
||||
room: {
|
||||
type: 'boolean',
|
||||
default_value: true,
|
||||
@ -90,6 +91,11 @@ let gGlobalSettings = {
|
||||
|
||||
let kPingInterval = 60 * 1000;
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} out
|
||||
* @param {*} error
|
||||
*/
|
||||
function printError(out, error) {
|
||||
if (error.stackTrace) {
|
||||
out.print(error.fileName + ":" + error.lineNumber + ": " + error.message);
|
||||
@ -102,6 +108,12 @@ function printError(out, error) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} handlers
|
||||
* @param {*} argv
|
||||
* @returns
|
||||
*/
|
||||
function invoke(handlers, argv) {
|
||||
let promises = [];
|
||||
if (handlers) {
|
||||
@ -118,22 +130,30 @@ function invoke(handlers, argv) {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} eventName
|
||||
* @param {*} argv
|
||||
* @returns
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} message
|
||||
* @returns
|
||||
*/
|
||||
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) {
|
||||
@ -144,11 +164,15 @@ function broadcast(message) {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} caller
|
||||
* @param {*} process
|
||||
* @returns
|
||||
*/
|
||||
function getUser(caller, process) {
|
||||
return {
|
||||
name: process.userName,
|
||||
key: process.key,
|
||||
index: process.index,
|
||||
packageOwner: process.packageOwner,
|
||||
packageName: process.packageName,
|
||||
credentials: process.credentials,
|
||||
@ -156,6 +180,12 @@ function getUser(caller, process) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} user
|
||||
* @param {*} process
|
||||
* @returns
|
||||
*/
|
||||
function getApps(user, process) {
|
||||
if (process.credentials &&
|
||||
process.credentials.session &&
|
||||
@ -177,12 +207,26 @@ function getApps(user, process) {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} from
|
||||
* @param {*} to
|
||||
* @param {*} message
|
||||
* @returns
|
||||
*/
|
||||
function postMessageInternal(from, to, message) {
|
||||
if (to.eventHandlers['message']) {
|
||||
return invoke(to.eventHandlers['message'], [getUser(from, from), message]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} blobId
|
||||
* @param {*} session
|
||||
* @param {*} options
|
||||
* @returns
|
||||
*/
|
||||
async function getSessionProcessBlob(blobId, session, options) {
|
||||
let actualOptions = {timeout: kPingInterval};
|
||||
if (options) {
|
||||
@ -193,7 +237,15 @@ async function getSessionProcessBlob(blobId, session, options) {
|
||||
return getProcessBlob(blobId, 'session_' + session, actualOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} blobId
|
||||
* @param {*} key
|
||||
* @param {*} options
|
||||
* @returns
|
||||
*/
|
||||
async function getProcessBlob(blobId, key, options) {
|
||||
// TODO(tasiaiso): break this down ?
|
||||
let process = gProcesses[key];
|
||||
if (!process
|
||||
&& !(options && "create" in options && !options.create)) {
|
||||
@ -203,8 +255,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 = {};
|
||||
@ -315,6 +365,10 @@ async function getProcessBlob(blobId, key, options) {
|
||||
throw Error(`Permission denied: ${permission}.`);
|
||||
}
|
||||
},
|
||||
app: {
|
||||
owner: options?.packageOwner,
|
||||
name: options?.packageName,
|
||||
},
|
||||
url: options?.url,
|
||||
}
|
||||
};
|
||||
@ -391,6 +445,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);
|
||||
@ -403,6 +475,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 &&
|
||||
@ -509,6 +590,11 @@ async function getProcessBlob(blobId, key, options) {
|
||||
return process;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} settings
|
||||
* @returns
|
||||
*/
|
||||
function setGlobalSettings(settings) {
|
||||
gGlobalSettings = settings;
|
||||
try {
|
||||
@ -518,6 +604,12 @@ function setGlobalSettings(settings) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} data
|
||||
* @param {*} bytes
|
||||
* @returns
|
||||
*/
|
||||
function startsWithBytes(data, bytes) {
|
||||
if (data.byteLength >= bytes.length) {
|
||||
let dataBytes = new Uint8Array(data.slice(0, bytes.length));
|
||||
@ -530,83 +622,21 @@ function startsWithBytes(data, bytes) {
|
||||
}
|
||||
}
|
||||
|
||||
async function staticFileHandler(request, response, blobId, uri) {
|
||||
for (let i in k_static_files) {
|
||||
if (uri === k_static_files[i].uri) {
|
||||
let path = k_static_files[i].path || uri.substring(1);
|
||||
let type = k_static_files[i].type || guessTypeFromName(path);
|
||||
|
||||
let stat = await File.stat('core/' + path);
|
||||
let id = `${stat.mtime}_${stat.size}`;
|
||||
|
||||
if (request.headers['if-none-match'] === '"' + id + '"') {
|
||||
response.writeHead(304, {'Content-Length': '0'});
|
||||
response.end();
|
||||
} else {
|
||||
let data = await File.readFile('core/' + path);
|
||||
response.writeHead(200, Object.assign(
|
||||
{
|
||||
'Content-Type': type,
|
||||
'Content-Length': data.byteLength,
|
||||
'etag': '"' + id + '"',
|
||||
},
|
||||
k_static_files[i].headers || {}));
|
||||
response.end(data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
response.writeHead(404, {"Content-Type": "text/plain; charset=utf-8", "Content-Length": "File not found".length});
|
||||
response.end("File not found");
|
||||
}
|
||||
|
||||
async function staticDirectoryHandler(request, response, directory, uri) {
|
||||
let filename = uri || 'index.html';
|
||||
if (filename.indexOf('..') != -1) {
|
||||
response.writeHead(404, {"Content-Type": "text/plain; charset=utf-8", "Content-Length": "File not found".length});
|
||||
response.end("File not found");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let stat = await File.stat(directory + filename);
|
||||
let id = `${stat.mtime}_${stat.size}`;
|
||||
|
||||
if (request.headers['if-none-match'] === '"' + id + '"') {
|
||||
response.writeHead(304, {'Content-Length': '0'});
|
||||
response.end();
|
||||
} else {
|
||||
let data = await File.readFile(directory + filename);
|
||||
response.writeHead(200, {
|
||||
'Content-Type': k_mime_types[filename.split('.').pop()] || 'text/plain',
|
||||
'Content-Length': data.byteLength,
|
||||
'etag': '"' + id + '"',
|
||||
});
|
||||
response.end(data);
|
||||
}
|
||||
} catch {
|
||||
response.writeHead(404, {"Content-Type": "text/plain; charset=utf-8", "Content-Length": "File not found".length});
|
||||
response.end("File not found");
|
||||
}
|
||||
}
|
||||
|
||||
async function wellKnownHandler(request, response, path) {
|
||||
let data = await File.readFile("data/global/.well-known/" + path);
|
||||
if (data) {
|
||||
response.writeHead(200, {"Content-Type": "text/plain", "Content-Length": data.length});
|
||||
response.end(data);
|
||||
} else {
|
||||
response.writeHead(404, {"Content-Type": "text/plain", "Content-Length": "File not found".length});
|
||||
response.end("File not found");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} path
|
||||
* @returns
|
||||
*/
|
||||
function guessTypeFromName(path) {
|
||||
let extension = path.split('.').pop();
|
||||
return k_mime_types[extension];
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
function guessTypeFromMagicBytes(data) {
|
||||
for (let magic of k_magic_bytes) {
|
||||
if (startsWithBytes(data, magic.bytes)) {
|
||||
@ -615,6 +645,14 @@ function guessTypeFromMagicBytes(data) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} response
|
||||
* @param {*} data
|
||||
* @param {*} type
|
||||
* @param {*} headers
|
||||
* @param {*} status_code
|
||||
*/
|
||||
function sendData(response, data, type, headers, status_code) {
|
||||
if (data) {
|
||||
response.writeHead(status_code ?? 200, Object.assign({"Content-Type": type || guessTypeFromMagicBytes(data) || "application/binary", "Content-Length": data.byteLength}, headers || {}));
|
||||
@ -625,6 +663,11 @@ function sendData(response, data, type, headers, status_code) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} id
|
||||
* @returns
|
||||
*/
|
||||
async function getBlobOrContent(id) {
|
||||
if (!id) {
|
||||
return;
|
||||
@ -636,6 +679,18 @@ async function getBlobOrContent(id) {
|
||||
}
|
||||
|
||||
let g_handler_index = 0;
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} response
|
||||
* @param {*} handler_blob_id
|
||||
* @param {*} path
|
||||
* @param {*} query
|
||||
* @param {*} headers
|
||||
* @param {*} packageOwner
|
||||
* @param {*} packageName
|
||||
* @returns
|
||||
*/
|
||||
async function useAppHandler(response, handler_blob_id, path, query, headers, packageOwner, packageName) {
|
||||
print('useAppHandler', packageOwner, packageName);
|
||||
let do_resolve;
|
||||
@ -669,7 +724,16 @@ async function useAppHandler(response, handler_blob_id, path, query, headers, pa
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} request
|
||||
* @param {*} response
|
||||
* @param {*} blobId
|
||||
* @param {*} uri
|
||||
* @returns
|
||||
*/
|
||||
async function blobHandler(request, response, blobId, uri) {
|
||||
// TODO(tasiaiso): break this down ?
|
||||
for (let i in k_static_files) {
|
||||
if (uri === k_static_files[i].uri && k_static_files[i].path) {
|
||||
let stat = await File.stat('core/' + k_static_files[i].path);
|
||||
@ -694,7 +758,7 @@ async function blobHandler(request, response, blobId, uri) {
|
||||
}
|
||||
|
||||
if (!uri) {
|
||||
response.writeHead(303, {"Location": (request.client.tls ? 'https://' : 'http://') + request.headers.host + blobId + '/', "Content-Length": "0"});
|
||||
response.writeHead(303, {"Location": (request.client.tls ? 'https://' : 'http://') + (request.headers['x-forwarded-host'] ?? request.headers.host) + blobId + '/', "Content-Length": "0"});
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
@ -705,7 +769,7 @@ async function blobHandler(request, response, blobId, uri) {
|
||||
let match;
|
||||
let query = form.decodeForm(request.query);
|
||||
let headers = {
|
||||
'Content-Security-Policy': 'sandbox',
|
||||
'Content-Security-Policy': k_content_security_policy,
|
||||
};
|
||||
if (query.filename && query.filename.match(/^[A-Za-z0-9\.-]*$/)) {
|
||||
headers['Content-Disposition'] = `attachment; filename=${query.filename}`;
|
||||
@ -862,13 +926,13 @@ async function blobHandler(request, response, blobId, uri) {
|
||||
}
|
||||
sendData(response, answer?.data, answer?.content_type, Object.assign(answer?.headers ?? {}, {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Security-Policy': 'sandbox',
|
||||
'Content-Security-Policy': k_content_security_policy,
|
||||
}), answer.status_code);
|
||||
} else if (id) {
|
||||
if (request.headers['if-none-match'] && request.headers['if-none-match'] == '"' + id + '"') {
|
||||
let headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Security-Policy': 'sandbox',
|
||||
'Content-Security-Policy': k_content_security_policy,
|
||||
'Content-Length': '0',
|
||||
};
|
||||
response.writeHead(304, headers);
|
||||
@ -877,7 +941,7 @@ async function blobHandler(request, response, blobId, uri) {
|
||||
let headers = {
|
||||
'ETag': '"' + id + '"',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Security-Policy': 'sandbox',
|
||||
'Content-Security-Policy': k_content_security_policy,
|
||||
};
|
||||
data = await getBlobOrContent(id);
|
||||
let type = guessTypeFromName(uri) || guessTypeFromMagicBytes(data);
|
||||
@ -897,6 +961,9 @@ ssb.addEventListener('connections', function() {
|
||||
broadcastEvent('onConnectionsChanged', []);
|
||||
});
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
*/
|
||||
async function loadSettings() {
|
||||
let data = {};
|
||||
try {
|
||||
@ -915,6 +982,9 @@ async function loadSettings() {
|
||||
gGlobalSettings = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
*/
|
||||
function sendStats() {
|
||||
let apps = Object.values(gProcesses).filter(process => process.app && process.stats).map(process => process.app);
|
||||
if (apps.length) {
|
||||
@ -928,6 +998,11 @@ function sendStats() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} process
|
||||
* @param {*} enabled
|
||||
*/
|
||||
function enableStats(process, enabled) {
|
||||
process.stats = enabled;
|
||||
if (!gStatsTimer) {
|
||||
@ -936,64 +1011,82 @@ function enableStats(process, enabled) {
|
||||
}
|
||||
}
|
||||
|
||||
function stringResponse(response, data) {
|
||||
let bytes = utf8Encode(data);
|
||||
response.writeHead(200, {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
"Content-Length": bytes.byteLength.toString(),
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
});
|
||||
return response.end(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
*/
|
||||
loadSettings().then(function() {
|
||||
if (tildefriends.https_port && gGlobalSettings.http_redirect) {
|
||||
httpd.set_http_redirect(gGlobalSettings.http_redirect);
|
||||
}
|
||||
httpd.all("/login", auth.handler);
|
||||
httpd.all("", function(request, response) {
|
||||
httpd.all("/login/logout", auth.handler);
|
||||
httpd.all("/app/socket", app.socket);
|
||||
httpd.all("", function default_http_handler(request, response) {
|
||||
let match;
|
||||
if (request.uri === "/" || request.uri === "") {
|
||||
response.writeHead(303, {"Location": (request.client.tls ? 'https://' : 'http://') + request.headers.host + gGlobalSettings.index, "Content-Length": "0"});
|
||||
let host = request.headers['x-forwarded-host'] ?? request.headers.host;
|
||||
try {
|
||||
for (let line of (gGlobalSettings.index_map || '').split('\n')) {
|
||||
let parts = line.split('=');
|
||||
if (parts.length == 2 && host.match(new RegExp(parts[0], 'i'))) {
|
||||
response.writeHead(303, {"Location": (request.client.tls ? 'https://' : 'http://') + host + parts[1], "Content-Length": "0"});
|
||||
return response.end();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
response.writeHead(303, {"Location": (request.client.tls ? 'https://' : 'http://') + host + gGlobalSettings.index, "Content-Length": "0"});
|
||||
return response.end();
|
||||
} else if (match = /^(\/~[^\/]+\/[^\/]+)(\/?.*)$/.exec(request.uri)) {
|
||||
return blobHandler(request, response, match[1], match[2]);
|
||||
} else if (match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri)) {
|
||||
return blobHandler(request, response, match[1], match[2]);
|
||||
} else if (match = /^\/static\/lit\/([\.\w-/]*)$/.exec(request.uri)) {
|
||||
return staticDirectoryHandler(request, response, 'deps/lit/', match[1]);
|
||||
} else if (match = /^\/codemirror\/([\.\w-/]*)$/.exec(request.uri)) {
|
||||
return staticDirectoryHandler(request, response, 'deps/codemirror/', match[1]);
|
||||
} else if (match = /^\/speedscope\/([\.\w-/]*)$/.exec(request.uri)) {
|
||||
return staticDirectoryHandler(request, response, 'deps/speedscope/', match[1]);
|
||||
} else if (match = /^\/static(\/.*)/.exec(request.uri)) {
|
||||
return staticFileHandler(request, response, null, match[1]);
|
||||
} else if (request.uri == "/robots.txt") {
|
||||
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 {
|
||||
let data = "File not found.";
|
||||
response.writeHead(404, {"Content-Type": "text/plain; charset=utf-8", "Content-Length": data.length.toString()});
|
||||
return response.end(data);
|
||||
}
|
||||
});
|
||||
httpd.registerSocketHandler("/app/socket", app.socket);
|
||||
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);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} user
|
||||
* @param {*} packageOwner
|
||||
* @param {*} packageName
|
||||
* @param {*} permission
|
||||
* @param {*} allow
|
||||
*/
|
||||
function storePermission(user, packageOwner, packageName, permission, allow) {
|
||||
if (!gGlobalSettings.userPermissions) {
|
||||
gGlobalSettings.userPermissions = {};
|
||||
|
11
core/form.js
11
core/form.js
@ -1,3 +1,8 @@
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} encoded
|
||||
* @returns
|
||||
*/
|
||||
function decode(encoded) {
|
||||
let result = "";
|
||||
for (let i = 0; i < encoded.length; i++) {
|
||||
@ -14,6 +19,12 @@ function decode(encoded) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} encoded
|
||||
* @param {*} initial
|
||||
* @returns
|
||||
*/
|
||||
function decodeForm(encoded, initial) {
|
||||
let result = initial || {};
|
||||
if (encoded) {
|
||||
|
18
core/http.js
18
core/http.js
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* TODOC
|
||||
* TODO: document so we can improve this
|
||||
* @param {*} url
|
||||
* @returns
|
||||
*/
|
||||
function parseUrl(url) {
|
||||
// XXX: Hack.
|
||||
let match = url.match(new RegExp("(\\w+)://([^/:]+)(?::(\\d+))?(.*)"));
|
||||
@ -9,6 +15,11 @@ function parseUrl(url) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
function parseResponse(data) {
|
||||
let firstLine;
|
||||
let headers = {};
|
||||
@ -28,6 +39,13 @@ function parseResponse(data) {
|
||||
return {body: data};
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} url
|
||||
* @param {*} options
|
||||
* @param {*} allowed_hosts
|
||||
* @returns
|
||||
*/
|
||||
export function fetch(url, options, allowed_hosts) {
|
||||
let parsed = parseUrl(url);
|
||||
return new Promise(function(resolve, reject) {
|
||||
|
593
core/httpd.js
593
core/httpd.js
@ -1,593 +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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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";
|
||||
let kAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
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' : '::';
|
||||
|
||||
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, registerSocketHandler };
|
@ -3,31 +3,40 @@
|
||||
<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>
|
||||
<button class="w3-bar-item w3-button w3-blue" id="pretty" name="pretty" accesskey="p" onmouseover="set_access_key_title(event)" data-tip="Clean up source formatting">🧼</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
@ -1,3 +0,0 @@
|
||||
User-Agent: *
|
||||
Disallow: /*/*/edit
|
||||
Allow: /
|
@ -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 {
|
||||
@ -125,34 +119,6 @@ a:active {
|
||||
.cyan { color: #2aa198; }
|
||||
.green { color: #859900; }
|
||||
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
display: none;
|
||||
border: 1px solid black;
|
||||
padding: 4px;
|
||||
color: black;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.tooltip_parent:hover .tooltip {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
kbd {
|
||||
background-color: #eee;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #b4b4b4;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, .2), 0 2px 0 0 rgba(255, 255, 255, .7) inset;
|
||||
color: #333;
|
||||
display: inline-block;
|
||||
font-size: .85em;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
padding: 2px 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.permissions {
|
||||
position: absolute;
|
||||
display: block;
|
||||
|
@ -3,6 +3,10 @@ let g_api = {};
|
||||
let g_next_id = 1;
|
||||
let g_calls = {};
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
*/
|
||||
function get_is_browser() {
|
||||
try { return window !== undefined && console !== undefined; } catch { return false; }
|
||||
}
|
||||
@ -11,6 +15,13 @@ if (k_is_browser) {
|
||||
print = console.log;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} target
|
||||
* @param {*} prop
|
||||
* @param {*} receiver
|
||||
* @returns
|
||||
*/
|
||||
function make_rpc(target, prop, receiver) {
|
||||
return function() {
|
||||
let id = g_next_id++;
|
||||
@ -29,6 +40,10 @@ function make_rpc(target, prop, receiver) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} response
|
||||
*/
|
||||
function send(response) {
|
||||
if (k_is_browser) {
|
||||
window.parent.postMessage(response, '*');
|
||||
@ -37,6 +52,10 @@ function send(response) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} message
|
||||
*/
|
||||
function call_rpc(message) {
|
||||
if (message && message.message === 'tfrpc') {
|
||||
let id = message.id;
|
||||
@ -85,6 +104,10 @@ if (k_is_browser) {
|
||||
|
||||
export let rpc = new Proxy({}, {get: make_rpc});
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} method
|
||||
*/
|
||||
export function register(method) {
|
||||
g_api[method.name] = method;
|
||||
}
|
||||
|
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,
|
||||
};
|
853
deps/codemirror_src/package-lock.json
generated
vendored
Normal file
853
deps/codemirror_src/package-lock.json
generated
vendored
Normal file
@ -0,0 +1,853 @@
|
||||
{
|
||||
"name": "codemirror_src",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/autocomplete": {
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.12.0.tgz",
|
||||
"integrity": "sha512-r4IjdYFthwbCQyvqnSlx0WBHRHi8nBvU+WjJxFUij81qsBfhNudf/XKKmmC2j3m0LaOYUQTf3qiEK1J8lO1sdg==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/commands": {
|
||||
"version": "6.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.3.3.tgz",
|
||||
"integrity": "sha512-dO4hcF0fGT9tu1Pj1D2PvGvxjeGkbC6RGcZw6Qs74TH+Ed1gw98jmUgd2axWvIZEqTeTuFrg1lEB1KV6cK9h1A==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-css": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.2.1.tgz",
|
||||
"integrity": "sha512-/UNWDNV5Viwi/1lpr/dIXJNWiwDxpw13I4pTUAsNxZdg6E0mI2kTQb0P2iHczg1Tu+H4EBgJR+hYhKiHKko7qg==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.0.2",
|
||||
"@lezer/css": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-html": {
|
||||
"version": "6.4.8",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.8.tgz",
|
||||
"integrity": "sha512-tE2YK7wDlb9ZpAH6mpTPiYm6rhfdQKVDa5r9IwIFlwwgvVaKsCfuKKZoJGWsmMZIf3FQAuJ5CHMPLymOtg1hXw==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/lang-css": "^6.0.0",
|
||||
"@codemirror/lang-javascript": "^6.0.0",
|
||||
"@codemirror/language": "^6.4.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/css": "^1.1.0",
|
||||
"@lezer/html": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-javascript": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.1.tgz",
|
||||
"integrity": "sha512-jlFOXTejVyiQCW3EQwvKH0m99bUYIw40oPmFjSX2VS78yzfe0HELZ+NEo9Yfo1MkGRpGlj3Gnu4rdxV1EnAs5A==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/lint": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/javascript": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-javascript/node_modules/@lezer/javascript": {
|
||||
"version": "1.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.13.tgz",
|
||||
"integrity": "sha512-5IBr8LIO3xJdJH1e9aj/ZNLE4LSbdsx25wFmGRAZsj2zSmwAYjx26JyU/BYOCpRQlu1jcv1z3vy4NB9+UkfRow==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
"@lezer/lr": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-javascript/node_modules/@lezer/lr": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
|
||||
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-json": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
|
||||
"integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/json": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"version": "6.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.1.tgz",
|
||||
"integrity": "sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
"@lezer/common": "^1.1.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0",
|
||||
"style-mod": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language/node_modules/@lezer/lr": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
|
||||
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language/node_modules/style-mod": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz",
|
||||
"integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA=="
|
||||
},
|
||||
"node_modules/@codemirror/lint": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.5.0.tgz",
|
||||
"integrity": "sha512-+5YyicIaaAZKU8K43IQi8TBy6mF6giGeWAH7N96Z5LC30Wm5JMjqxOYIE9mxwMG1NbhT2mA3l9hA4uuKUM3E5g==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lint/node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
|
||||
},
|
||||
"node_modules/@codemirror/search": {
|
||||
"version": "6.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
|
||||
"integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/search/node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
|
||||
"integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A=="
|
||||
},
|
||||
"node_modules/@codemirror/theme-one-dark": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz",
|
||||
"integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/highlight": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.24.0.tgz",
|
||||
"integrity": "sha512-zK6m5pNkdhdJl8idPP1gA4N8JKTiSsOz8U/Iw+C1ChMwyLG7+MLiNXnH/wFuAk6KeGEe33/adOiAh5jMqee03w==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view/node_modules/style-mod": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz",
|
||||
"integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA=="
|
||||
},
|
||||
"node_modules/@codemirror/view/node_modules/w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
|
||||
},
|
||||
"node_modules/@lezer/common": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz",
|
||||
"integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ=="
|
||||
},
|
||||
"node_modules/@lezer/css": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.8.tgz",
|
||||
"integrity": "sha512-7JhxupKuMBaWQKjQoLtzhGj83DdnZY9MckEOG5+/iLKNK2ZJqKc6hf6uc0HjwCX7Qlok44jBNqZhHKDhEhZYLA==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/css/node_modules/@lezer/lr": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
|
||||
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/highlight": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz",
|
||||
"integrity": "sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/html": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.8.tgz",
|
||||
"integrity": "sha512-EXseJ3pUzWxE6XQBQdqWHZqqlGQRSuNMBcLb6mZWS2J2v+QZhOObD+3ZIKIcm59ntTzyor4LqFTb72iJc3k23Q==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/html/node_modules/@lezer/lr": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
|
||||
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/json": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz",
|
||||
"integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/json/node_modules/@lezer/lr": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
|
||||
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
"version": "15.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
|
||||
"integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
"@types/resolve": "1.20.2",
|
||||
"deepmerge": "^4.2.2",
|
||||
"is-builtin-module": "^3.2.1",
|
||||
"is-module": "^1.0.0",
|
||||
"resolve": "^1.22.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^2.78.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-terser": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
|
||||
"integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"serialize-javascript": "^6.0.1",
|
||||
"smob": "^1.0.0",
|
||||
"terser": "^5.17.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^2.0.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
|
||||
"integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils/node_modules/@types/estree": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
|
||||
},
|
||||
"node_modules/@rollup/pluginutils/node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
|
||||
},
|
||||
"node_modules/@rollup/pluginutils/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz",
|
||||
"integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz",
|
||||
"integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz",
|
||||
"integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz",
|
||||
"integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz",
|
||||
"integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz",
|
||||
"integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz",
|
||||
"integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz",
|
||||
"integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz",
|
||||
"integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz",
|
||||
"integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz",
|
||||
"integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
|
||||
},
|
||||
"node_modules/codemirror": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
|
||||
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/commands": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/lint": "^6.0.0",
|
||||
"@codemirror/search": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-builtin-module": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
|
||||
"integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
|
||||
"dependencies": {
|
||||
"builtin-modules": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-builtin-module/node_modules/builtin-modules": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
|
||||
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.13.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve/node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve/node_modules/hasown": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
|
||||
"integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve/node_modules/is-core-module": {
|
||||
"version": "2.13.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
|
||||
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve/node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
||||
},
|
||||
"node_modules/resolve/node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz",
|
||||
"integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.12.0",
|
||||
"@rollup/rollup-android-arm64": "4.12.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.12.0",
|
||||
"@rollup/rollup-darwin-x64": "4.12.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.12.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.12.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.12.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.12.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.12.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.12.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.12.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.12.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.12.0",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz",
|
||||
"integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz",
|
||||
"integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/rollup/node_modules/@types/estree": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
|
||||
},
|
||||
"node_modules/serialize-javascript": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
|
||||
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/serialize-javascript/node_modules/randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/serialize-javascript/node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/smob": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz",
|
||||
"integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.27.2",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.27.2.tgz",
|
||||
"integrity": "sha512-sHXmLSkImesJ4p5apTeT63DsV4Obe1s37qT8qvwHRmVxKTBH7Rv9Wr26VcAMmLbmk9UliiwK8z+657NyJHHy/w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.8.2",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"bin": {
|
||||
"terser": "bin/terser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/terser/node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
||||
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.0.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/terser/node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/terser/node_modules/@jridgewell/set-array": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
|
||||
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/terser/node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
|
||||
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
}
|
||||
},
|
||||
"node_modules/terser/node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/terser/node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.22",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
|
||||
"integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/terser/node_modules/acorn": {
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/terser/node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/terser/node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/terser/node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/terser/node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user