forked from cory/tildefriends
Compare commits
175 Commits
Author | SHA1 | Date | |
---|---|---|---|
b7362dd84d | |||
01637b31e1 | |||
0e9a39608a | |||
79404e4d41 | |||
35c21fbdaf | |||
8c7bd7dc11 | |||
09ad4f0320 | |||
d96b836bef | |||
59b2ffaf95 | |||
f1b55ddd64 | |||
85acac3a30 | |||
befff5c1e5 | |||
d72ba81a67 | |||
fef88e2032 | |||
20557e8ce4 | |||
99c905e908 | |||
d7b58ee2c5 | |||
faca2d387b | |||
358d02d97f | |||
b66dac7465 | |||
f7d201859a | |||
61d2ef5469 | |||
ac994b9c62 | |||
264dcbc331 | |||
e5425c0ffb | |||
e10803de68 | |||
07b1a0e403 | |||
6ed2c702d8 | |||
5c1c33d33e | |||
70d37c88b5 | |||
1ba37d95b5 | |||
0d82198849 | |||
39927e75f2 | |||
e6fd33b969 | |||
e8fe32d5af | |||
bfc8bb864d | |||
9179746763 | |||
d0177d24cb | |||
0573008c9c | |||
9506f518c2 | |||
0f0ae9153b | |||
09c7c8ac64 | |||
5e2dfff148 | |||
958b47548d | |||
16155ef746 | |||
5755b61ea6 | |||
353847a77f | |||
bdf64edeb8 | |||
b5768dd927 | |||
3e5abf3a4d | |||
d3029639de | |||
d21d7e4add | |||
afde69b5d9 | |||
3319df3df0 | |||
1102feaac3 | |||
deede728be | |||
fc3dd84122 | |||
9239441d73 | |||
b984811851 | |||
1c52446331 | |||
b6dffa8e66 | |||
315d650d27 | |||
07c121044a | |||
f3169afcf5 | |||
c371fc2a8e | |||
6889e11fd1 | |||
fb73fd0afc | |||
6fcebd7a08 | |||
15ea62a546 | |||
b0cd58f5aa | |||
7fe8f66fd3 | |||
68ca99e9d9 | |||
a2542c658b | |||
eb203c7e62 | |||
6ef466f3ed | |||
5074246462 | |||
73bbcebddb | |||
18128303b6 | |||
c4a2d790a3 | |||
c1ec150696 | |||
f4b856df15 | |||
85b87553dd | |||
5decdf3afa | |||
a4acee4939 | |||
d06aea2831 | |||
ae0a8b0a33 | |||
f0452704a1 | |||
b8b1f1ba80 | |||
caf7478da4 | |||
0e40ba78a4 | |||
d1eac6c9eb | |||
8f5201b2bc | |||
6022001d66 | |||
f018c367ed | |||
48c47f097a | |||
39ac215b5a | |||
7d562ce85c | |||
51b317233a | |||
87ce715011 | |||
ef5afc1e23 | |||
486212f22a | |||
0e8867dd6e | |||
ca28b5ca82 | |||
19e26c1759 | |||
790f6643a4 | |||
2158ad3c0b | |||
d904d8922f | |||
da50792500 | |||
b4629acc48 | |||
0cf4118330 | |||
dd61a6ecc3 | |||
8e6f1284e1 | |||
813d3cd492 | |||
f421606e21 | |||
1ccb9183b4 | |||
7d9b627f37 | |||
3038138909 | |||
2ca08d21e4 | |||
478e96fc5f | |||
e237c7ea1d | |||
bf9ff088fd | |||
e073ebedd1 | |||
10d4ae7dcc | |||
5b8bdbb3e4 | |||
c807e21c6b | |||
cc92d0e316 | |||
09c396d5a3 | |||
bc5bbca951 | |||
ed4faedcd7 | |||
251556ebed | |||
1324afb459 | |||
1119804fc2 | |||
cdf6440197 | |||
8727fe00af | |||
7da7890bb6 | |||
706bd2c51f | |||
acabec940e | |||
470b998b61 | |||
80fad05f23 | |||
07a912fb9a | |||
e9d83262c4 | |||
74323c22f9 | |||
2614e89b1b | |||
e092fe1399 | |||
9cbe895cb8 | |||
b0b0f74e83 | |||
d9eaa92c37 | |||
566d07117e | |||
2bffdb1168 | |||
1359b48c9f | |||
a69fb5eeac | |||
38e313350e | |||
5052dc04f2 | |||
9ef3a3aca0 | |||
7b91a2ec37 | |||
2926f855a1 | |||
639419db60 | |||
54747c127c | |||
791c3dd787 | |||
b00d75ab7c | |||
956ea0df56 | |||
30014040e7 | |||
ab055c3394 | |||
1e37eeea05 | |||
84aec0278d | |||
06642f58c5 | |||
e6d44b32f4 | |||
1f3f6e2b92 | |||
8f2d3e3bcd | |||
2df2fc5792 | |||
20b0337e0a | |||
e86b9dae48 | |||
71de897419 | |||
3edfaf9137 | |||
19c1784864 |
120
Makefile
120
Makefile
@ -3,6 +3,10 @@
|
|||||||
MAKEFLAGS += --warn-undefined-variables
|
MAKEFLAGS += --warn-undefined-variables
|
||||||
MAKEFLAGS += --no-builtin-rules
|
MAKEFLAGS += --no-builtin-rules
|
||||||
|
|
||||||
|
VERSION_CODE := 10
|
||||||
|
VERSION_NUMBER := 0.0.10
|
||||||
|
VERSION_NAME := Pride is not the opposite of shame but its source.
|
||||||
|
|
||||||
PROJECT = tildefriends
|
PROJECT = tildefriends
|
||||||
BUILD_DIR ?= out
|
BUILD_DIR ?= out
|
||||||
BUILD_TYPES := debug release windebug winrelease androiddebug androidrelease androiddebug-x86_64 androidrelease-x86_64
|
BUILD_TYPES := debug release windebug winrelease androiddebug androidrelease androiddebug-x86_64 androidrelease-x86_64
|
||||||
@ -24,8 +28,7 @@ ANDROID_SDK ?= ~/Android/Sdk
|
|||||||
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/33.0.1
|
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/33.0.1
|
||||||
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-33
|
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-33
|
||||||
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/23.1.7779620
|
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/23.1.7779620
|
||||||
ANDROID_NDK_API_VERSION := 31
|
ANDROID_MIN_SDK_VERSION := 28
|
||||||
ANDROID_MIN_SDK_VERSION := 26
|
|
||||||
|
|
||||||
ANDROID_ARM64_TARGETS := \
|
ANDROID_ARM64_TARGETS := \
|
||||||
out/androiddebug/tildefriends \
|
out/androiddebug/tildefriends \
|
||||||
@ -56,8 +59,10 @@ $(NONANDROID_TARGETS): LDFLAGS += -rdynamic
|
|||||||
$(ANDROID_TARGETS): CFLAGS += \
|
$(ANDROID_TARGETS): CFLAGS += \
|
||||||
--sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
|
--sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
|
||||||
-fPIC \
|
-fPIC \
|
||||||
|
-fdebug-compilation-dir . \
|
||||||
-fomit-frame-pointer \
|
-fomit-frame-pointer \
|
||||||
-fno-asynchronous-unwind-tables
|
-fno-asynchronous-unwind-tables \
|
||||||
|
-funwind-tables
|
||||||
$(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC
|
$(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC
|
||||||
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
|
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
|
||||||
$(RELEASE_TARGETS): CFLAGS += -DNDEBUG
|
$(RELEASE_TARGETS): CFLAGS += -DNDEBUG
|
||||||
@ -76,10 +81,10 @@ windebug winrelease: LDFLAGS += \
|
|||||||
-Ldeps/openssl/mingw64/lib
|
-Ldeps/openssl/mingw64/lib
|
||||||
$(ANDROID_X86_64_TARGETS): ANDROID_NDK_TARGET_TRIPLE := x86_64-linux-android
|
$(ANDROID_X86_64_TARGETS): ANDROID_NDK_TARGET_TRIPLE := x86_64-linux-android
|
||||||
$(ANDROID_ARM64_TARGETS): ANDROID_NDK_TARGET_TRIPLE := aarch64-linux-android
|
$(ANDROID_ARM64_TARGETS): ANDROID_NDK_TARGET_TRIPLE := aarch64-linux-android
|
||||||
$(ANDROID_TARGETS): CC = $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/clang
|
$(ANDROID_TARGETS): CC = $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/$(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION)-clang
|
||||||
$(ANDROID_TARGETS): AS = $(CC)
|
$(ANDROID_TARGETS): AS = $(CC)
|
||||||
$(ANDROID_TARGETS): CFLAGS += \
|
$(ANDROID_TARGETS): CFLAGS += \
|
||||||
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_NDK_API_VERSION) \
|
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \
|
||||||
-Wno-unknown-warning-option
|
-Wno-unknown-warning-option
|
||||||
$(ANDROID_ARM64_TARGETS): CFLAGS += -Ideps/openssl/android/arm64-v8a/usr/local/include
|
$(ANDROID_ARM64_TARGETS): CFLAGS += -Ideps/openssl/android/arm64-v8a/usr/local/include
|
||||||
$(ANDROID_ARM64_TARGETS): LDFLAGS += -Ldeps/openssl/android/arm64-v8a/usr/local/lib
|
$(ANDROID_ARM64_TARGETS): LDFLAGS += -Ldeps/openssl/android/arm64-v8a/usr/local/lib
|
||||||
@ -114,6 +119,7 @@ $(APP_OBJS): CFLAGS += \
|
|||||||
-Ideps/sqlite \
|
-Ideps/sqlite \
|
||||||
-Ideps/valgrind \
|
-Ideps/valgrind \
|
||||||
-Ideps/xopt \
|
-Ideps/xopt \
|
||||||
|
-Wdouble-promotion \
|
||||||
-Werror
|
-Werror
|
||||||
|
|
||||||
BLOWFISH_SOURCES := \
|
BLOWFISH_SOURCES := \
|
||||||
@ -140,13 +146,10 @@ UV_SOURCES_unix := \
|
|||||||
deps/libuv/src/unix/async.c \
|
deps/libuv/src/unix/async.c \
|
||||||
deps/libuv/src/unix/core.c \
|
deps/libuv/src/unix/core.c \
|
||||||
deps/libuv/src/unix/dl.c \
|
deps/libuv/src/unix/dl.c \
|
||||||
deps/libuv/src/unix/epoll.c \
|
|
||||||
deps/libuv/src/unix/fs.c \
|
deps/libuv/src/unix/fs.c \
|
||||||
deps/libuv/src/unix/getaddrinfo.c \
|
deps/libuv/src/unix/getaddrinfo.c \
|
||||||
deps/libuv/src/unix/getnameinfo.c \
|
deps/libuv/src/unix/getnameinfo.c \
|
||||||
deps/libuv/src/unix/linux-core.c \
|
deps/libuv/src/unix/linux.c \
|
||||||
deps/libuv/src/unix/linux-inotify.c \
|
|
||||||
deps/libuv/src/unix/linux-syscalls.c \
|
|
||||||
deps/libuv/src/unix/loop-watcher.c \
|
deps/libuv/src/unix/loop-watcher.c \
|
||||||
deps/libuv/src/unix/loop.c \
|
deps/libuv/src/unix/loop.c \
|
||||||
deps/libuv/src/unix/pipe.c \
|
deps/libuv/src/unix/pipe.c \
|
||||||
@ -164,7 +167,6 @@ UV_SOURCES_unix := \
|
|||||||
deps/libuv/src/unix/tty.c \
|
deps/libuv/src/unix/tty.c \
|
||||||
deps/libuv/src/unix/udp.c
|
deps/libuv/src/unix/udp.c
|
||||||
UV_SOURCES_android := \
|
UV_SOURCES_android := \
|
||||||
deps/libuv/src/unix/pthread-fixes.c \
|
|
||||||
deps/libuv/src/unix/random-getentropy.c
|
deps/libuv/src/unix/random-getentropy.c
|
||||||
UV_SOURCES_win := \
|
UV_SOURCES_win := \
|
||||||
deps/libuv/src/win/async.c \
|
deps/libuv/src/win/async.c \
|
||||||
@ -196,12 +198,13 @@ UV_OBJS := $(call get_objs,UV_SOURCES)
|
|||||||
$(UV_OBJS): CFLAGS += \
|
$(UV_OBJS): CFLAGS += \
|
||||||
-Ideps/libuv/include \
|
-Ideps/libuv/include \
|
||||||
-Ideps/libuv/src \
|
-Ideps/libuv/src \
|
||||||
-Wno-unused-but-set-variable \
|
|
||||||
-Wno-incompatible-pointer-types \
|
|
||||||
-Wno-sign-compare \
|
|
||||||
-Wno-unused-variable \
|
|
||||||
-Wno-dangling-pointer \
|
-Wno-dangling-pointer \
|
||||||
|
-Wno-incompatible-pointer-types \
|
||||||
-Wno-maybe-uninitialized \
|
-Wno-maybe-uninitialized \
|
||||||
|
-Wno-sign-compare \
|
||||||
|
-Wno-unused-but-set-variable \
|
||||||
|
-Wno-unused-result \
|
||||||
|
-Wno-unused-variable \
|
||||||
-D_GNU_SOURCE
|
-D_GNU_SOURCE
|
||||||
|
|
||||||
SODIUM_SOURCES := \
|
SODIUM_SOURCES := \
|
||||||
@ -244,7 +247,8 @@ SODIUM_SOURCES := \
|
|||||||
deps/libsodium/src/libsodium/sodium/core.c \
|
deps/libsodium/src/libsodium/sodium/core.c \
|
||||||
deps/libsodium/src/libsodium/sodium/codecs.c \
|
deps/libsodium/src/libsodium/sodium/codecs.c \
|
||||||
deps/libsodium/src/libsodium/sodium/runtime.c \
|
deps/libsodium/src/libsodium/sodium/runtime.c \
|
||||||
deps/libsodium/src/libsodium/sodium/utils.c
|
deps/libsodium/src/libsodium/sodium/utils.c \
|
||||||
|
deps/libsodium/src/libsodium/sodium/version.c
|
||||||
SODIUM_OBJS := $(call get_objs,SODIUM_SOURCES)
|
SODIUM_OBJS := $(call get_objs,SODIUM_SOURCES)
|
||||||
$(SODIUM_OBJS): CFLAGS += \
|
$(SODIUM_OBJS): CFLAGS += \
|
||||||
-DCONFIGURED=1 \
|
-DCONFIGURED=1 \
|
||||||
@ -253,6 +257,7 @@ $(SODIUM_OBJS): CFLAGS += \
|
|||||||
-Wno-unused-variable \
|
-Wno-unused-variable \
|
||||||
-Wno-type-limits \
|
-Wno-type-limits \
|
||||||
-Wno-unknown-pragmas \
|
-Wno-unknown-pragmas \
|
||||||
|
-Ideps/libsodium/builds/msvc \
|
||||||
-Ideps/libsodium/src/libsodium/include/sodium
|
-Ideps/libsodium/src/libsodium/include/sodium
|
||||||
|
|
||||||
SQLITE_SOURCES := deps/sqlite/sqlite3.c
|
SQLITE_SOURCES := deps/sqlite/sqlite3.c
|
||||||
@ -265,7 +270,7 @@ $(SQLITE_OBJS): CFLAGS += \
|
|||||||
-DSQLITE_ENABLE_FTS5 \
|
-DSQLITE_ENABLE_FTS5 \
|
||||||
-DSQLITE_ENABLE_JSON1 \
|
-DSQLITE_ENABLE_JSON1 \
|
||||||
-DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
|
-DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
|
||||||
-DSQLITE_MAX_ATTACHED=0 \
|
-DSQLITE_MAX_ATTACHED=1 \
|
||||||
-DSQLITE_MAX_COLUMN=100 \
|
-DSQLITE_MAX_COLUMN=100 \
|
||||||
-DSQLITE_MAX_COMPOUND_SELECT=300 \
|
-DSQLITE_MAX_COMPOUND_SELECT=300 \
|
||||||
-DSQLITE_MAX_EXPR_DEPTH=40 \
|
-DSQLITE_MAX_EXPR_DEPTH=40 \
|
||||||
@ -374,17 +379,19 @@ debug release: LDFLAGS += \
|
|||||||
-lssl \
|
-lssl \
|
||||||
-lcrypto
|
-lcrypto
|
||||||
windebug winrelease: LDFLAGS += \
|
windebug winrelease: LDFLAGS += \
|
||||||
-lwsock32 \
|
|
||||||
-lws2_32 \
|
|
||||||
-lkernel32 \
|
|
||||||
-liphlpapi \
|
|
||||||
-luserenv \
|
|
||||||
-lssl \
|
-lssl \
|
||||||
-lcrypto \
|
-lcrypto \
|
||||||
|
-lcrypt32 \
|
||||||
|
-ldbghelp \
|
||||||
|
-liphlpapi \
|
||||||
|
-lkernel32 \
|
||||||
|
-lole32 \
|
||||||
|
-luserenv \
|
||||||
|
-luuid \
|
||||||
-lws2_32 \
|
-lws2_32 \
|
||||||
-lcrypt32
|
-lwsock32
|
||||||
$(ANDROID_TARGETS): LDFLAGS += \
|
$(ANDROID_TARGETS): LDFLAGS += \
|
||||||
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_NDK_API_VERSION) \
|
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \
|
||||||
-ldl \
|
-ldl \
|
||||||
-llog \
|
-llog \
|
||||||
-lssl \
|
-lssl \
|
||||||
@ -431,15 +438,32 @@ endef
|
|||||||
|
|
||||||
$(foreach build_type,$(BUILD_TYPES),$(eval $(call build_rules,$(build_type))))
|
$(foreach build_type,$(BUILD_TYPES),$(eval $(call build_rules,$(build_type))))
|
||||||
|
|
||||||
|
src/version.h : $(firstword $(MAKEFILE_LIST))
|
||||||
|
@echo [version] $@
|
||||||
|
@echo "#define VERSION_NUMBER \"$(VERSION_NUMBER)\"\n#define VERSION_NAME \"$(VERSION_NAME)\"\n" > $@
|
||||||
|
|
||||||
|
src/android/AndroidManifest.xml : $(firstword $(MAKEFILE_LIST))
|
||||||
|
@echo [android_version] $@
|
||||||
|
@sed -i \
|
||||||
|
-e 's/versionCode=".*"/versionCode="$(VERSION_CODE)"/' \
|
||||||
|
-e 's/versionName=".*"/versionName="$(VERSION_NUMBER)"/' \
|
||||||
|
-e 's/android:minSdkVersion=".*"/android:minSdkVersion="$(ANDROID_MIN_SDK_VERSION)"/' \
|
||||||
|
$@
|
||||||
|
|
||||||
# Android support.
|
# Android support.
|
||||||
out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml
|
out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
@echo [aapt2] $@
|
@echo [aapt2] $@
|
||||||
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ src/android/res/layout/activity_main.xml
|
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ src/android/res/layout/activity_main.xml
|
||||||
|
|
||||||
out/apk/res.apk out/gen/com/unprompted/tildefriends/R.java: out/res/layout_activity_main.xml.flat src/android/AndroidManifest.xml
|
out/res/drawable_icon.xml.flat: src/android/res/drawable/icon.xml
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat --manifest src/android/AndroidManifest.xml -o out/apk/res.apk --java out/gen/
|
@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
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat --manifest src/android/AndroidManifest.xml -o out/apk/res.apk --java out/gen/
|
||||||
|
|
||||||
JAVA_FILES := out/gen/com/unprompted/tildefriends/R.java $(wildcard src/android/com/unprompted/tildefriends/*.java)
|
JAVA_FILES := out/gen/com/unprompted/tildefriends/R.java $(wildcard src/android/com/unprompted/tildefriends/*.java)
|
||||||
CLASS_FILES := $(foreach src,$(JAVA_FILES),out/classes/com/unprompted/tildefriends/$(notdir $(src:.java=.class)))
|
CLASS_FILES := $(foreach src,$(JAVA_FILES),out/classes/com/unprompted/tildefriends/$(notdir $(src:.java=.class)))
|
||||||
@ -457,8 +481,7 @@ PACKAGE_DIRS := \
|
|||||||
apps/ \
|
apps/ \
|
||||||
core/ \
|
core/ \
|
||||||
deps/codemirror/ \
|
deps/codemirror/ \
|
||||||
deps/split/ \
|
deps/lit/
|
||||||
deps/smoothie/
|
|
||||||
|
|
||||||
RAW_FILES := $(shell find $(PACKAGE_DIRS) -type f)
|
RAW_FILES := $(shell find $(PACKAGE_DIRS) -type f)
|
||||||
|
|
||||||
@ -478,20 +501,57 @@ out/%.unsigned.apk:
|
|||||||
@cp out/apk/res.apk $@
|
@cp out/apk/res.apk $@
|
||||||
@cp out/apk/classes.dex out/apk$(BUILD_TYPE)/
|
@cp out/apk/classes.dex out/apk$(BUILD_TYPE)/
|
||||||
@cd out/apk$(BUILD_TYPE) && zip -u ../../$@ -q -9 -r . && cd ../../
|
@cd out/apk$(BUILD_TYPE) && zip -u ../../$@ -q -9 -r . && cd ../../
|
||||||
@zip -u $@ -q -9 -r $(PACKAGE_DIRS) $(RAW_FILES)
|
@zip -u $@ -q -9 -x '*.map' -r $(PACKAGE_DIRS) $(RAW_FILES)
|
||||||
|
|
||||||
out/%.apk: out/apk/%.unsigned.apk
|
out/%.apk: out/apk/%.unsigned.apk
|
||||||
@echo [apksigner] $(notdir $@)
|
@echo [apksigner] $(notdir $@)
|
||||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks keystore.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --out $@ $<
|
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks keystore.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --out $@ $<
|
||||||
|
|
||||||
apk: out/TildeFriends-debug.apk
|
apk: out/TildeFriends-release.apk
|
||||||
.PHONY: apk
|
.PHONY: apk
|
||||||
|
|
||||||
apkgo: out/TildeFriends-debug.apk
|
apkgo: out/TildeFriends-release.apk
|
||||||
@adb install $<
|
@adb install $<
|
||||||
@adb shell am start com.unprompted.tildefriends/.MainActivity
|
@adb shell am start com.unprompted.tildefriends/.MainActivity
|
||||||
.PHONY: apkgo
|
.PHONY: apkgo
|
||||||
|
|
||||||
|
apklog:
|
||||||
|
@adb logcat *:S tildefriends
|
||||||
|
.PHONY: apklog
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BUILD_DIR)
|
rm -rf $(BUILD_DIR)
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
|
|
||||||
|
dist: apk
|
||||||
|
@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"
|
||||||
|
@tar \
|
||||||
|
--exclude=apps/gg* \
|
||||||
|
--exclude=deps/libbacktrace/Isaac.Newton-Opticks.txt \
|
||||||
|
--exclude=deps/libsodium/builds/msvc/vs* \
|
||||||
|
--exclude=deps/libsodium/builds/msvc/build \
|
||||||
|
--exclude=deps/libsodium/builds/msvc/properties \
|
||||||
|
--exclude=deps/libsodium/configure \
|
||||||
|
--exclude=deps/libsodium/test \
|
||||||
|
--exclude=deps/libuv/docs \
|
||||||
|
--exclude=deps/libuv/test \
|
||||||
|
--exclude=deps/openssl \
|
||||||
|
--exclude=deps/speedscope/*.map \
|
||||||
|
--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)
|
||||||
|
@echo "[cp] TildeFriends-$(VERSION_NUMBER).apk"
|
||||||
|
@cp out/TildeFriends-release.apk TildeFriends-$(VERSION_NUMBER).apk
|
||||||
|
.PHONY: dist
|
||||||
|
|
||||||
|
dist-test: dist
|
||||||
|
@tar -xf tildefriends-$(VERSION_NUMBER).tar.xz
|
||||||
|
@$(MAKE) -C tildefriends-$(VERSION_NUMBER)/ debug release
|
||||||
|
@rm -rf tildefriends-$(VERSION_NUMBER)
|
||||||
|
.PHONY: dist-test
|
||||||
|
@ -9,6 +9,7 @@ tfrpc.register(function global_settings_set(key, value) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
try {
|
||||||
let data = {
|
let data = {
|
||||||
users: {},
|
users: {},
|
||||||
granted: await core.allPermissionsGranted(),
|
granted: await core.allPermissionsGranted(),
|
||||||
@ -18,5 +19,8 @@ async function main() {
|
|||||||
data.users[user] = await core.permissionsForUser(user);
|
data.users[user] = await core.permissionsForUser(user);
|
||||||
}
|
}
|
||||||
await app.setDocument(utf8Decode(getFile('index.html')).replace('$data', JSON.stringify(data)));
|
await app.setDocument(utf8Decode(getFile('index.html')).replace('$data', JSON.stringify(data)));
|
||||||
|
} catch {
|
||||||
|
await app.setDocument('<span style="color: #f00">Only an administrator can modify these settings.</span>');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
main();
|
main();
|
@ -1,9 +1,9 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html style="width: 100%">
|
||||||
<head>
|
<head>
|
||||||
<script>const g_data = $data;</script>
|
<script>const g_data = $data;</script>
|
||||||
</head>
|
</head>
|
||||||
<body style="color: #fff">
|
<body style="color: #fff; width: 100%">
|
||||||
<h1>Tilde Friends Administration</h1>
|
<h1>Tilde Friends Administration</h1>
|
||||||
</body>
|
</body>
|
||||||
<script type="module" src="script.js"></script>
|
<script type="module" src="script.js"></script>
|
||||||
|
@ -25,29 +25,37 @@ window.addEventListener('load', function() {
|
|||||||
function input_template(key, description) {
|
function input_template(key, description) {
|
||||||
if (description.type === 'boolean') {
|
if (description.type === 'boolean') {
|
||||||
return html`
|
return html`
|
||||||
<label ?for=${'gs_' + key} style="grid-column: 1">${key}: </label>
|
<div style="margin-top: 1em">
|
||||||
<input type="checkbox" ?checked=${description.value} ?id=${'gs_' + key} style="grid-column: 2"></input>
|
<label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
|
||||||
<div style="grid-column: 3">
|
<div>
|
||||||
|
<input type="checkbox" ?checked=${description.value} id=${'gs_' + key}></input>
|
||||||
<button @click=${(e) => global_settings_set(key, e.srcElement.parentElement.previousElementSibling.checked)}>Set</button>
|
<button @click=${(e) => global_settings_set(key, e.srcElement.parentElement.previousElementSibling.checked)}>Set</button>
|
||||||
<span>${description.description}</span>
|
<div>${description.description}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
} else if (description.type === 'textarea') {
|
} else if (description.type === 'textarea') {
|
||||||
return html`
|
return html`
|
||||||
<label ?for=${'gs_' + key} style="grid-column: 1">${key}: </label>
|
<div style="margin-top: 1em"">
|
||||||
<textarea style="vertical-align: top" rows=20 cols=80 ?id=${'gs_' + key}>${description.value}</textarea>
|
<label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
|
||||||
<div style="grid-column: 3">
|
<div style="width: 100%; padding: 0; margin: 0">
|
||||||
|
<div style="width: 90%; padding: 0 margin: 0">
|
||||||
|
<textarea style="vertical-align: top; width: 100%" rows=20 cols=80 id=${'gs_' + key}>${description.value}</textarea>
|
||||||
|
</div>
|
||||||
<button @click=${(e) => global_settings_set(key, e.srcElement.parentElement.previousElementSibling.value)}>Set</button>
|
<button @click=${(e) => global_settings_set(key, e.srcElement.parentElement.previousElementSibling.value)}>Set</button>
|
||||||
<span>${description.description}</span>
|
<div>${description.description}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
return html`
|
return html`
|
||||||
<label ?for=${'gs_' + key} style="grid-column: 1">${key}: </label>
|
<div style="margin-top: 1em">
|
||||||
<input type="text" value="${description.value}" ?id=${'gs_' + key}></input>
|
<label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
|
||||||
<div style="grid-column: 3">
|
<div>
|
||||||
|
<input type="text" value="${description.value}" id=${'gs_' + key}></input>
|
||||||
<button @click=${(e) => global_settings_set(key, e.srcElement.parentElement.previousElementSibling.value)}>Set</button>
|
<button @click=${(e) => global_settings_set(key, e.srcElement.parentElement.previousElementSibling.value)}>Set</button>
|
||||||
<span>${description.description}</span>
|
<div>${description.description}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -67,12 +75,13 @@ window.addEventListener('load', function() {
|
|||||||
${Object.entries(users).map(u => user_template(u[0], u[1]))}
|
${Object.entries(users).map(u => user_template(u[0], u[1]))}
|
||||||
</ul>`;
|
</ul>`;
|
||||||
const page_template = (data) =>
|
const page_template = (data) =>
|
||||||
html`<div>
|
html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%">
|
||||||
<h2>Global Settings</h2>
|
<h2>Global Settings</h2>
|
||||||
<div style="display: grid">
|
<div>
|
||||||
${Object.keys(data.settings).sort().map(x => html`${input_template(x, data.settings[x])}`)}
|
${Object.keys(data.settings).sort().map(x => html`${input_template(x, data.settings[x])}`)}
|
||||||
</div>
|
</div>
|
||||||
${users_template(data.users)}
|
${users_template(data.users)}
|
||||||
</div>`;
|
</div>
|
||||||
|
`;
|
||||||
render(page_template(g_data), document.body);
|
render(page_template(g_data), document.body);
|
||||||
});
|
});
|
@ -1,4 +1,3 @@
|
|||||||
var global = Function('return this')();
|
|
||||||
function treeify(o) {
|
function treeify(o) {
|
||||||
if (typeof(o) == 'object') {
|
if (typeof(o) == 'object') {
|
||||||
return Object.fromEntries(Object.keys(o).map(x => [x, treeify(o[x])]));
|
return Object.fromEntries(Object.keys(o).map(x => [x, treeify(o[x])]));
|
||||||
@ -8,4 +7,4 @@ function treeify(o) {
|
|||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app.setDocument(`<pre style="color:#fff">${JSON.stringify(treeify(global), null, 2)}</pre>`);
|
app.setDocument(`<pre style="color:#fff">${JSON.stringify(treeify(globalThis), null, 2)}</pre>`);
|
@ -70,7 +70,7 @@ async function main() {
|
|||||||
populate_apps('apps', '${core.user.credentials?.session?.name}', ${JSON.stringify(apps)});
|
populate_apps('apps', '${core.user.credentials?.session?.name}', ${JSON.stringify(apps)});
|
||||||
populate_apps('core_apps', 'core', ${JSON.stringify(core_apps)});
|
populate_apps('core_apps', 'core', ${JSON.stringify(core_apps)});
|
||||||
</script>
|
</script>
|
||||||
</html>`
|
</html>`;
|
||||||
app.setDocument(doc);
|
app.setDocument(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app"
|
"type": "tildefriends-app",
|
||||||
|
"emoji": "🛍"
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
async function get_apps() {
|
async function get_apps() {
|
||||||
let results = {};
|
let results = {};
|
||||||
await ssb.sqlStream(`
|
await ssb.sqlAsync(`
|
||||||
SELECT messages.*
|
SELECT messages.*
|
||||||
FROM messages_fts('"application/tildefriends"')
|
FROM messages_fts('"application/tildefriends"')
|
||||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||||
|
@ -20,7 +20,7 @@ async function database_list() {
|
|||||||
}
|
}
|
||||||
populate_dbs('dbs', ${JSON.stringify(dbs)});
|
populate_dbs('dbs', ${JSON.stringify(dbs)});
|
||||||
</script>
|
</script>
|
||||||
</html>`
|
</html>`;
|
||||||
app.setDocument(doc);
|
app.setDocument(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ async function key_list(db) {
|
|||||||
}
|
}
|
||||||
populate_dbs('keys', ${JSON.stringify(object)});
|
populate_dbs('keys', ${JSON.stringify(object)});
|
||||||
</script>
|
</script>
|
||||||
</html>`
|
</html>`;
|
||||||
app.setDocument(doc);
|
app.setDocument(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,9 +16,7 @@
|
|||||||
- / => Something good.
|
- / => Something good.
|
||||||
- update docs
|
- update docs
|
||||||
- audit + document API exposed to apps
|
- audit + document API exposed to apps
|
||||||
- sqlStream => sqlExec or something
|
|
||||||
- fix weird HTTP warnings
|
- fix weird HTTP warnings
|
||||||
- ssb from child process?
|
|
||||||
- channels
|
- channels
|
||||||
- placeholder/missing images
|
- placeholder/missing images
|
||||||
- no denial of service
|
- no denial of service
|
||||||
@ -57,6 +55,8 @@
|
|||||||
- keep working on good error feedback
|
- keep working on good error feedback
|
||||||
- build for windows
|
- build for windows
|
||||||
- installable apps (bring back an app message?)
|
- installable apps (bring back an app message?)
|
||||||
|
- sqlStream => sqlExec or something
|
||||||
|
- !ssb from child process?
|
||||||
|
|
||||||
## Done
|
## Done
|
||||||
- update LICENSE
|
- update LICENSE
|
||||||
|
@ -1,75 +1,163 @@
|
|||||||
"use strict";
|
let g_about_cache = {};
|
||||||
|
|
||||||
var g_following_cache = {};
|
async function query(sql, args) {
|
||||||
var g_following_deep_cache = {};
|
let result = [];
|
||||||
var g_about_cache = {};
|
await ssb.sqlAsync(sql, args, function(row) {
|
||||||
|
result.push(row);
|
||||||
async function following(db, id) {
|
|
||||||
if (g_following_cache[id]) {
|
|
||||||
return g_following_cache[id];
|
|
||||||
}
|
|
||||||
var o = await db.get(id + ":following");
|
|
||||||
const k_version = 5;
|
|
||||||
var f = o ? JSON.parse(o) : o;
|
|
||||||
if (!f || f.version != k_version) {
|
|
||||||
f = {users: [], sequence: 0, version: k_version};
|
|
||||||
}
|
|
||||||
f.users = new Set(f.users);
|
|
||||||
await ssb.sqlAsync(
|
|
||||||
"SELECT "+
|
|
||||||
" sequence, "+
|
|
||||||
" json_extract(content, '$.contact') AS contact, "+
|
|
||||||
" json_extract(content, '$.following') AS following "+
|
|
||||||
"FROM messages "+
|
|
||||||
"WHERE "+
|
|
||||||
" author = ?1 AND "+
|
|
||||||
" sequence > ?2 AND "+
|
|
||||||
" json_extract(content, '$.type') = 'contact' "+
|
|
||||||
"UNION SELECT MAX(sequence) AS sequence, NULL, NULL FROM messages WHERE author = ?1 "+
|
|
||||||
"ORDER BY sequence",
|
|
||||||
[id, f.sequence],
|
|
||||||
function(row) {
|
|
||||||
if (row.following) {
|
|
||||||
f.users.add(row.contact);
|
|
||||||
} else {
|
|
||||||
f.users.delete(row.contact);
|
|
||||||
}
|
|
||||||
f.sequence = row.sequence;
|
|
||||||
});
|
});
|
||||||
var as_set = f.users;
|
return result;
|
||||||
f.users = Array.from(f.users).sort();
|
|
||||||
var j = JSON.stringify(f);
|
|
||||||
if (o != j) {
|
|
||||||
await db.set(id + ":following", j);
|
|
||||||
}
|
|
||||||
f.users = as_set;
|
|
||||||
g_following_cache[id] = f.users;
|
|
||||||
return f.users;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function followingDeep(db, seed_ids, depth) {
|
async function contacts_internal(id, last_row_id, following, max_row_id) {
|
||||||
if (depth <= 0) {
|
let result = Object.assign({}, following[id] || {});
|
||||||
return seed_ids;
|
result.following = result.following || {};
|
||||||
|
result.blocking = result.blocking || {};
|
||||||
|
let contacts = await query(
|
||||||
|
`
|
||||||
|
SELECT content FROM messages
|
||||||
|
WHERE author = ? AND
|
||||||
|
rowid > ? AND
|
||||||
|
rowid <= ? AND
|
||||||
|
json_extract(content, '$.type') = 'contact'
|
||||||
|
ORDER BY sequence
|
||||||
|
`,
|
||||||
|
[id, last_row_id, max_row_id]);
|
||||||
|
for (let row of contacts) {
|
||||||
|
let contact = JSON.parse(row.content);
|
||||||
|
if (contact.following === true) {
|
||||||
|
result.following[contact.contact] = true;
|
||||||
|
} else if (contact.following === false) {
|
||||||
|
delete result.following[contact.contact];
|
||||||
|
} else if (contact.blocking === true) {
|
||||||
|
result.blocking[contact.contact] = true;
|
||||||
|
} else if (contact.blocking === false) {
|
||||||
|
delete result.blocking[contact.contact];
|
||||||
}
|
}
|
||||||
var key = JSON.stringify([seed_ids, depth]);
|
|
||||||
if (g_following_deep_cache[key]) {
|
|
||||||
return g_following_deep_cache[key];
|
|
||||||
}
|
}
|
||||||
var f = await Promise.all(seed_ids.map(x => following(db, x).then(x => [...x])));
|
following[id] = result;
|
||||||
var ids = [].concat(...f);
|
return result;
|
||||||
var x = await followingDeep(db, [...new Set(ids)].sort(), depth - 1);
|
}
|
||||||
x = [...new Set([].concat(...x, ...seed_ids))].sort();
|
|
||||||
g_following_deep_cache[key] = x;
|
async function contact(id, last_row_id, following, max_row_id) {
|
||||||
return x;
|
return await contacts_internal(id, last_row_id, following, max_row_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function following_deep_internal(ids, depth, blocking, last_row_id, following, max_row_id) {
|
||||||
|
let contacts = await Promise.all([...new Set(ids)].map(x => contact(x, last_row_id, following, max_row_id)));
|
||||||
|
let result = {};
|
||||||
|
for (let i = 0; i < ids.length; i++) {
|
||||||
|
let id = ids[i];
|
||||||
|
let contact = contacts[i];
|
||||||
|
let all_blocking = Object.assign({}, contact.blocking, blocking);
|
||||||
|
let found = Object.keys(contact.following).filter(y => !all_blocking[y]);
|
||||||
|
let deeper = depth > 1 ? await following_deep_internal(found, depth - 1, all_blocking, last_row_id, following, max_row_id) : [];
|
||||||
|
result[id] = [id, ...found, ...deeper];
|
||||||
|
}
|
||||||
|
return [...new Set(Object.values(result).flat())];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function following_deep(ids, depth, blocking) {
|
||||||
|
let db = await database('cache');
|
||||||
|
const k_cache_version = 5;
|
||||||
|
let cache = await db.get('following');
|
||||||
|
cache = cache ? JSON.parse(cache) : {};
|
||||||
|
if (cache.version !== k_cache_version) {
|
||||||
|
cache = {
|
||||||
|
version: k_cache_version,
|
||||||
|
following: {},
|
||||||
|
last_row_id: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let max_row_id = (await query(`
|
||||||
|
SELECT MAX(rowid) AS max_row_id FROM messages
|
||||||
|
`, []))[0].max_row_id;
|
||||||
|
let result = await following_deep_internal(ids, depth, blocking, cache.last_row_id, cache.following, max_row_id);
|
||||||
|
cache.last_row_id = max_row_id;
|
||||||
|
let store = JSON.stringify(cache);
|
||||||
|
await db.set('following', store);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetch_about(db, ids, users) {
|
||||||
|
const k_cache_version = 1;
|
||||||
|
let cache = await db.get('about');
|
||||||
|
cache = cache ? JSON.parse(cache) : {};
|
||||||
|
if (cache.version !== k_cache_version) {
|
||||||
|
cache = {
|
||||||
|
version: k_cache_version,
|
||||||
|
about: {},
|
||||||
|
last_row_id: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let max_row_id = 0;
|
||||||
|
await ssb.sqlAsync(`
|
||||||
|
SELECT MAX(rowid) AS max_row_id FROM messages
|
||||||
|
`,
|
||||||
|
[],
|
||||||
|
function(row) {
|
||||||
|
max_row_id = row.max_row_id;
|
||||||
|
});
|
||||||
|
for (let id of Object.keys(cache.about)) {
|
||||||
|
if (ids.indexOf(id) == -1) {
|
||||||
|
delete cache.about[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let abouts = [];
|
||||||
|
await ssb.sqlAsync(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
messages.*
|
||||||
|
FROM
|
||||||
|
messages,
|
||||||
|
json_each(?1) AS following
|
||||||
|
WHERE
|
||||||
|
messages.author = following.value AND
|
||||||
|
messages.rowid > ?3 AND
|
||||||
|
messages.rowid <= ?4 AND
|
||||||
|
json_extract(messages.content, '$.type') = 'about'
|
||||||
|
UNION
|
||||||
|
SELECT
|
||||||
|
messages.*
|
||||||
|
FROM
|
||||||
|
messages,
|
||||||
|
json_each(?2) AS following
|
||||||
|
WHERE
|
||||||
|
messages.author = following.value AND
|
||||||
|
messages.rowid <= ?4 AND
|
||||||
|
json_extract(messages.content, '$.type') = 'about'
|
||||||
|
ORDER BY messages.author, messages.sequence
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
JSON.stringify(ids.filter(id => cache.about[id])),
|
||||||
|
JSON.stringify(ids.filter(id => !cache.about[id])),
|
||||||
|
cache.last_row_id,
|
||||||
|
max_row_id,
|
||||||
|
]);
|
||||||
|
for (let about of abouts) {
|
||||||
|
let content = JSON.parse(about.content);
|
||||||
|
if (content.about === about.author) {
|
||||||
|
delete content.type;
|
||||||
|
delete content.about;
|
||||||
|
cache.about[about.author] = Object.assign(cache.about[about.author] || {}, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cache.last_row_id = max_row_id;
|
||||||
|
await db.set('about', JSON.stringify(cache));
|
||||||
|
users = users || {};
|
||||||
|
for (let id of Object.keys(cache.about)) {
|
||||||
|
users[id] = Object.assign(users[id] || {}, cache.about[id]);
|
||||||
|
}
|
||||||
|
return Object.assign({}, users);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAbout(db, id) {
|
async function getAbout(db, id) {
|
||||||
if (g_about_cache[id]) {
|
if (g_about_cache[id]) {
|
||||||
return g_about_cache[id];
|
return g_about_cache[id];
|
||||||
}
|
}
|
||||||
var o = await db.get(id + ":about");
|
let o = await db.get(id + ":about");
|
||||||
const k_version = 4;
|
const k_version = 4;
|
||||||
var f = o ? JSON.parse(o) : o;
|
let f = o ? JSON.parse(o) : o;
|
||||||
if (!f || f.version != k_version) {
|
if (!f || f.version != k_version) {
|
||||||
f = {about: {}, sequence: 0, version: k_version};
|
f = {about: {}, sequence: 0, version: k_version};
|
||||||
}
|
}
|
||||||
@ -89,7 +177,7 @@ async function getAbout(db, id) {
|
|||||||
function(row) {
|
function(row) {
|
||||||
f.sequence = row.sequence;
|
f.sequence = row.sequence;
|
||||||
if (row.content) {
|
if (row.content) {
|
||||||
var about = {};
|
let about = {};
|
||||||
try {
|
try {
|
||||||
about = JSON.parse(row.content);
|
about = JSON.parse(row.content);
|
||||||
} catch {
|
} catch {
|
||||||
@ -99,7 +187,7 @@ async function getAbout(db, id) {
|
|||||||
f.about = Object.assign(f.about, about);
|
f.about = Object.assign(f.about, about);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var j = JSON.stringify(f);
|
let j = JSON.stringify(f);
|
||||||
if (o != j) {
|
if (o != j) {
|
||||||
await db.set(id + ":about", j);
|
await db.set(id + ":about", j);
|
||||||
}
|
}
|
||||||
@ -110,7 +198,7 @@ async function getAbout(db, id) {
|
|||||||
async function getSize(db, id) {
|
async function getSize(db, id) {
|
||||||
let size = 0;
|
let size = 0;
|
||||||
await ssb.sqlAsync(
|
await ssb.sqlAsync(
|
||||||
"SELECT (SUM(LENGTH(content)) + SUM(LENGTH(author)) + SUM(LENGTH(id))) AS size FROM messages WHERE author = ?1",
|
"SELECT (LENGTH(author) * COUNT(*) + SUM(LENGTH(content)) + SUM(LENGTH(id))) AS size FROM messages WHERE author = ?1",
|
||||||
[id],
|
[id],
|
||||||
function (row) {
|
function (row) {
|
||||||
size += row.size;
|
size += row.size;
|
||||||
@ -118,6 +206,25 @@ async function getSize(db, id) {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function getSizes(ids) {
|
||||||
|
let sizes = {};
|
||||||
|
await ssb.sqlAsync(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
author,
|
||||||
|
(SUM(LENGTH(content)) + SUM(LENGTH(author)) + SUM(LENGTH(messages.id))) AS size
|
||||||
|
FROM messages
|
||||||
|
JOIN json_each(?) AS ids ON author = ids.value
|
||||||
|
GROUP BY author
|
||||||
|
`,
|
||||||
|
[JSON.stringify(ids)],
|
||||||
|
function (row) {
|
||||||
|
sizes[row.author] = row.size;
|
||||||
|
});
|
||||||
|
return sizes;
|
||||||
|
}
|
||||||
|
|
||||||
function niceSize(bytes) {
|
function niceSize(bytes) {
|
||||||
let value = bytes;
|
let value = bytes;
|
||||||
let unit = 'B';
|
let unit = 'B';
|
||||||
@ -133,27 +240,28 @@ function niceSize(bytes) {
|
|||||||
return Math.round(value * 10) / 10 + ' ' + unit;
|
return Math.round(value * 10) / 10 + ' ' + unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildTree(db, root, indent, depth) {
|
function escape(value) {
|
||||||
var f = await following(db, root);
|
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
||||||
var result = indent + '[' + f.size + '] ' + '<a target="_top" href="../index/#' + root + '">' + ((await getAbout(db, root)).name || root) + '</a> ' + niceSize(await getSize(db, root)) + '\n';
|
|
||||||
if (depth > 0) {
|
|
||||||
for (let next of f) {
|
|
||||||
result += await buildTree(db, next, indent + ' ', depth - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
await app.setDocument('<pre style="color: #fff">building...</pre>');
|
await app.setDocument('<pre style="color: #fff">building...</pre>');
|
||||||
var db = await database('ssb');
|
let db = await database('ssb');
|
||||||
var whoami = await ssb.getIdentities();
|
let whoami = await ssb.getIdentities();
|
||||||
var tree = '';
|
let tree = '';
|
||||||
for (let id of whoami) {
|
await app.setDocument(`<pre style="color: #fff">Enumerating followed users...</pre>`);
|
||||||
await app.setDocument(`<pre style="color: #fff">building... ${id}</pre>`);
|
let following = await following_deep(whoami, 2, {});
|
||||||
tree += await buildTree(db, id, '', 2);
|
await app.setDocument(`<pre style="color: #fff">Getting names and sizes...</pre>`);
|
||||||
|
let [about, sizes] = await Promise.all([
|
||||||
|
fetch_about(db, following, {}),
|
||||||
|
getSizes(following),
|
||||||
|
]);
|
||||||
|
await app.setDocument(`<pre style="color: #fff">Finishing...</pre>`);
|
||||||
|
following.sort((a, b) => ((sizes[b] ?? 0) - (sizes[a] ?? 0)));
|
||||||
|
for (let id of following) {
|
||||||
|
tree += `<li><a href="/~core/ssb/#${id}">${escape(about[id]?.name ?? id)}</a> ${niceSize(sizes[id] ?? 0)}</li>\n`;
|
||||||
}
|
}
|
||||||
await app.setDocument('<pre style="color: #fff">FOLLOWING:\n' + tree + '</pre>');
|
await app.setDocument('<!DOCTYPE html>\n<html>\n<body style="color: #fff"><h1>Following</h1>\n<ul>' + tree + '</ul>\n</body>\n</html>');
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
4
apps/gg.json
Normal file
4
apps/gg.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"type": "tildefriends-app",
|
||||||
|
"emoji": "🗺"
|
||||||
|
}
|
79
apps/gg/app.js
Normal file
79
apps/gg/app.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import * as tfrpc from '/tfrpc.js';
|
||||||
|
import * as strava from './strava.js';
|
||||||
|
|
||||||
|
let g_database;
|
||||||
|
let g_shared_database;
|
||||||
|
|
||||||
|
tfrpc.register(async function createIdentity() {
|
||||||
|
return ssb.createIdentity();
|
||||||
|
});
|
||||||
|
tfrpc.register(async function appendMessage(id, message) {
|
||||||
|
return ssb.appendMessageWithIdentity(id, message);
|
||||||
|
});
|
||||||
|
tfrpc.register(function url() {
|
||||||
|
return core.url;
|
||||||
|
});
|
||||||
|
tfrpc.register(async function getUser() {
|
||||||
|
return core.user;
|
||||||
|
});
|
||||||
|
tfrpc.register(function getIdentities() {
|
||||||
|
return ssb.getIdentities();
|
||||||
|
});
|
||||||
|
tfrpc.register(async function databaseGet(key) {
|
||||||
|
return g_database ? g_database.get(key) : undefined;
|
||||||
|
});
|
||||||
|
tfrpc.register(async function databaseSet(key, value) {
|
||||||
|
return g_database ? g_database.set(key, value) : undefined;
|
||||||
|
});
|
||||||
|
tfrpc.register(async function databaseRemove(key, value) {
|
||||||
|
return g_database ? g_database.remove(key, value) : undefined;
|
||||||
|
});
|
||||||
|
tfrpc.register(async function sharedDatabaseGet(key) {
|
||||||
|
return g_shared_database ? g_shared_database.get(key) : undefined;
|
||||||
|
});
|
||||||
|
tfrpc.register(async function sharedDatabaseSet(key, value) {
|
||||||
|
return g_shared_database ? g_shared_database.set(key, value) : undefined;
|
||||||
|
});
|
||||||
|
tfrpc.register(async function sharedDatabaseRemove(key, value) {
|
||||||
|
return g_shared_database ? g_shared_database.remove(key, value) : undefined;
|
||||||
|
});
|
||||||
|
tfrpc.register(async function query(sql, args) {
|
||||||
|
let result = [];
|
||||||
|
await ssb.sqlAsync(sql, args, function callback(row) {
|
||||||
|
result.push(row);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
tfrpc.register(async function store_blob(blob) {
|
||||||
|
if (typeof(blob) == 'string') {
|
||||||
|
blob = utf8Encode(blob);
|
||||||
|
}
|
||||||
|
if (Array.isArray(blob)) {
|
||||||
|
blob = Uint8Array.from(blob);
|
||||||
|
}
|
||||||
|
return await ssb.blobStore(blob);
|
||||||
|
});
|
||||||
|
|
||||||
|
tfrpc.register(async function get_blob(id) {
|
||||||
|
return utf8Decode(await ssb.blobGet(id));
|
||||||
|
});
|
||||||
|
tfrpc.register(strava.refresh_token);
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
g_shared_database = await shared_database('state');
|
||||||
|
if (core.user.credentials?.session?.name) {
|
||||||
|
g_database = await database('state');
|
||||||
|
}
|
||||||
|
|
||||||
|
let attempt;
|
||||||
|
if (core.user.credentials?.session?.name) {
|
||||||
|
let shared_db = await shared_database('state');
|
||||||
|
attempt = await shared_db.get(core.user.credentials.session.name);
|
||||||
|
}
|
||||||
|
app.setDocument(utf8Decode(getFile('index.html')).replace('${data}', JSON.stringify({
|
||||||
|
attempt: attempt,
|
||||||
|
state: core.user?.credentials?.session?.name,
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
81
apps/gg/gpx.js
Normal file
81
apps/gg/gpx.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
function xml_parse(xml) {
|
||||||
|
let result;
|
||||||
|
let path = [];
|
||||||
|
let tag_begin;
|
||||||
|
let text_begin;
|
||||||
|
for (let i = 0; i < xml.length; i++) {
|
||||||
|
let c = xml.charAt(i);
|
||||||
|
if (!tag_begin && c == '<') {
|
||||||
|
if (i > text_begin && path.length) {
|
||||||
|
let value = xml.substring(text_begin, i);
|
||||||
|
if (!/^\s*$/.test(value)) {
|
||||||
|
path[path.length - 1].value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag_begin = i + 1;
|
||||||
|
} else if (tag_begin && c == '>') {
|
||||||
|
let tag = xml.substring(tag_begin, i).trim();
|
||||||
|
if (tag.startsWith('?') && tag.endsWith('?')) {
|
||||||
|
/* Ignore directives. */
|
||||||
|
} else if (tag.startsWith('/')) {
|
||||||
|
path.pop();
|
||||||
|
} else {
|
||||||
|
let parts = tag.split(' ');
|
||||||
|
let attributes = {};
|
||||||
|
for (let j = 1; j < parts.length; j++) {
|
||||||
|
let eq = parts[j].indexOf('=');
|
||||||
|
let value = parts[j].substring(eq + 1);
|
||||||
|
if (value.startsWith('"') && value.endsWith('"')) {
|
||||||
|
value = value.substring(1, value.length - 1);
|
||||||
|
}
|
||||||
|
attributes[parts[j].substring(0, eq)] = value;
|
||||||
|
}
|
||||||
|
let next = {name: parts[0], children: [], attributes: attributes};
|
||||||
|
if (path.length) {
|
||||||
|
path[path.length - 1].children.push(next);
|
||||||
|
} else {
|
||||||
|
result = next;
|
||||||
|
}
|
||||||
|
if (!tag.endsWith('/')) {
|
||||||
|
path.push(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag_begin = undefined;
|
||||||
|
text_begin = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function* xml_each(node, name) {
|
||||||
|
for (let child of node.children) {
|
||||||
|
if (child.name == name) {
|
||||||
|
yield child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function gpx_parse(xml) {
|
||||||
|
let result = {segments: []};
|
||||||
|
let tree = xml_parse(xml);
|
||||||
|
if (tree?.name == 'gpx') {
|
||||||
|
for (let trk of xml_each(tree, 'trk')) {
|
||||||
|
for (let trkseg of xml_each(trk, 'trkseg')) {
|
||||||
|
let segment = [];
|
||||||
|
for (let trkpt of xml_each(trkseg, 'trkpt')) {
|
||||||
|
segment.push({lat: parseFloat(trkpt.attributes.lat), lon: parseFloat(trkpt.attributes.lon)});
|
||||||
|
}
|
||||||
|
result.segments.push(segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let metadata of xml_each(tree, 'metadata')) {
|
||||||
|
for (let link of xml_each(metadata, 'link')) {
|
||||||
|
result.link = link.attributes.href;
|
||||||
|
}
|
||||||
|
for (let time of xml_each(metadata, 'time')) {
|
||||||
|
result.time = time.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
20
apps/gg/handler.js
Normal file
20
apps/gg/handler.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import * as strava from './strava.js';
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
let r = await strava.authorization_code(request.query.code);
|
||||||
|
print('state =', request.query.state);
|
||||||
|
print('body = ', r.body);
|
||||||
|
if (request.query.state && r.body) {
|
||||||
|
let shared_db = await shared_database('state');
|
||||||
|
await shared_db.set(request.query.state, r.body);
|
||||||
|
}
|
||||||
|
await respond({
|
||||||
|
data: r.body,
|
||||||
|
content_type: 'text/plain',
|
||||||
|
headers: {
|
||||||
|
Location: 'https://tildefriends.net/~cory/gg/',
|
||||||
|
},
|
||||||
|
status_code: 307,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
main();
|
16
apps/gg/index.html
Normal file
16
apps/gg/index.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html style="width: 100%; height: 100%; margin: 0; padding: 0">
|
||||||
|
<head>
|
||||||
|
<script>window.litDisableBundleWarning = true;</script>
|
||||||
|
<script>
|
||||||
|
let g_data = ${data};
|
||||||
|
</script>
|
||||||
|
<script src="script.js" type="module"></script>
|
||||||
|
<link rel="stylesheet" href="leaflet.css"/>
|
||||||
|
<script src="leaflet.js"></script>
|
||||||
|
</head>
|
||||||
|
<body style="color: #fff; display: flex; flex-flow: column; height: 100%; width: 100%; margin: 0; padding: 0">
|
||||||
|
<gg-app style="flex: 0 1 auto; overflow: scroll"></gg-app>
|
||||||
|
<div id="map" style="flex: 1 0"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
661
apps/gg/leaflet.css
Normal file
661
apps/gg/leaflet.css
Normal file
@ -0,0 +1,661 @@
|
|||||||
|
/* required styles */
|
||||||
|
|
||||||
|
.leaflet-pane,
|
||||||
|
.leaflet-tile,
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow,
|
||||||
|
.leaflet-tile-container,
|
||||||
|
.leaflet-pane > svg,
|
||||||
|
.leaflet-pane > canvas,
|
||||||
|
.leaflet-zoom-box,
|
||||||
|
.leaflet-image-layer,
|
||||||
|
.leaflet-layer {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.leaflet-container {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.leaflet-tile,
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
}
|
||||||
|
/* Prevents IE11 from highlighting tiles in blue */
|
||||||
|
.leaflet-tile::selection {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
|
||||||
|
.leaflet-safari .leaflet-tile {
|
||||||
|
image-rendering: -webkit-optimize-contrast;
|
||||||
|
}
|
||||||
|
/* hack that prevents hw layers "stretching" when loading new tiles */
|
||||||
|
.leaflet-safari .leaflet-tile-container {
|
||||||
|
width: 1600px;
|
||||||
|
height: 1600px;
|
||||||
|
-webkit-transform-origin: 0 0;
|
||||||
|
}
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
|
||||||
|
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
|
||||||
|
.leaflet-container .leaflet-overlay-pane svg {
|
||||||
|
max-width: none !important;
|
||||||
|
max-height: none !important;
|
||||||
|
}
|
||||||
|
.leaflet-container .leaflet-marker-pane img,
|
||||||
|
.leaflet-container .leaflet-shadow-pane img,
|
||||||
|
.leaflet-container .leaflet-tile-pane img,
|
||||||
|
.leaflet-container img.leaflet-image-layer,
|
||||||
|
.leaflet-container .leaflet-tile {
|
||||||
|
max-width: none !important;
|
||||||
|
max-height: none !important;
|
||||||
|
width: auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-container img.leaflet-tile {
|
||||||
|
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
|
||||||
|
mix-blend-mode: plus-lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-container.leaflet-touch-zoom {
|
||||||
|
-ms-touch-action: pan-x pan-y;
|
||||||
|
touch-action: pan-x pan-y;
|
||||||
|
}
|
||||||
|
.leaflet-container.leaflet-touch-drag {
|
||||||
|
-ms-touch-action: pinch-zoom;
|
||||||
|
/* Fallback for FF which doesn't support pinch-zoom */
|
||||||
|
touch-action: none;
|
||||||
|
touch-action: pinch-zoom;
|
||||||
|
}
|
||||||
|
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
|
||||||
|
-ms-touch-action: none;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
.leaflet-container {
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.leaflet-container a {
|
||||||
|
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
|
||||||
|
}
|
||||||
|
.leaflet-tile {
|
||||||
|
filter: inherit;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.leaflet-tile-loaded {
|
||||||
|
visibility: inherit;
|
||||||
|
}
|
||||||
|
.leaflet-zoom-box {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 800;
|
||||||
|
}
|
||||||
|
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
|
||||||
|
.leaflet-overlay-pane svg {
|
||||||
|
-moz-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-pane { z-index: 400; }
|
||||||
|
|
||||||
|
.leaflet-tile-pane { z-index: 200; }
|
||||||
|
.leaflet-overlay-pane { z-index: 400; }
|
||||||
|
.leaflet-shadow-pane { z-index: 500; }
|
||||||
|
.leaflet-marker-pane { z-index: 600; }
|
||||||
|
.leaflet-tooltip-pane { z-index: 650; }
|
||||||
|
.leaflet-popup-pane { z-index: 700; }
|
||||||
|
|
||||||
|
.leaflet-map-pane canvas { z-index: 100; }
|
||||||
|
.leaflet-map-pane svg { z-index: 200; }
|
||||||
|
|
||||||
|
.leaflet-vml-shape {
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
.lvml {
|
||||||
|
behavior: url(#default#VML);
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* control positioning */
|
||||||
|
|
||||||
|
.leaflet-control {
|
||||||
|
position: relative;
|
||||||
|
z-index: 800;
|
||||||
|
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.leaflet-top,
|
||||||
|
.leaflet-bottom {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.leaflet-top {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.leaflet-right {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.leaflet-bottom {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.leaflet-left {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.leaflet-control {
|
||||||
|
float: left;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.leaflet-right .leaflet-control {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.leaflet-top .leaflet-control {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.leaflet-bottom .leaflet-control {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.leaflet-left .leaflet-control {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.leaflet-right .leaflet-control {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* zoom and fade animations */
|
||||||
|
|
||||||
|
.leaflet-fade-anim .leaflet-popup {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transition: opacity 0.2s linear;
|
||||||
|
-moz-transition: opacity 0.2s linear;
|
||||||
|
transition: opacity 0.2s linear;
|
||||||
|
}
|
||||||
|
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.leaflet-zoom-animated {
|
||||||
|
-webkit-transform-origin: 0 0;
|
||||||
|
-ms-transform-origin: 0 0;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
}
|
||||||
|
svg.leaflet-zoom-animated {
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||||
|
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||||
|
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||||
|
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||||
|
}
|
||||||
|
.leaflet-zoom-anim .leaflet-tile,
|
||||||
|
.leaflet-pan-anim .leaflet-tile {
|
||||||
|
-webkit-transition: none;
|
||||||
|
-moz-transition: none;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-zoom-anim .leaflet-zoom-hide {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* cursors */
|
||||||
|
|
||||||
|
.leaflet-interactive {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.leaflet-grab {
|
||||||
|
cursor: -webkit-grab;
|
||||||
|
cursor: -moz-grab;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
.leaflet-crosshair,
|
||||||
|
.leaflet-crosshair .leaflet-interactive {
|
||||||
|
cursor: crosshair;
|
||||||
|
}
|
||||||
|
.leaflet-popup-pane,
|
||||||
|
.leaflet-control {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
.leaflet-dragging .leaflet-grab,
|
||||||
|
.leaflet-dragging .leaflet-grab .leaflet-interactive,
|
||||||
|
.leaflet-dragging .leaflet-marker-draggable {
|
||||||
|
cursor: move;
|
||||||
|
cursor: -webkit-grabbing;
|
||||||
|
cursor: -moz-grabbing;
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* marker & overlays interactivity */
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow,
|
||||||
|
.leaflet-image-layer,
|
||||||
|
.leaflet-pane > svg path,
|
||||||
|
.leaflet-tile-container {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-marker-icon.leaflet-interactive,
|
||||||
|
.leaflet-image-layer.leaflet-interactive,
|
||||||
|
.leaflet-pane > svg path.leaflet-interactive,
|
||||||
|
svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
|
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* visual tweaks */
|
||||||
|
|
||||||
|
.leaflet-container {
|
||||||
|
background: #ddd;
|
||||||
|
outline-offset: 1px;
|
||||||
|
}
|
||||||
|
.leaflet-container a {
|
||||||
|
color: #0078A8;
|
||||||
|
}
|
||||||
|
.leaflet-zoom-box {
|
||||||
|
border: 2px dotted #38f;
|
||||||
|
background: rgba(255,255,255,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* general typography */
|
||||||
|
.leaflet-container {
|
||||||
|
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* general toolbar styles */
|
||||||
|
|
||||||
|
.leaflet-bar {
|
||||||
|
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.leaflet-bar a {
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
line-height: 26px;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
.leaflet-bar a,
|
||||||
|
.leaflet-control-layers-toggle {
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.leaflet-bar a:hover,
|
||||||
|
.leaflet-bar a:focus {
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
.leaflet-bar a:first-child {
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
}
|
||||||
|
.leaflet-bar a:last-child {
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.leaflet-bar a.leaflet-disabled {
|
||||||
|
cursor: default;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-touch .leaflet-bar a {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-bar a:first-child {
|
||||||
|
border-top-left-radius: 2px;
|
||||||
|
border-top-right-radius: 2px;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-bar a:last-child {
|
||||||
|
border-bottom-left-radius: 2px;
|
||||||
|
border-bottom-right-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* zoom control */
|
||||||
|
|
||||||
|
.leaflet-control-zoom-in,
|
||||||
|
.leaflet-control-zoom-out {
|
||||||
|
font: bold 18px 'Lucida Console', Monaco, monospace;
|
||||||
|
text-indent: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* layers control */
|
||||||
|
|
||||||
|
.leaflet-control-layers {
|
||||||
|
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-toggle {
|
||||||
|
background-image: url(images/layers.png);
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
.leaflet-retina .leaflet-control-layers-toggle {
|
||||||
|
background-image: url(images/layers-2x.png);
|
||||||
|
background-size: 26px 26px;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-control-layers-toggle {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers .leaflet-control-layers-list,
|
||||||
|
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-expanded {
|
||||||
|
padding: 6px 10px 6px 6px;
|
||||||
|
color: #333;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-scrollbar {
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-selector {
|
||||||
|
margin-top: 2px;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-size: 1.08333em;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-separator {
|
||||||
|
height: 0;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
margin: 5px -10px 5px -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Default icon URLs */
|
||||||
|
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
|
||||||
|
background-image: url(images/marker-icon.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* attribution and scale controls */
|
||||||
|
|
||||||
|
.leaflet-container .leaflet-control-attribution {
|
||||||
|
background: #fff;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.leaflet-control-attribution,
|
||||||
|
.leaflet-control-scale-line {
|
||||||
|
padding: 0 5px;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.leaflet-control-attribution a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.leaflet-control-attribution a:hover,
|
||||||
|
.leaflet-control-attribution a:focus {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.leaflet-attribution-flag {
|
||||||
|
display: inline !important;
|
||||||
|
vertical-align: baseline !important;
|
||||||
|
width: 1em;
|
||||||
|
height: 0.6669em;
|
||||||
|
}
|
||||||
|
.leaflet-left .leaflet-control-scale {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.leaflet-bottom .leaflet-control-scale {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.leaflet-control-scale-line {
|
||||||
|
border: 2px solid #777;
|
||||||
|
border-top: none;
|
||||||
|
line-height: 1.1;
|
||||||
|
padding: 2px 5px 1px;
|
||||||
|
white-space: nowrap;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
text-shadow: 1px 1px #fff;
|
||||||
|
}
|
||||||
|
.leaflet-control-scale-line:not(:first-child) {
|
||||||
|
border-top: 2px solid #777;
|
||||||
|
border-bottom: none;
|
||||||
|
margin-top: -2px;
|
||||||
|
}
|
||||||
|
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
||||||
|
border-bottom: 2px solid #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-touch .leaflet-control-attribution,
|
||||||
|
.leaflet-touch .leaflet-control-layers,
|
||||||
|
.leaflet-touch .leaflet-bar {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-control-layers,
|
||||||
|
.leaflet-touch .leaflet-bar {
|
||||||
|
border: 2px solid rgba(0,0,0,0.2);
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* popup */
|
||||||
|
|
||||||
|
.leaflet-popup {
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.leaflet-popup-content-wrapper {
|
||||||
|
padding: 1px;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
.leaflet-popup-content {
|
||||||
|
margin: 13px 24px 13px 20px;
|
||||||
|
line-height: 1.3;
|
||||||
|
font-size: 13px;
|
||||||
|
font-size: 1.08333em;
|
||||||
|
min-height: 1px;
|
||||||
|
}
|
||||||
|
.leaflet-popup-content p {
|
||||||
|
margin: 17px 0;
|
||||||
|
margin: 1.3em 0;
|
||||||
|
}
|
||||||
|
.leaflet-popup-tip-container {
|
||||||
|
width: 40px;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
margin-top: -1px;
|
||||||
|
margin-left: -20px;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.leaflet-popup-tip {
|
||||||
|
width: 17px;
|
||||||
|
height: 17px;
|
||||||
|
padding: 1px;
|
||||||
|
|
||||||
|
margin: -10px auto 0;
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
-moz-transform: rotate(45deg);
|
||||||
|
-ms-transform: rotate(45deg);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
.leaflet-popup-content-wrapper,
|
||||||
|
.leaflet-popup-tip {
|
||||||
|
background: white;
|
||||||
|
color: #333;
|
||||||
|
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
.leaflet-container a.leaflet-popup-close-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
border: none;
|
||||||
|
text-align: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
font: 16px/24px Tahoma, Verdana, sans-serif;
|
||||||
|
color: #757575;
|
||||||
|
text-decoration: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.leaflet-container a.leaflet-popup-close-button:hover,
|
||||||
|
.leaflet-container a.leaflet-popup-close-button:focus {
|
||||||
|
color: #585858;
|
||||||
|
}
|
||||||
|
.leaflet-popup-scrolled {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-oldie .leaflet-popup-content-wrapper {
|
||||||
|
-ms-zoom: 1;
|
||||||
|
}
|
||||||
|
.leaflet-oldie .leaflet-popup-tip {
|
||||||
|
width: 24px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
||||||
|
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-oldie .leaflet-control-zoom,
|
||||||
|
.leaflet-oldie .leaflet-control-layers,
|
||||||
|
.leaflet-oldie .leaflet-popup-content-wrapper,
|
||||||
|
.leaflet-oldie .leaflet-popup-tip {
|
||||||
|
border: 1px solid #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* div icon */
|
||||||
|
|
||||||
|
.leaflet-div-icon {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Tooltip */
|
||||||
|
/* Base styles for the element that has a tooltip */
|
||||||
|
.leaflet-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
padding: 6px;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #fff;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #222;
|
||||||
|
white-space: nowrap;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
.leaflet-tooltip.leaflet-interactive {
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-top:before,
|
||||||
|
.leaflet-tooltip-bottom:before,
|
||||||
|
.leaflet-tooltip-left:before,
|
||||||
|
.leaflet-tooltip-right:before {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
border: 6px solid transparent;
|
||||||
|
background: transparent;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Directions */
|
||||||
|
|
||||||
|
.leaflet-tooltip-bottom {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-top {
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-bottom:before,
|
||||||
|
.leaflet-tooltip-top:before {
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-top:before {
|
||||||
|
bottom: 0;
|
||||||
|
margin-bottom: -12px;
|
||||||
|
border-top-color: #fff;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-bottom:before {
|
||||||
|
top: 0;
|
||||||
|
margin-top: -12px;
|
||||||
|
margin-left: -6px;
|
||||||
|
border-bottom-color: #fff;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-left {
|
||||||
|
margin-left: -6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-right {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-left:before,
|
||||||
|
.leaflet-tooltip-right:before {
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-left:before {
|
||||||
|
right: 0;
|
||||||
|
margin-right: -12px;
|
||||||
|
border-left-color: #fff;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-right:before {
|
||||||
|
left: 0;
|
||||||
|
margin-left: -12px;
|
||||||
|
border-right-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Printing */
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
/* Prevent printers from removing background-images of controls. */
|
||||||
|
.leaflet-control {
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
}
|
6
apps/gg/leaflet.js
Normal file
6
apps/gg/leaflet.js
Normal file
File diff suppressed because one or more lines are too long
126
apps/gg/lit-all.min.js
vendored
Normal file
126
apps/gg/lit-all.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
apps/gg/lit-all.min.js.map
Normal file
1
apps/gg/lit-all.min.js.map
Normal file
File diff suppressed because one or more lines are too long
158
apps/gg/polyline.js
Normal file
158
apps/gg/polyline.js
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* Based off of [the offical Google document](https://developers.google.com/maps/documentation/utilities/polylinealgorithm)
|
||||||
|
*
|
||||||
|
* Some parts from [this implementation](http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/PolylineEncoder.js)
|
||||||
|
* by [Mark McClure](http://facstaff.unca.edu/mcmcclur/)
|
||||||
|
*
|
||||||
|
* @module polyline
|
||||||
|
*/
|
||||||
|
|
||||||
|
var polyline = {};
|
||||||
|
|
||||||
|
function py2_round(value) {
|
||||||
|
// Google's polyline algorithm uses the same rounding strategy as Python 2, which is different from JS for negative values
|
||||||
|
return Math.floor(Math.abs(value) + 0.5) * (value >= 0 ? 1 : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function encode(current, previous, factor) {
|
||||||
|
current = py2_round(current * factor);
|
||||||
|
previous = py2_round(previous * factor);
|
||||||
|
var coordinate = (current - previous) * 2;
|
||||||
|
if (coordinate < 0) {
|
||||||
|
coordinate = -coordinate - 1
|
||||||
|
}
|
||||||
|
var output = '';
|
||||||
|
while (coordinate >= 0x20) {
|
||||||
|
output += String.fromCharCode((0x20 | (coordinate & 0x1f)) + 63);
|
||||||
|
coordinate /= 32;
|
||||||
|
}
|
||||||
|
output += String.fromCharCode((coordinate | 0) + 63);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes to a [latitude, longitude] coordinates array.
|
||||||
|
*
|
||||||
|
* This is adapted from the implementation in Project-OSRM.
|
||||||
|
*
|
||||||
|
* @param {String} str
|
||||||
|
* @param {Number} precision
|
||||||
|
* @returns {Array}
|
||||||
|
*
|
||||||
|
* @see https://github.com/Project-OSRM/osrm-frontend/blob/master/WebContent/routing/OSRM.RoutingGeometry.js
|
||||||
|
*/
|
||||||
|
polyline.decode = function(str, precision) {
|
||||||
|
var index = 0,
|
||||||
|
lat = 0,
|
||||||
|
lng = 0,
|
||||||
|
coordinates = [],
|
||||||
|
shift = 0,
|
||||||
|
result = 0,
|
||||||
|
byte = null,
|
||||||
|
latitude_change,
|
||||||
|
longitude_change,
|
||||||
|
factor = Math.pow(10, Number.isInteger(precision) ? precision : 5);
|
||||||
|
|
||||||
|
// Coordinates have variable length when encoded, so just keep
|
||||||
|
// track of whether we've hit the end of the string. In each
|
||||||
|
// loop iteration, a single coordinate is decoded.
|
||||||
|
while (index < str.length) {
|
||||||
|
|
||||||
|
// Reset shift, result, and byte
|
||||||
|
byte = null;
|
||||||
|
shift = 1;
|
||||||
|
result = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
byte = str.charCodeAt(index++) - 63;
|
||||||
|
result += (byte & 0x1f) * shift;
|
||||||
|
shift *= 32;
|
||||||
|
} while (byte >= 0x20);
|
||||||
|
|
||||||
|
latitude_change = (result & 1) ? ((-result - 1) / 2) : (result / 2);
|
||||||
|
|
||||||
|
shift = 1;
|
||||||
|
result = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
byte = str.charCodeAt(index++) - 63;
|
||||||
|
result += (byte & 0x1f) * shift;
|
||||||
|
shift *= 32;
|
||||||
|
} while (byte >= 0x20);
|
||||||
|
|
||||||
|
longitude_change = (result & 1) ? ((-result - 1) / 2) : (result / 2);
|
||||||
|
|
||||||
|
lat += latitude_change;
|
||||||
|
lng += longitude_change;
|
||||||
|
|
||||||
|
coordinates.push([lat / factor, lng / factor]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return coordinates;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes the given [latitude, longitude] coordinates array.
|
||||||
|
*
|
||||||
|
* @param {Array.<Array.<Number>>} coordinates
|
||||||
|
* @param {Number} precision
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
polyline.encode = function(coordinates, precision) {
|
||||||
|
if (!coordinates.length) { return ''; }
|
||||||
|
|
||||||
|
var factor = Math.pow(10, Number.isInteger(precision) ? precision : 5),
|
||||||
|
output = encode(coordinates[0][0], 0, factor) + encode(coordinates[0][1], 0, factor);
|
||||||
|
|
||||||
|
for (var i = 1; i < coordinates.length; i++) {
|
||||||
|
var a = coordinates[i], b = coordinates[i - 1];
|
||||||
|
output += encode(a[0], b[0], factor);
|
||||||
|
output += encode(a[1], b[1], factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
function flipped(coords) {
|
||||||
|
var flipped = [];
|
||||||
|
for (var i = 0; i < coords.length; i++) {
|
||||||
|
var coord = coords[i].slice();
|
||||||
|
flipped.push([coord[1], coord[0]]);
|
||||||
|
}
|
||||||
|
return flipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a GeoJSON LineString feature/geometry.
|
||||||
|
*
|
||||||
|
* @param {Object} geojson
|
||||||
|
* @param {Number} precision
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
polyline.fromGeoJSON = function(geojson, precision) {
|
||||||
|
if (geojson && geojson.type === 'Feature') {
|
||||||
|
geojson = geojson.geometry;
|
||||||
|
}
|
||||||
|
if (!geojson || geojson.type !== 'LineString') {
|
||||||
|
throw new Error('Input must be a GeoJSON LineString');
|
||||||
|
}
|
||||||
|
return polyline.encode(flipped(geojson.coordinates), precision);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes to a GeoJSON LineString geometry.
|
||||||
|
*
|
||||||
|
* @param {String} str
|
||||||
|
* @param {Number} precision
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
polyline.toGeoJSON = function(str, precision) {
|
||||||
|
var coords = polyline.decode(str, precision);
|
||||||
|
return {
|
||||||
|
type: 'LineString',
|
||||||
|
coordinates: flipped(coords)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let polyline_decode = polyline.decode;
|
||||||
|
export { polyline_decode as decode };
|
530
apps/gg/script.js
Normal file
530
apps/gg/script.js
Normal file
@ -0,0 +1,530 @@
|
|||||||
|
import {LitElement, html, unsafeHTML, css, guard, until} from './lit-all.min.js';
|
||||||
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
|
import * as polyline from './polyline.js';
|
||||||
|
import {gpx_parse} from './gpx.js';
|
||||||
|
|
||||||
|
const k_client_id = '28276';
|
||||||
|
const k_redirect_url = 'https://tildefriends.net/~cory/gg/login';
|
||||||
|
|
||||||
|
const k_color_snow = [128, 128, 255, 255];
|
||||||
|
const k_color_ice = [160, 160, 255, 255];
|
||||||
|
const k_color_water = [0, 0, 255, 255];
|
||||||
|
const k_color_dirt = [128, 129, 130, 255];
|
||||||
|
const k_color_pavement = [32, 32, 32, 255];
|
||||||
|
const k_color_grass = [0, 255, 0, 255];
|
||||||
|
const k_color_default = [128, 128, 128, 255];
|
||||||
|
|
||||||
|
class GgAppElement extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
user: {type: Object},
|
||||||
|
strava: {type: Object},
|
||||||
|
activities: {type: Array},
|
||||||
|
activity: {type: Object},
|
||||||
|
world: {type: Object},
|
||||||
|
id: {type: String},
|
||||||
|
status: {type: Object},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.activities = [];
|
||||||
|
this.activity = {};
|
||||||
|
this.loaded_activities = [];
|
||||||
|
this.strava = {};
|
||||||
|
this.min_lat = Number.MAX_VALUE;
|
||||||
|
this.min_lon = Number.MAX_VALUE;
|
||||||
|
this.max_lat = -Number.MAX_VALUE;
|
||||||
|
this.max_lon = -Number.MAX_VALUE;
|
||||||
|
this.status = undefined;
|
||||||
|
this.load().catch(function(e) {
|
||||||
|
console.log('load error', e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
console.log('load');
|
||||||
|
this.user = await tfrpc.rpc.getUser();
|
||||||
|
try {
|
||||||
|
await this.update_credentials();
|
||||||
|
} catch (e) {
|
||||||
|
console.log('update_credentials failed', e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.update_activities();
|
||||||
|
} catch (e) {
|
||||||
|
console.log('update_activities failed', e);
|
||||||
|
}
|
||||||
|
await this.acquire_ssb_identity();
|
||||||
|
if (this.id && this.activities?.length) {
|
||||||
|
await this.sync_activities();
|
||||||
|
}
|
||||||
|
await this.get_activities_from_ssb();
|
||||||
|
}
|
||||||
|
|
||||||
|
async get_activities_from_ssb() {
|
||||||
|
this.status = {text: 'loading activities'};
|
||||||
|
this.loaded_activities = [];
|
||||||
|
let blob_ids = await tfrpc.rpc.query(`
|
||||||
|
SELECT json_extract(mention.value, '$.link') AS blob_id
|
||||||
|
FROM messages_fts('"gg-activity"')
|
||||||
|
JOIN messages ON messages.rowid = messages_fts.rowid,
|
||||||
|
json_each(messages.content, '$.mentions') as mention
|
||||||
|
WHERE json_extract(messages.content, '$.type') = 'gg-activity' AND
|
||||||
|
json_extract(mention.value, '$.name') = 'activity_data'
|
||||||
|
ORDER BY messages.timestamp DESC
|
||||||
|
`, []);
|
||||||
|
for (let [index, row] of blob_ids.entries()) {
|
||||||
|
this.status = {text: 'loading activity data', value: index, max: blob_ids.length};
|
||||||
|
let blob = await tfrpc.rpc.get_blob(row.blob_id);
|
||||||
|
try {
|
||||||
|
this.loaded_activities.push(JSON.parse(blob));
|
||||||
|
} catch {
|
||||||
|
this.loaded_activities.push(gpx_parse(blob));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.status = undefined;
|
||||||
|
this.update_map();
|
||||||
|
}
|
||||||
|
|
||||||
|
async sync_activities() {
|
||||||
|
let ids = this.activities.map(x => `https://www.strava.com/activities/${x.id}`);
|
||||||
|
let missing = await tfrpc.rpc.query(`
|
||||||
|
WITH my_activities AS (
|
||||||
|
SELECT json_extract(mention.value, '$.link') AS url
|
||||||
|
FROM messages, json_each(messages.content, '$.mentions') AS mention
|
||||||
|
WHERE
|
||||||
|
author = ? AND
|
||||||
|
json_extract(messages.content, '$.type') = 'gg-activity' AND
|
||||||
|
json_extract(mention.value, '$.name') = 'activity_url')
|
||||||
|
SELECT from_strava.value FROM json_each(?) AS from_strava
|
||||||
|
LEFT OUTER JOIN my_activities ON from_strava.value = my_activities.url
|
||||||
|
WHERE my_activities.url IS NULL
|
||||||
|
`, [this.id, JSON.stringify(ids)]);
|
||||||
|
console.log('missing = ', missing);
|
||||||
|
for (let [index, row] of missing.entries()) {
|
||||||
|
this.status = {text: 'syncing from strava', value: index, max: missing.length};
|
||||||
|
let url = row.value;
|
||||||
|
let id = url.match(/.*\/(\d+)/)[1];
|
||||||
|
let response = await fetch(`https://www.strava.com/api/v3/activities/${id}`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${this.strava.access_token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let activity = await response.json();
|
||||||
|
let blob_id = await tfrpc.rpc.store_blob(JSON.stringify(activity));
|
||||||
|
let message = {
|
||||||
|
type: 'gg-activity',
|
||||||
|
mentions: [
|
||||||
|
{
|
||||||
|
link: url,
|
||||||
|
name: 'activity_url',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
link: blob_id,
|
||||||
|
name: 'activity_data',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
};
|
||||||
|
await tfrpc.rpc.appendMessage(this.id, message);
|
||||||
|
}
|
||||||
|
this.status = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async acquire_ssb_identity() {
|
||||||
|
let user = await tfrpc.rpc.getUser();
|
||||||
|
if (!user?.credentials?.session?.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let ids = await tfrpc.rpc.getIdentities();
|
||||||
|
let players = ids.length ? (await tfrpc.rpc.query(`
|
||||||
|
SELECT author FROM messages JOIN json_each(?) ON messages.author = json_each.value
|
||||||
|
WHERE
|
||||||
|
json_extract(messages.content, '$.type') = 'gg-player' AND
|
||||||
|
json_extract(messages.content, '$.active')
|
||||||
|
ORDER BY timestamp DESC limit 1
|
||||||
|
`, [JSON.stringify(ids)])).map(row => row.author) : [];
|
||||||
|
if (!players.length) {
|
||||||
|
this.id = await tfrpc.rpc.createIdentity();
|
||||||
|
if (this.id) {
|
||||||
|
await tfrpc.rpc.appendMessage(this.id, {
|
||||||
|
type: 'gg-player',
|
||||||
|
active: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
players.sort();
|
||||||
|
this.id = players[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async update_credentials() {
|
||||||
|
let name = this.user?.credentials?.session?.name;
|
||||||
|
if (!name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let shared = await tfrpc.rpc.sharedDatabaseGet(name);
|
||||||
|
if (shared) {
|
||||||
|
await tfrpc.rpc.databaseSet('strava', shared);
|
||||||
|
await tfrpc.rpc.sharedDatabaseRemove(name);
|
||||||
|
}
|
||||||
|
this.strava = JSON.parse(await tfrpc.rpc.databaseGet('strava') || '{}');
|
||||||
|
if (new Date().valueOf() / 1000 > this.strava.expires_at) {
|
||||||
|
console.log('this looks expired', new Date().valueOf() / 1000, '>', this.strava.expires_at);
|
||||||
|
let x = await tfrpc.rpc.refresh_token(this.strava);
|
||||||
|
if (x) {
|
||||||
|
this.strava = x;
|
||||||
|
await tfrpc.rpc.databaseSet('strava', JSON.stringify(x));
|
||||||
|
} else {
|
||||||
|
this.strava = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async update_activities() {
|
||||||
|
if (this?.strava?.access_token) {
|
||||||
|
let response = await fetch('https://www.strava.com/api/v3/athlete/activities', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${this.strava.access_token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.activities = await response.json();
|
||||||
|
this.activities.sort((a, b) => (a.id - b.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
color_to_emoji(color) {
|
||||||
|
const k_map = [
|
||||||
|
[k_color_snow, '⬜'],
|
||||||
|
[k_color_ice, '🟦'],
|
||||||
|
[k_color_water, '🟦'],
|
||||||
|
[k_color_dirt, '🟫'],
|
||||||
|
[k_color_pavement, '⬛'],
|
||||||
|
[k_color_grass, '🟩'],
|
||||||
|
[k_color_default, '🟧'],
|
||||||
|
];
|
||||||
|
for (let m of k_map) {
|
||||||
|
if (m[0][0] == color[0] &&
|
||||||
|
m[0][1] == color[1] &&
|
||||||
|
m[0][2] == color[2] &&
|
||||||
|
m[0][3] == color[3]) {
|
||||||
|
return m[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activity_bounds(activity) {
|
||||||
|
let min_lat = Number.MAX_VALUE;
|
||||||
|
let min_lon = Number.MAX_VALUE;
|
||||||
|
let max_lat = -Number.MAX_VALUE;
|
||||||
|
let max_lon = -Number.MAX_VALUE;
|
||||||
|
if (activity?.map?.polyline) {
|
||||||
|
for (let pt of polyline.decode(activity.map.polyline)) {
|
||||||
|
min_lat = Math.min(min_lat, pt[0]);
|
||||||
|
min_lon = Math.min(min_lon, pt[1]);
|
||||||
|
max_lat = Math.max(max_lat, pt[0]);
|
||||||
|
max_lon = Math.max(max_lon, pt[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (activity?.segments) {
|
||||||
|
for (let segment of activity.segments) {
|
||||||
|
for (let pt of segment) {
|
||||||
|
min_lat = Math.min(min_lat, pt.lat);
|
||||||
|
min_lon = Math.min(min_lon, pt.lon);
|
||||||
|
max_lat = Math.max(max_lat, pt.lat);
|
||||||
|
max_lon = Math.max(max_lon, pt.lon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
min: {
|
||||||
|
lat: min_lat,
|
||||||
|
lng: min_lon,
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
lat: max_lat,
|
||||||
|
lng: max_lon,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async update_map() {
|
||||||
|
if (!this.leaflet) {
|
||||||
|
this.leaflet = L.map('map', {attributionControl: false, maxZoom: 16, bounceAtZoomLimits: false});
|
||||||
|
}
|
||||||
|
let self = this;
|
||||||
|
let grid_layer = L.GridLayer.extend({
|
||||||
|
createTile: function(coords) {
|
||||||
|
var tile = L.DomUtil.create('canvas', 'leaflet-tile');
|
||||||
|
var size = this.getTileSize();
|
||||||
|
tile.width = size.x;
|
||||||
|
tile.height = size.y;
|
||||||
|
var context = tile.getContext('2d');
|
||||||
|
context.font = '10pt sans';
|
||||||
|
let bounds = this._tileCoordsToBounds(coords);
|
||||||
|
let degrees = 360.0 / (2 ** coords.z);
|
||||||
|
let ul = bounds.getNorthWest();
|
||||||
|
let lr = bounds.getSouthEast();
|
||||||
|
//context.fillText(JSON.stringify(coords), 0, 12);
|
||||||
|
//context.fillText(`${Math.round(ul.lat * 100) / 100} ${Math.round(ul.lng * 100) / 100}`, 0, 24);
|
||||||
|
//context.fillText(`${Math.round(lr.lat * 100) / 100} ${Math.round(lr.lng * 100) / 100}`, 0, 36);
|
||||||
|
|
||||||
|
let mini = document.createElement('canvas');
|
||||||
|
mini.width = Math.floor(size.x / 16.0);
|
||||||
|
mini.height = Math.floor(size.y / 16.0);
|
||||||
|
let mini_context = mini.getContext('2d');
|
||||||
|
let image_data = context.getImageData(0, 0, mini.width, mini.height);
|
||||||
|
for (let activity of self.loaded_activities) {
|
||||||
|
self.draw_activity_to_tile(image_data, mini.width, mini.height, ul, lr, activity);
|
||||||
|
}
|
||||||
|
//mini_context.putImageData(image_data, 0, 0);
|
||||||
|
for (let x = 0; x < mini.width; x++) {
|
||||||
|
for (let y = 0; y < mini.height; y++) {
|
||||||
|
let start = (y * mini.width + x) * 4;
|
||||||
|
let pixel = self.color_to_emoji(image_data.data.slice(start, start + 4));
|
||||||
|
if (pixel) {
|
||||||
|
context.fillText(pixel, x * size.x / mini.width, y * size.y / mini.height + 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (this.grid_layer) {
|
||||||
|
this.grid_layer.redraw();
|
||||||
|
} else {
|
||||||
|
this.grid_layer = new grid_layer();
|
||||||
|
this.grid_layer.addTo(this.leaflet);
|
||||||
|
}
|
||||||
|
for (let activity of this.loaded_activities) {
|
||||||
|
let bounds = this.activity_bounds(activity);
|
||||||
|
this.min_lat = Math.min(this.min_lat, bounds.min.lat);
|
||||||
|
this.min_lon = Math.min(this.min_lon, bounds.min.lng);
|
||||||
|
this.max_lat = Math.max(this.max_lat, bounds.max.lat);
|
||||||
|
this.max_lon = Math.max(this.max_lon, bounds.max.lng);
|
||||||
|
}
|
||||||
|
this.leaflet.fitBounds([
|
||||||
|
[this.min_lat, this.min_lon],
|
||||||
|
[this.max_lat, this.max_lon],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
activity_to_color(activity) {
|
||||||
|
let color = [0, 0, 0, 255];
|
||||||
|
switch (activity.sport_type) {
|
||||||
|
/* Implies snow. */
|
||||||
|
case 'AlpineSki':
|
||||||
|
case 'BackcountrySki':
|
||||||
|
case 'NordicSki':
|
||||||
|
case 'Snowshoe':
|
||||||
|
case 'Snowboard':
|
||||||
|
color = k_color_snow;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Implies ice. */
|
||||||
|
case 'IceSkate':
|
||||||
|
case 'InlineSkate':
|
||||||
|
color = k_color_ice;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Implies water. */
|
||||||
|
case 'Canoeing':
|
||||||
|
case 'Kayaking':
|
||||||
|
case 'Kitesurf':
|
||||||
|
case 'Rowing':
|
||||||
|
case 'Sail':
|
||||||
|
case 'StandUpPaddling':
|
||||||
|
case 'Surfing':
|
||||||
|
case 'Swim':
|
||||||
|
case 'Windsurf':
|
||||||
|
color = k_color_water;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Implies dirt. */
|
||||||
|
case 'EMountainBikeRide':
|
||||||
|
case 'Hike':
|
||||||
|
case 'MountainBikeRide':
|
||||||
|
case 'RockClimbing':
|
||||||
|
case 'TrailRun':
|
||||||
|
color = k_color_dirt;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Implies pavement. */
|
||||||
|
case 'EBikeRide':
|
||||||
|
case 'GravelRide':
|
||||||
|
case 'Handcycle':
|
||||||
|
case 'Ride':
|
||||||
|
case 'RollerSki':
|
||||||
|
case 'Run':
|
||||||
|
case 'Skateboard':
|
||||||
|
case 'Badminton':
|
||||||
|
case 'Tennis':
|
||||||
|
case 'Velomobile':
|
||||||
|
case 'Walk':
|
||||||
|
case 'Wheelchair':
|
||||||
|
color = k_color_pavement;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Grass, maybe? */
|
||||||
|
case 'Golf':
|
||||||
|
case 'Soccer':
|
||||||
|
case 'Squash':
|
||||||
|
color = k_color_grass;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Crossfit,
|
||||||
|
// Elliptical
|
||||||
|
// HighIntensityIntervalTraining
|
||||||
|
// Pickleball
|
||||||
|
// Pilates
|
||||||
|
// Racquetball
|
||||||
|
// StairStepper
|
||||||
|
// TableTennis,
|
||||||
|
// VirtualRide
|
||||||
|
// VirtualRow
|
||||||
|
// VirtualRun
|
||||||
|
// WeightTraining
|
||||||
|
// Workout
|
||||||
|
// Yoga
|
||||||
|
default:
|
||||||
|
color = k_color_default;
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
line(image_data, x0, y0, x1, y1, value) {
|
||||||
|
/* <3 https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm */
|
||||||
|
let dx = Math.abs(x1 - x0);
|
||||||
|
let sx = x0 < x1 ? 1 : -1;
|
||||||
|
let dy = -Math.abs(y1 - y0);
|
||||||
|
let sy = y0 < y1 ? 1 : -1;
|
||||||
|
let error = dx + dy;
|
||||||
|
while (true) {
|
||||||
|
if (x0 >= 0 && y0 >= 0 && x0 < image_data.width && y0 < image_data.height) {
|
||||||
|
let base = (y0 * image_data.width + x0) * 4;
|
||||||
|
image_data.data[base + 0] = value[0];
|
||||||
|
image_data.data[base + 1] = value[1];
|
||||||
|
image_data.data[base + 2] = value[2];
|
||||||
|
image_data.data[base + 3] = value[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x0 == x1 && y0 == y1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let e2 = 2 * error;
|
||||||
|
if (e2 >= dy) {
|
||||||
|
if (x0 == x1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
error += dy;
|
||||||
|
x0 = Math.round(x0 + sx);
|
||||||
|
}
|
||||||
|
if (e2 <= dx) {
|
||||||
|
if (y0 == y1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
error += dx;
|
||||||
|
y0 = Math.round(y0 + sy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_activity_to_tile(image_data, width, height, ul, lr, activity) {
|
||||||
|
let color = this.activity_to_color(activity);
|
||||||
|
if (activity?.map?.polyline) {
|
||||||
|
let last;
|
||||||
|
for (let pt of polyline.decode(activity.map.polyline)) {
|
||||||
|
let px = [
|
||||||
|
Math.floor(width * (pt[1] - ul.lng) / (lr.lng - ul.lng)),
|
||||||
|
Math.floor(height * (pt[0] - ul.lat) / (lr.lat - ul.lat)),
|
||||||
|
];
|
||||||
|
if (last) {
|
||||||
|
this.line(image_data, last[0], last[1], px[0], px[1], color);
|
||||||
|
}
|
||||||
|
last = px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (activity?.segments) {
|
||||||
|
for (let segment of activity.segments) {
|
||||||
|
let last;
|
||||||
|
for (let pt of segment) {
|
||||||
|
let px = [
|
||||||
|
Math.floor(width * (pt.lon - ul.lng) / (lr.lng - ul.lng)),
|
||||||
|
Math.floor(height * (pt.lat - ul.lat) / (lr.lat - ul.lat)),
|
||||||
|
];
|
||||||
|
if (last) {
|
||||||
|
this.line(image_data, last[0], last[1], px[0], px[1], color);
|
||||||
|
}
|
||||||
|
last = px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async on_upload(event) {
|
||||||
|
try {
|
||||||
|
let file = event.srcElement.files[0];
|
||||||
|
let xml = await file.text();
|
||||||
|
let gpx = gpx_parse(xml);
|
||||||
|
let blob_id = await tfrpc.rpc.store_blob(xml);
|
||||||
|
console.log('blob_id = ', blob_id);
|
||||||
|
console.log(gpx);
|
||||||
|
let message = {
|
||||||
|
type: 'gg-activity',
|
||||||
|
mentions: [
|
||||||
|
{
|
||||||
|
link: `https://${gpx.link}/activity/${gpx.time}`,
|
||||||
|
name: 'activity_url',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
link: blob_id,
|
||||||
|
name: 'activity_data',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
};
|
||||||
|
console.log('id =', this.id, 'message = ', message);
|
||||||
|
let id = await tfrpc.rpc.appendMessage(this.id, message);
|
||||||
|
console.log('appended message', id);
|
||||||
|
alert('Activity uploaded.');
|
||||||
|
await this.get_activities_from_ssb();
|
||||||
|
} catch (e) {
|
||||||
|
alert(`Error: ${JSON.stringify(e, null, 2)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
upload() {
|
||||||
|
let input = document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
input.onchange = (event) => this.on_upload(event);
|
||||||
|
input.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.user?.credentials?.session?.name) {
|
||||||
|
return html`<div>Please <a target="_top" href="/login">login</a> to Tilde Friends, first.</div>`;
|
||||||
|
}
|
||||||
|
if (!this.strava?.access_token) {
|
||||||
|
let strava_url = `https://www.strava.com/oauth/authorize?client_id=${k_client_id}&redirect_uri=${k_redirect_url}&response_type=code&approval_prompt=auto&scope=activity%3Aread&state=${g_data.state}`;
|
||||||
|
return html`
|
||||||
|
<div style="display: flex; flex-direction: row; align-items: center; gap: 1em; width: 100%">
|
||||||
|
<div style="flex: 1 1">Please <a target="_top" href=${strava_url}>login</a> to Strava.</div>
|
||||||
|
<span style="font-size: xx-small; flex: 1 1; word-break: break-all">${this.id}</span>
|
||||||
|
<input type="button" value="📁" @click=${this.upload}></input>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div>
|
||||||
|
<div style="display: flex; flex-direction: row; align-items: center; gap: 1em; width: 100%">
|
||||||
|
<h1>Welcome, ${this.user.credentials.session.name}</h1>
|
||||||
|
<span style="font-size: xx-small; flex: 1 1; word-break: break-all">${this.id}</span>
|
||||||
|
<input type="button" value="📁" @click=${this.upload}></input>
|
||||||
|
</div>
|
||||||
|
<h3 ?hidden=${!this.status?.text}>${this.status?.text} <progress ?hidden=${!this.status?.max} value=${this.status?.value} max=${this.status?.max}>${this.status?.value}</progress></h3>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define('gg-app', GgAppElement);
|
20
apps/gg/strava.js
Normal file
20
apps/gg/strava.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
const k_client_id = '28276';
|
||||||
|
const k_client_secret = '3123f1f5afe132d9731111066d1d17bdb22ef27e';
|
||||||
|
const k_access_token = 'f753e77764c26252bd2d80e7c5cc17ace51a8864';
|
||||||
|
const k_refresh_token = 'f58d8e1b5a3ec3bf96e681589d5014f9a294f5a4';
|
||||||
|
const k_redirect_url = 'https://tildefriends.net/~cory/gg/login';
|
||||||
|
|
||||||
|
export async function refresh_token(token) {
|
||||||
|
let r = await fetch('https://www.strava.com/api/v3/oauth/token', {
|
||||||
|
method: 'POST',
|
||||||
|
body: `client_id=${k_client_id}&client_secret=${k_client_secret}&refresh_token=${token.refresh_token}&grant_type=refresh_token`,
|
||||||
|
});
|
||||||
|
return r?.body ? JSON.parse(utf8Decode(r.body)) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function authorization_code(code) {
|
||||||
|
return await fetch('https://www.strava.com/api/v3/oauth/token', {
|
||||||
|
method: 'POST',
|
||||||
|
body: `client_id=${k_client_id}&client_secret=${k_client_secret}&code=${code}&grant_type=authorization_code`,
|
||||||
|
});
|
||||||
|
}
|
4
apps/sneaker.json
Normal file
4
apps/sneaker.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"type": "tildefriends-app",
|
||||||
|
"emoji": "👟"
|
||||||
|
}
|
30
apps/sneaker/app.js
Normal file
30
apps/sneaker/app.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import * as tfrpc from '/tfrpc.js';
|
||||||
|
|
||||||
|
tfrpc.register(async function getAllIdentities() {
|
||||||
|
return ssb.getAllIdentities();
|
||||||
|
});
|
||||||
|
tfrpc.register(async function query(sql, args) {
|
||||||
|
let result = [];
|
||||||
|
await ssb.sqlAsync(sql, args, function callback(row) {
|
||||||
|
result.push(row);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
tfrpc.register(async function store_blob(blob) {
|
||||||
|
if (Array.isArray(blob)) {
|
||||||
|
blob = Uint8Array.from(blob);
|
||||||
|
}
|
||||||
|
return await ssb.blobStore(blob);
|
||||||
|
});
|
||||||
|
tfrpc.register(async function get_blob(id) {
|
||||||
|
return Array.from(new Uint8Array(await ssb.blobGet(id)));
|
||||||
|
});
|
||||||
|
tfrpc.register(async function store_message(message) {
|
||||||
|
return await ssb.storeMessage(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await app.setDocument(utf8Decode(await getFile('index.html')));
|
||||||
|
}
|
||||||
|
main();
|
3
apps/sneaker/filesaver.min.js
vendored
Normal file
3
apps/sneaker/filesaver.min.js
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
(function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error("could not download file")},d.send()}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open("","_blank"),g&&(g.document.title=g.document.body.innerText="downloading..."),"string"==typeof b)return c(b,d,e);var h="application/octet-stream"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\/[\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&"undefined"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,"undefined"!=typeof module&&(module.exports=g)});
|
||||||
|
|
||||||
|
//# sourceMappingURL=FileSaver.min.js.map
|
14
apps/sneaker/index.html
Normal file
14
apps/sneaker/index.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html style="color: #fff">
|
||||||
|
<head>
|
||||||
|
<title>Tilde Friends</title>
|
||||||
|
<base target="_top">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<tf-sneaker-app/>
|
||||||
|
<script>window.litDisableBundleWarning = true;</script>
|
||||||
|
<script src="filesaver.min.js"></script>
|
||||||
|
<script src="jszip.min.js"></script>
|
||||||
|
<script src="script.js" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
13
apps/sneaker/jszip.min.js
vendored
Normal file
13
apps/sneaker/jszip.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
120
apps/sneaker/lit-all.min.js
vendored
Normal file
120
apps/sneaker/lit-all.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
apps/sneaker/lit-all.min.js.map
Normal file
1
apps/sneaker/lit-all.min.js.map
Normal file
File diff suppressed because one or more lines are too long
232
apps/sneaker/script.js
Normal file
232
apps/sneaker/script.js
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
import {LitElement, html} from './lit-all.min.js';
|
||||||
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
|
|
||||||
|
class TfSneakerAppElement extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
feeds: {type: Object},
|
||||||
|
progress: {type: Object},
|
||||||
|
result: {type: String},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.feeds = [];
|
||||||
|
this.progress = undefined;
|
||||||
|
this.result = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async search() {
|
||||||
|
let q = this.renderRoot.getElementById('search').value;
|
||||||
|
let result = await tfrpc.rpc.query(`
|
||||||
|
SELECT messages.author AS id, json_extract(messages.content, '$.name') AS name
|
||||||
|
FROM messages_fts(?)
|
||||||
|
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||||
|
WHERE
|
||||||
|
json_extract(messages.content, '$.type') = 'about' AND
|
||||||
|
json_extract(messages.content, '$.about') = messages.author AND
|
||||||
|
json_extract(messages.content, '$.name') IS NOT NULL
|
||||||
|
GROUP BY messages.author
|
||||||
|
HAVING MAX(messages.sequence)
|
||||||
|
ORDER BY COUNT(*) DESC
|
||||||
|
`,
|
||||||
|
[`"${q.replaceAll('"', '""')}"`]);
|
||||||
|
this.feeds = Object.fromEntries(result.map(x => [x.id, x.name]));
|
||||||
|
}
|
||||||
|
|
||||||
|
format_message(message) {
|
||||||
|
let out = {
|
||||||
|
previous: message.previous ?? null,
|
||||||
|
};
|
||||||
|
if (message.sequence_before_author) {
|
||||||
|
out.sequence = message.sequence;
|
||||||
|
out.author = message.author;
|
||||||
|
} else {
|
||||||
|
out.author = message.author;
|
||||||
|
out.sequence = message.sequence;
|
||||||
|
}
|
||||||
|
out.timestamp = message.timestamp;
|
||||||
|
out.hash = message.hash;
|
||||||
|
out.content = JSON.parse(message.content);
|
||||||
|
out.signature = message.signature;
|
||||||
|
return {key: message.id, value: out};
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitize(value) {
|
||||||
|
return value.replaceAll('/', '_').replaceAll('+', '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
guess_ext(data) {
|
||||||
|
function startsWith(prefix) {
|
||||||
|
if (data.length < prefix.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < prefix.length; i++) {
|
||||||
|
if (prefix[i] !== null && data[i] !== prefix[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startsWith(data, [0xff, 0xd8, 0xff, 0xdb]) ||
|
||||||
|
startsWith(data, [0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01]) ||
|
||||||
|
startsWith(data, [0xff, 0xd8, 0xff, 0xee]) ||
|
||||||
|
startsWith(data, [0xff, 0xd8, 0xff, 0xe1, null, null, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00])) {
|
||||||
|
return '.jpg';
|
||||||
|
} else if (startsWith(data, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
|
||||||
|
return '.png';
|
||||||
|
} else if (startsWith(data, [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]) ||
|
||||||
|
startsWith(data, [0x47, 0x49, 0x46, 0x38, 0x39, 0x61])) {
|
||||||
|
return '.gif';
|
||||||
|
} else if (startsWith(data, [0x52, 0x49, 0x46, 0x46, null, null, null, null, 0x57, 0x45, 0x42, 0x50])) {
|
||||||
|
return '.webp';
|
||||||
|
} else if (startsWith(data, [0x3c, 0x73, 0x76, 0x67])) {
|
||||||
|
return '.svg';
|
||||||
|
} else if (startsWith(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32])) {
|
||||||
|
return '.mp3';
|
||||||
|
} else if (startsWith(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d]) ||
|
||||||
|
startsWith(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32])) {
|
||||||
|
return '.mp4';
|
||||||
|
} else {
|
||||||
|
return '.bin';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async export(id) {
|
||||||
|
let all_messages = '';
|
||||||
|
let sequence = -1;
|
||||||
|
let messages_done = 0;
|
||||||
|
let messages_max = (await tfrpc.rpc.query('SELECT MAX(sequence) AS total FROM messages WHERE author = ?', [id]))[0].total;
|
||||||
|
while (true) {
|
||||||
|
let messages = await tfrpc.rpc.query(
|
||||||
|
'SELECT * FROM messages WHERE author = ? AND SEQUENCE > ? ORDER BY sequence LIMIT 100',
|
||||||
|
[id, sequence]
|
||||||
|
);
|
||||||
|
if (messages?.length) {
|
||||||
|
all_messages += messages.map(x => JSON.stringify(this.format_message(x))).join('\n') + '\n';
|
||||||
|
sequence = messages[messages.length - 1].sequence;
|
||||||
|
messages_done += messages.length;
|
||||||
|
this.progress = {name: 'messages', value: messages_done, max: messages_max};
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let zip = new JSZip();
|
||||||
|
zip.file(`message/classic/${this.sanitize(id)}.ndjson`, all_messages);
|
||||||
|
|
||||||
|
let blobs = await tfrpc.rpc.query(
|
||||||
|
`SELECT messages_refs.ref AS id
|
||||||
|
FROM messages
|
||||||
|
JOIN messages_refs ON messages.id = messages_refs.message
|
||||||
|
WHERE messages.author = ? AND messages_refs.ref LIKE '&%.sha256'`,
|
||||||
|
[id]);
|
||||||
|
let blobs_done = 0;
|
||||||
|
for (let row of blobs) {
|
||||||
|
this.progress = {name: 'blobs', value: blobs_done, max: blobs.length};
|
||||||
|
let blob;
|
||||||
|
try {
|
||||||
|
blob = await tfrpc.rpc.get_blob(row.id);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Failed to get ${row.id}: ${e.message}`);
|
||||||
|
}
|
||||||
|
if (blob) {
|
||||||
|
zip.file(`blob/classic/${this.sanitize(row.id)}${this.guess_ext(blob)}`, new Uint8Array(blob));
|
||||||
|
}
|
||||||
|
blobs_done++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.progress = {name: 'saving'};
|
||||||
|
let blob = await zip.generateAsync({type: 'blob'});
|
||||||
|
saveAs(blob, `${this.sanitize(id)}.zip`);
|
||||||
|
this.progress = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
keypress(event) {
|
||||||
|
if (event.key == 'Enter') {
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async import(event) {
|
||||||
|
let file = event.target.files[0];
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.progress = {name: 'loading'};
|
||||||
|
let zip = new JSZip();
|
||||||
|
file = await zip.loadAsync(file);
|
||||||
|
let messages = [];
|
||||||
|
let blobs = [];
|
||||||
|
file.forEach(function(path, entry) {
|
||||||
|
if (!entry.dir) {
|
||||||
|
if (path.startsWith('message/classic/')) {
|
||||||
|
messages.push(entry);
|
||||||
|
} else {
|
||||||
|
blobs.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let success = {messages: 0, blobs: 0};
|
||||||
|
let progress = 0;
|
||||||
|
let total_messages = 0;
|
||||||
|
for (let entry of messages) {
|
||||||
|
let lines = (await entry.async('string')).split('\n');
|
||||||
|
total_messages += lines.length;
|
||||||
|
for (let line of lines) {
|
||||||
|
if (!line.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let message = JSON.parse(line);
|
||||||
|
this.progress = {name: 'messages', value: progress++, max: total_messages};
|
||||||
|
if (await tfrpc.rpc.store_message(message.value)) {
|
||||||
|
success.messages++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
progress = 0;
|
||||||
|
for (let blob of blobs) {
|
||||||
|
this.progress = {name: 'blobs', value: progress++, max: blobs.length};
|
||||||
|
if (await tfrpc.rpc.store_blob(await blob.async('arraybuffer'))) {
|
||||||
|
success.blobs++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.progress = undefined;
|
||||||
|
this.result = `imported ${success.messages} messages and ${success.blobs} blobs`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let progress;
|
||||||
|
if (this.progress) {
|
||||||
|
if (this.progress.max) {
|
||||||
|
progress = html`<div><label for="progress">${this.progress.name}</label><progress value=${this.progress.value} max=${this.progress.max}></progress></div>`;
|
||||||
|
} else {
|
||||||
|
progress = html`<div><span>${this.progress.name}</span></div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return html`<h1>SSB 👟net</h1>
|
||||||
|
<code>${this.result}</code>
|
||||||
|
${progress}
|
||||||
|
|
||||||
|
<h2>Import</h2>
|
||||||
|
<input type="file" id="import" @change=${this.import}></input>
|
||||||
|
|
||||||
|
<h2>Export</h2>
|
||||||
|
<input type="text" id="search" @keypress=${this.keypress}></input>
|
||||||
|
<input type="button" value="Search Users" @click=${this.search}></input>
|
||||||
|
<ul>
|
||||||
|
${Object.entries(this.feeds).map(([id, name]) => html`
|
||||||
|
<li>
|
||||||
|
${this.progress ? undefined : html`<input type="button" value="Export" @click=${() => this.export(id)}></input>`}
|
||||||
|
${name}
|
||||||
|
<code style="color: #ccc">${id}</code>
|
||||||
|
</li>
|
||||||
|
`)}
|
||||||
|
</ul>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define('tf-sneaker-app', TfSneakerAppElement);
|
@ -79,9 +79,15 @@ tfrpc.register(async function store_blob(blob) {
|
|||||||
tfrpc.register(async function get_blob(id) {
|
tfrpc.register(async function get_blob(id) {
|
||||||
return utf8Decode(await ssb.blobGet(id));
|
return utf8Decode(await ssb.blobGet(id));
|
||||||
});
|
});
|
||||||
|
tfrpc.register(async function store_message(message) {
|
||||||
|
return await ssb.storeMessage(message);
|
||||||
|
});
|
||||||
tfrpc.register(function apps() {
|
tfrpc.register(function apps() {
|
||||||
return core.apps();
|
return core.apps();
|
||||||
});
|
});
|
||||||
|
tfrpc.register(async function try_decrypt(id, content) {
|
||||||
|
return await ssb.privateMessageDecrypt(id, content);
|
||||||
|
});
|
||||||
ssb.addEventListener('broadcasts', async function() {
|
ssb.addEventListener('broadcasts', async function() {
|
||||||
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
|
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
|
||||||
});
|
});
|
||||||
|
@ -54,21 +54,27 @@ export function picker(callback, anchor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function chosen(event) {
|
||||||
|
console.log(event.srcElement.innerText);
|
||||||
|
callback(event.srcElement.innerText);
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
function refresh() {
|
function refresh() {
|
||||||
while (list.firstChild) {
|
while (list.firstChild) {
|
||||||
list.removeChild(list.firstChild);
|
list.removeChild(list.firstChild);
|
||||||
}
|
}
|
||||||
let search = input.value;
|
let search = input.value;
|
||||||
let any_at_all = false;
|
let any_at_all = false;
|
||||||
Object.entries(json).forEach(function(row) {
|
for (let row of Object.entries(json)) {
|
||||||
let header = document.createElement('div');
|
let header = document.createElement('div');
|
||||||
header.appendChild(document.createTextNode(row[0]));
|
header.appendChild(document.createTextNode(row[0]));
|
||||||
list.appendChild(header);
|
list.appendChild(header);
|
||||||
let any = false;
|
let any = false;
|
||||||
for (let entry of row[1]) {
|
for (let entry of Object.entries(row[1])) {
|
||||||
if (search &&
|
if (search &&
|
||||||
search.length &&
|
search.length &&
|
||||||
entry.name.indexOf(search) == -1) {
|
entry[0].indexOf(search) == -1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let emoji = document.createElement('span');
|
let emoji = document.createElement('span');
|
||||||
@ -76,12 +82,9 @@ export function picker(callback, anchor) {
|
|||||||
emoji.style.display = 'inline-block';
|
emoji.style.display = 'inline-block';
|
||||||
emoji.style.overflow = 'hidden';
|
emoji.style.overflow = 'hidden';
|
||||||
emoji.style.cursor = 'pointer';
|
emoji.style.cursor = 'pointer';
|
||||||
emoji.onclick = function() {
|
emoji.onclick = chosen;
|
||||||
callback(entry);
|
emoji.title = entry[0];
|
||||||
cleanup();
|
emoji.appendChild(document.createTextNode(entry[1]));
|
||||||
}
|
|
||||||
emoji.title = entry.name;
|
|
||||||
emoji.appendChild(document.createTextNode(entry.emoji));
|
|
||||||
list.appendChild(emoji);
|
list.appendChild(emoji);
|
||||||
any = true;
|
any = true;
|
||||||
any_at_all = true;
|
any_at_all = true;
|
||||||
@ -89,7 +92,7 @@ export function picker(callback, anchor) {
|
|||||||
if (!any) {
|
if (!any) {
|
||||||
list.removeChild(header);
|
list.removeChild(header);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
if (!any_at_all) {
|
if (!any_at_all) {
|
||||||
list.appendChild(document.createTextNode('No matches found.'));
|
list.appendChild(document.createTextNode('No matches found.'));
|
||||||
}
|
}
|
||||||
|
15116
apps/ssb/emojis.json
15116
apps/ssb/emojis.json
File diff suppressed because one or more lines are too long
3
apps/ssb/filesaver.min.js
vendored
Normal file
3
apps/ssb/filesaver.min.js
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
(function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error("could not download file")},d.send()}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open("","_blank"),g&&(g.document.title=g.document.body.innerText="downloading..."),"string"==typeof b)return c(b,d,e);var h="application/octet-stream"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\/[\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&"undefined"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,"undefined"!=typeof module&&(module.exports=g)});
|
||||||
|
|
||||||
|
//# sourceMappingURL=FileSaver.min.js.map
|
@ -13,6 +13,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<tf-app/>
|
<tf-app/>
|
||||||
<script>window.litDisableBundleWarning = true;</script>
|
<script>window.litDisableBundleWarning = true;</script>
|
||||||
|
<script src="filesaver.min.js"></script>
|
||||||
<script src="commonmark.min.js"></script>
|
<script src="commonmark.min.js"></script>
|
||||||
<script src="commonmark-linkify.js" type="module"></script>
|
<script src="commonmark-linkify.js" type="module"></script>
|
||||||
<script src="commonmark-hashtag.js" type="module"></script>
|
<script src="commonmark-hashtag.js" type="module"></script>
|
||||||
|
54
apps/ssb/lit-all.min.js
vendored
54
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
@ -8,6 +8,10 @@ import * as tf_user from './tf-user.js';
|
|||||||
import * as tf_compose from './tf-compose.js';
|
import * as tf_compose from './tf-compose.js';
|
||||||
import * as tf_news from './tf-news.js';
|
import * as tf_news from './tf-news.js';
|
||||||
import * as tf_profile from './tf-profile.js';
|
import * as tf_profile from './tf-profile.js';
|
||||||
|
import * as tf_tab_mentions from './tf-tab-mentions.js';
|
||||||
import * as tf_tab_news from './tf-tab-news.js';
|
import * as tf_tab_news from './tf-tab-news.js';
|
||||||
|
import * as tf_tab_news_feed from './tf-tab-news-feed.js';
|
||||||
import * as tf_tab_search from './tf-tab-search.js';
|
import * as tf_tab_search from './tf-tab-search.js';
|
||||||
import * as tf_tab_connections from './tf-tab-connections.js';
|
import * as tf_tab_connections from './tf-tab-connections.js';
|
||||||
|
import * as tf_tab_query from './tf-tab-query.js';
|
||||||
|
import * as tf_tag from './tf-tag.js';
|
@ -16,6 +16,7 @@ class TfElement extends LitElement {
|
|||||||
following: {type: Array},
|
following: {type: Array},
|
||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
ids: {type: Array},
|
ids: {type: Array},
|
||||||
|
tags: {type: Array},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,8 +33,9 @@ class TfElement extends LitElement {
|
|||||||
this.following = [];
|
this.following = [];
|
||||||
this.users = {};
|
this.users = {};
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
tfrpc.rpc.getBroadcasts().then(b => { self.broadcasts = b || [] });
|
this.tags = [];
|
||||||
tfrpc.rpc.getConnections().then(c => { self.connections = c || [] });
|
tfrpc.rpc.getBroadcasts().then(b => { self.broadcasts = b || []; });
|
||||||
|
tfrpc.rpc.getConnections().then(c => { self.connections = c || []; });
|
||||||
tfrpc.rpc.getHash().then(hash => self.set_hash(hash));
|
tfrpc.rpc.getHash().then(hash => self.set_hash(hash));
|
||||||
tfrpc.register(function hashChanged(hash) {
|
tfrpc.register(function hashChanged(hash) {
|
||||||
self.set_hash(hash);
|
self.set_hash(hash);
|
||||||
@ -64,6 +66,10 @@ class TfElement extends LitElement {
|
|||||||
this.tab = 'search';
|
this.tab = 'search';
|
||||||
} else if (this.hash === '#connections') {
|
} else if (this.hash === '#connections') {
|
||||||
this.tab = 'connections';
|
this.tab = 'connections';
|
||||||
|
} else if (this.hash === '#mentions') {
|
||||||
|
this.tab = 'mentions';
|
||||||
|
} else if (this.hash.startsWith('#sql=')) {
|
||||||
|
this.tab = 'query';
|
||||||
} else {
|
} else {
|
||||||
this.tab = 'news';
|
this.tab = 'news';
|
||||||
}
|
}
|
||||||
@ -241,22 +247,47 @@ class TfElement extends LitElement {
|
|||||||
if (confirm("Are you sure you want to create a new identity?")) {
|
if (confirm("Are you sure you want to create a new identity?")) {
|
||||||
await tfrpc.rpc.createIdentity();
|
await tfrpc.rpc.createIdentity();
|
||||||
this.ids = (await tfrpc.rpc.getIdentities()) || [];
|
this.ids = (await tfrpc.rpc.getIdentities()) || [];
|
||||||
|
if (this.ids && !this.whoami) {
|
||||||
|
this.whoami = this.ids[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render_id_picker() {
|
render_id_picker() {
|
||||||
return html`
|
return html`
|
||||||
<tf-id-picker id="picker" selected=${this.whoami} .ids=${this.ids} @change=${this._handle_whoami_changed}></tf-id-picker>
|
<tf-id-picker id="picker" selected=${this.whoami} .ids=${this.ids} @change=${this._handle_whoami_changed}></tf-id-picker>
|
||||||
<button @click=${this.create_identity}>Create Identity</button>
|
<button @click=${this.create_identity} id="create_identity">Create Identity</button>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async load_recent_tags() {
|
||||||
|
let start = new Date();
|
||||||
|
this.tags = await tfrpc.rpc.query(`
|
||||||
|
WITH
|
||||||
|
recent AS (SELECT id, content FROM messages
|
||||||
|
WHERE messages.timestamp > ? AND json_extract(content, '$.type') = 'post'
|
||||||
|
ORDER BY timestamp DESC LIMIT 1024),
|
||||||
|
recent_channels AS (SELECT recent.id, '#' || json_extract(content, '$.channel') AS tag
|
||||||
|
FROM recent
|
||||||
|
WHERE json_extract(content, '$.channel') IS NOT NULL),
|
||||||
|
recent_mentions AS (SELECT recent.id, json_extract(mention.value, '$.link') AS tag
|
||||||
|
FROM recent, json_each(recent.content, '$.mentions') AS mention
|
||||||
|
WHERE json_valid(mention.value) AND tag LIKE '#%'),
|
||||||
|
combined AS (SELECT id, tag FROM recent_channels UNION ALL SELECT id, tag FROM recent_mentions),
|
||||||
|
by_message AS (SELECT DISTINCT id, tag FROM combined)
|
||||||
|
SELECT tag, COUNT(*) AS count FROM by_message GROUP BY tag ORDER BY count DESC LIMIT 10
|
||||||
|
`, [new Date() - 7 * 24 * 60 * 60 * 1000]);
|
||||||
|
console.log('tags took', (new Date() - start) / 1000.0, 'seconds');
|
||||||
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
let whoami = this.whoami;
|
let whoami = this.whoami;
|
||||||
|
let tags = this.load_recent_tags();
|
||||||
let [following, users] = await this.following_deep([whoami], 2, {});
|
let [following, users] = await this.following_deep([whoami], 2, {});
|
||||||
users = await this.fetch_about(following.sort(), users);
|
users = await this.fetch_about(following.sort(), users);
|
||||||
this.following = following;
|
this.following = following;
|
||||||
this.users = users;
|
this.users = users;
|
||||||
|
await tags;
|
||||||
console.log(`load finished ${whoami} => ${this.whoami}`);
|
console.log(`load finished ${whoami} => ${this.whoami}`);
|
||||||
this.whoami = whoami;
|
this.whoami = whoami;
|
||||||
this.loaded = whoami;
|
this.loaded = whoami;
|
||||||
@ -267,16 +298,24 @@ class TfElement extends LitElement {
|
|||||||
let users = this.users;
|
let users = this.users;
|
||||||
if (this.tab === 'news') {
|
if (this.tab === 'news') {
|
||||||
return html`
|
return html`
|
||||||
<tf-tab-news .following=${this.following} whoami=${this.whoami} .users=${this.users} hash=${this.hash} .unread=${this.unread} @refresh=${() => this.unread = []}></tf-tab-news>
|
<tf-tab-news id="tf-tab-news" .following=${this.following} whoami=${this.whoami} .users=${this.users} hash=${this.hash} .unread=${this.unread} @refresh=${() => this.unread = []}></tf-tab-news>
|
||||||
`;
|
`;
|
||||||
} else if (this.tab === 'connections') {
|
} else if (this.tab === 'connections') {
|
||||||
return html`
|
return html`
|
||||||
<tf-tab-connections .users=${this.users} .connections=${this.connections} .broadcasts=${this.broadcasts}></tf-tab-connections>
|
<tf-tab-connections .users=${this.users} .connections=${this.connections} .broadcasts=${this.broadcasts}></tf-tab-connections>
|
||||||
`;
|
`;
|
||||||
|
} else if (this.tab === 'mentions') {
|
||||||
|
return html`
|
||||||
|
<tf-tab-mentions .following=${this.following} whoami=${this.whoami} .users=${this.users}}></tf-tab-mentions>
|
||||||
|
`;
|
||||||
} else if (this.tab === 'search') {
|
} else if (this.tab === 'search') {
|
||||||
return html`
|
return html`
|
||||||
<tf-tab-search .following=${this.following} whoami=${this.whoami} .users=${this.users} query=${this.hash?.startsWith('#q=') ? decodeURIComponent(this.hash.substring(3)) : null}></tf-tab-search>
|
<tf-tab-search .following=${this.following} whoami=${this.whoami} .users=${this.users} query=${this.hash?.startsWith('#q=') ? decodeURIComponent(this.hash.substring(3)) : null}></tf-tab-search>
|
||||||
`;
|
`;
|
||||||
|
} else if (this.tab === 'query') {
|
||||||
|
return html`
|
||||||
|
<tf-tab-query .following=${this.following} whoami=${this.whoami} .users=${this.users} query=${this.hash?.startsWith('#sql=') ? decodeURIComponent(this.hash.substring(5)) : null}></tf-tab-query>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,6 +325,10 @@ class TfElement extends LitElement {
|
|||||||
await tfrpc.rpc.setHash('#');
|
await tfrpc.rpc.setHash('#');
|
||||||
} else if (tab === 'connections') {
|
} else if (tab === 'connections') {
|
||||||
await tfrpc.rpc.setHash('#connections');
|
await tfrpc.rpc.setHash('#connections');
|
||||||
|
} else if (tab === 'mentions') {
|
||||||
|
await tfrpc.rpc.setHash('#mentions');
|
||||||
|
} else if (tab === 'query') {
|
||||||
|
await tfrpc.rpc.setHash('#sql=');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,7 +336,6 @@ class TfElement extends LitElement {
|
|||||||
let self = this;
|
let self = this;
|
||||||
|
|
||||||
if (!this.loading && this.whoami && this.loaded !== this.whoami) {
|
if (!this.loading && this.whoami && this.loaded !== this.whoami) {
|
||||||
console.log(`starting loading ${this.whoami} ${this.loaded}`);
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.load().finally(function() {
|
this.load().finally(function() {
|
||||||
self.loading = false;
|
self.loading = false;
|
||||||
@ -304,7 +346,9 @@ class TfElement extends LitElement {
|
|||||||
<div>
|
<div>
|
||||||
<input type="button" class="tab" value="News" ?disabled=${self.tab == 'news'} @click=${() => self.set_tab('news')}></input>
|
<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="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="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>
|
</div>
|
||||||
`;
|
`;
|
||||||
let contents =
|
let contents =
|
||||||
@ -316,6 +360,7 @@ class TfElement extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
${this.render_id_picker()}
|
${this.render_id_picker()}
|
||||||
${tabs}
|
${tabs}
|
||||||
|
${this.tags.map(x => html`<tf-tag tag=${x.tag} count=${x.count}></tf-tag>`)}
|
||||||
${contents}
|
${contents}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class TfComposeElement extends LitElement {
|
|||||||
branch: {type: String},
|
branch: {type: String},
|
||||||
apps: {type: Object},
|
apps: {type: Object},
|
||||||
drafts: {type: Object},
|
drafts: {type: Object},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = styles;
|
static styles = styles;
|
||||||
@ -56,7 +56,7 @@ class TfComposeElement extends LitElement {
|
|||||||
if (!draft.mentions[link]) {
|
if (!draft.mentions[link]) {
|
||||||
draft.mentions[link] = {
|
draft.mentions[link] = {
|
||||||
link: link,
|
link: link,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
draft.mentions[link].name = name.startsWith('@') ? name.substring(1) : name;
|
draft.mentions[link].name = name.startsWith('@') ? name.substring(1) : name;
|
||||||
updated = true;
|
updated = true;
|
||||||
@ -111,7 +111,7 @@ class TfComposeElement extends LitElement {
|
|||||||
let data_url = canvas.toDataURL(mime_type);
|
let data_url = canvas.toDataURL(mime_type);
|
||||||
let result = atob(data_url.split(',')[1]).split('').map(x => x.charCodeAt(0));
|
let result = atob(data_url.split(',')[1]).split('').map(x => x.charCodeAt(0));
|
||||||
resolve(result);
|
resolve(result);
|
||||||
}
|
};
|
||||||
img.onerror = function(event) {
|
img.onerror = function(event) {
|
||||||
reject(new Error('Failed to load image.'));
|
reject(new Error('Failed to load image.'));
|
||||||
};
|
};
|
||||||
@ -256,9 +256,18 @@ class TfComposeElement extends LitElement {
|
|||||||
render_mention(mention) {
|
render_mention(mention) {
|
||||||
let self = this;
|
let self = this;
|
||||||
return html`
|
return html`
|
||||||
<div>
|
<div style="display: flex; flex-direction: row">
|
||||||
<pre style="white-space: pre-wrap">${JSON.stringify(mention, null, 2)}</pre>
|
<div style="align-self: center; margin: 0.5em">
|
||||||
<input type="button" value="x" @click=${() => self.remove_mention(mention.link)}></input>
|
<input type="button" value="🚮" title="Remove ${mention.name} mention" @click=${() => self.remove_mention(mention.link)}></input>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column">
|
||||||
|
<h3>${mention.name}</h3>
|
||||||
|
<div style="padding-left: 1em">
|
||||||
|
${Object.entries(mention)
|
||||||
|
.filter(x => x[0] != 'name')
|
||||||
|
.map(x => html`<div><span style="font-weight: bold">${x[0]}</span>: ${x[1]}</div>`)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,11 +292,11 @@ class TfComposeElement extends LitElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let draft = this.get_draft();
|
let draft = self.get_draft();
|
||||||
draft.mentions = Object.assign(draft.mentions || {}, mentions);
|
draft.mentions = Object.assign(draft.mentions || {}, mentions);
|
||||||
this.requestUpdate();
|
self.requestUpdate();
|
||||||
this.notify(draft);
|
self.notify(draft);
|
||||||
this.apps = null;
|
self.apps = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.apps) {
|
if (this.apps) {
|
||||||
@ -304,13 +313,14 @@ class TfComposeElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render_attach_app_button() {
|
render_attach_app_button() {
|
||||||
|
let self = this;
|
||||||
async function attach_app() {
|
async function attach_app() {
|
||||||
this.apps = await tfrpc.rpc.apps();
|
self.apps = await tfrpc.rpc.apps();
|
||||||
}
|
}
|
||||||
if (!this.apps) {
|
if (!this.apps) {
|
||||||
return html`<input type="button" value="Attach App" @click=${attach_app}></input>`
|
return html`<input type="button" value="Attach App" @click=${attach_app}></input>`;
|
||||||
} else {
|
} else {
|
||||||
return html`<input type="button" value="Discard App" @click=${() => this.apps = null}></input>`
|
return html`<input type="button" value="Discard App" @click=${() => this.apps = null}></input>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,7 +372,7 @@ class TfComposeElement extends LitElement {
|
|||||||
${Object.values(draft.mentions || {}).map(x => self.render_mention(x))}
|
${Object.values(draft.mentions || {}).map(x => self.render_mention(x))}
|
||||||
${this.render_content_warning()}
|
${this.render_content_warning()}
|
||||||
${this.render_attach_app()}
|
${this.render_attach_app()}
|
||||||
<input type="button" value="Submit" @click=${this.submit}></input>
|
<input type="button" id="submit" value="Submit" @click=${this.submit}></input>
|
||||||
<input type="button" value="Attach" @click=${this.attach}></input>
|
<input type="button" value="Attach" @click=${this.attach}></input>
|
||||||
${this.render_attach_app_button()}
|
${this.render_attach_app_button()}
|
||||||
<input type="button" value="Discard" @click=${this.discard}></input>
|
<input type="button" value="Discard" @click=${this.discard}></input>
|
||||||
|
@ -9,12 +9,11 @@ class TfIdentityPickerElement extends LitElement {
|
|||||||
return {
|
return {
|
||||||
ids: {type: Array},
|
ids: {type: Array},
|
||||||
selected: {type: String},
|
selected: {type: String},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
let self = this;
|
|
||||||
this.ids = [];
|
this.ids = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,10 +11,11 @@ class TfMessageElement extends LitElement {
|
|||||||
message: {type: Object},
|
message: {type: Object},
|
||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
drafts: {type: Object},
|
drafts: {type: Object},
|
||||||
raw: {type: Boolean},
|
format: {type: String},
|
||||||
blog_data: {type: String},
|
blog_data: {type: String},
|
||||||
expanded: {type: Object},
|
expanded: {type: Object},
|
||||||
}
|
decrypted: {type: Object},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = styles;
|
static styles = styles;
|
||||||
@ -26,8 +27,9 @@ class TfMessageElement extends LitElement {
|
|||||||
this.message = {};
|
this.message = {};
|
||||||
this.users = {};
|
this.users = {};
|
||||||
this.drafts = {};
|
this.drafts = {};
|
||||||
this.raw = false;
|
this.format = 'message';
|
||||||
this.expanded = {};
|
this.expanded = {};
|
||||||
|
this.decrypted = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
show_reply() {
|
show_reply() {
|
||||||
@ -69,12 +71,12 @@ class TfMessageElement extends LitElement {
|
|||||||
hash: this.message?.hash,
|
hash: this.message?.hash,
|
||||||
content: this.message?.content,
|
content: this.message?.content,
|
||||||
signature: this.message?.signature,
|
signature: this.message?.signature,
|
||||||
}
|
};
|
||||||
return html`<div style="white-space: pre-wrap">${JSON.stringify(raw, null, 2)}</div>`
|
return html`<div style="white-space: pre-wrap">${JSON.stringify(raw, null, 2)}</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
vote(emoji) {
|
vote(emoji) {
|
||||||
let reaction = emoji.emoji;
|
let reaction = emoji;
|
||||||
let message = this.message.id;
|
let message = this.message.id;
|
||||||
if (confirm('Are you sure you want to react with ' + reaction + ' to ' + message + '?')) {
|
if (confirm('Are you sure you want to react with ' + reaction + ' to ' + message + '?')) {
|
||||||
tfrpc.rpc.appendMessage(
|
tfrpc.rpc.appendMessage(
|
||||||
@ -127,6 +129,13 @@ class TfMessageElement extends LitElement {
|
|||||||
body_click(event) {
|
body_click(event) {
|
||||||
if (event.srcElement.tagName == 'IMG') {
|
if (event.srcElement.tagName == 'IMG') {
|
||||||
this.show_image(event.srcElement.src);
|
this.show_image(event.srcElement.src);
|
||||||
|
} else if (event.srcElement.tagName == 'DIV' && event.srcElement.classList.contains('img_caption')) {
|
||||||
|
let next = event.srcElement.nextSibling;
|
||||||
|
if (next.style.display == 'block') {
|
||||||
|
next.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
next.style.display = 'block';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,10 +177,7 @@ class TfMessageElement extends LitElement {
|
|||||||
|
|
||||||
render_mentions() {
|
render_mentions() {
|
||||||
let mentions = this.message?.content?.mentions || [];
|
let mentions = this.message?.content?.mentions || [];
|
||||||
mentions = mentions.filter(x =>
|
mentions = mentions.filter(x => this.message?.content?.text?.indexOf(x.link) === -1);
|
||||||
x.name?.startsWith('audio:') ||
|
|
||||||
x.name?.startsWith('video:') ||
|
|
||||||
this.message?.content?.text?.indexOf(x.link) === -1);
|
|
||||||
if (mentions.length) {
|
if (mentions.length) {
|
||||||
let self = this;
|
let self = this;
|
||||||
return html`
|
return html`
|
||||||
@ -214,22 +220,68 @@ class TfMessageElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render_channels() {
|
||||||
|
let content = this.message?.content;
|
||||||
|
if (this.decrypted?.type == 'post') {
|
||||||
|
content = this.decrypted;
|
||||||
|
}
|
||||||
|
let channels = [];
|
||||||
|
if (typeof content.channel === 'string') {
|
||||||
|
channels.push(`#${content.channel}`);
|
||||||
|
}
|
||||||
|
if (Array.isArray(content.mentions)) {
|
||||||
|
for (let mention of content.mentions) {
|
||||||
|
if (typeof mention?.link === 'string' &&
|
||||||
|
mention.link.startsWith('#')) {
|
||||||
|
channels.push(mention.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return channels.map(x => html`<tf-tag tag=${x}></tf-tag>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async try_decrypt(content) {
|
||||||
|
let result = await tfrpc.rpc.try_decrypt(this.whoami, content);
|
||||||
|
if (result) {
|
||||||
|
this.decrypted = JSON.parse(result);
|
||||||
|
} else {
|
||||||
|
this.decrypted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let content = this.message?.content;
|
let content = this.message?.content;
|
||||||
|
if (this.decrypted?.type == 'post') {
|
||||||
|
content = this.decrypted;
|
||||||
|
}
|
||||||
let self = this;
|
let self = this;
|
||||||
let raw_button = this.raw ?
|
let raw_button;
|
||||||
html`<input type="button" value="Message" @click=${() => self.raw = false}></input>` :
|
switch (this.format) {
|
||||||
html`<input type="button" value="Raw" @click=${() => self.raw = true}></input>`;
|
case 'raw':
|
||||||
|
if (content?.type == 'post' || content?.type == 'blog') {
|
||||||
|
raw_button = html`<input type="button" value="Markdown" @click=${() => self.format = 'md'}></input>`;
|
||||||
|
} else {
|
||||||
|
raw_button = html`<input type="button" value="Message" @click=${() => self.format = 'message'}></input>`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'md':
|
||||||
|
raw_button = html`<input type="button" value="Message" @click=${() => self.format = 'message'}></input>`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
raw_button = html`<input type="button" value="Raw" @click=${() => self.format = 'raw'}></input>`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
function small_frame(inner) {
|
function small_frame(inner) {
|
||||||
|
let body;
|
||||||
return html`
|
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 style="border: 1px solid black; 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>
|
<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>
|
<span style="padding-right: 8px"><a tfarget="_top" href=${'#' + self.message.id}>%</a> ${new Date(self.message.timestamp).toLocaleString()}</span>
|
||||||
${raw_button}
|
${raw_button}
|
||||||
${self.raw ? self.render_raw() : inner}
|
${self.format == 'raw' ? self.render_raw() : inner}
|
||||||
${self.render_votes()}
|
${self.render_votes()}
|
||||||
</div>
|
</div>
|
||||||
`
|
`;
|
||||||
}
|
}
|
||||||
if (this.message?.type === 'contact_group') {
|
if (this.message?.type === 'contact_group') {
|
||||||
return html`
|
return html`
|
||||||
@ -265,7 +317,7 @@ class TfMessageElement extends LitElement {
|
|||||||
<div style="flex: 1 0 50%; overflow-wrap: anywhere">
|
<div style="flex: 1 0 50%; overflow-wrap: anywhere">
|
||||||
<div>${unsafeHTML(tfutils.markdown(content.description))}</div>
|
<div>${unsafeHTML(tfutils.markdown(content.description))}</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`;
|
||||||
}
|
}
|
||||||
let update = content.about == this.message.author ?
|
let update = content.about == this.message.author ?
|
||||||
html`<div style="font-weight: bold">Updated profile.</div>` :
|
html`<div style="font-weight: bold">Updated profile.</div>` :
|
||||||
@ -304,14 +356,24 @@ class TfMessageElement extends LitElement {
|
|||||||
<input type="button" value="Reply" @click=${this.show_reply}></input>
|
<input type="button" value="Reply" @click=${this.show_reply}></input>
|
||||||
`;
|
`;
|
||||||
let self = this;
|
let self = this;
|
||||||
let body = this.raw ?
|
let body;
|
||||||
this.render_raw() :
|
switch (this.format) {
|
||||||
unsafeHTML(tfutils.markdown(content.text));
|
case 'raw':
|
||||||
|
body = this.render_raw();
|
||||||
|
break;
|
||||||
|
case 'md':
|
||||||
|
body = html`<code style="white-space: pre-wrap; overflow-wrap: anywhere">${content.text}</code>`;
|
||||||
|
break;
|
||||||
|
case 'message':
|
||||||
|
body = unsafeHTML(tfutils.markdown(content.text));
|
||||||
|
break;
|
||||||
|
}
|
||||||
let content_warning = html`
|
let content_warning = html`
|
||||||
<div class="content_warning" @click=${x => this.toggle_expanded(':cw')}>${content.contentWarning}</div>
|
<div class="content_warning" @click=${x => this.toggle_expanded(':cw')}>${content.contentWarning}</div>
|
||||||
`;
|
`;
|
||||||
let content_html =
|
let content_html =
|
||||||
html`
|
html`
|
||||||
|
${this.render_channels()}
|
||||||
<div @click=${this.body_click}>${body}</div>
|
<div @click=${this.body_click}>${body}</div>
|
||||||
${this.render_mentions()}
|
${this.render_mentions()}
|
||||||
`;
|
`;
|
||||||
@ -324,6 +386,8 @@ class TfMessageElement extends LitElement {
|
|||||||
` :
|
` :
|
||||||
content_warning :
|
content_warning :
|
||||||
content_html;
|
content_html;
|
||||||
|
let is_encrypted = this.decrypted ? html`<span style="align-self: center">🔓</span>` : undefined;
|
||||||
|
let style_background = this.decrypted ? 'rgba(255, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.1)';
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
code {
|
code {
|
||||||
@ -339,9 +403,10 @@ class TfMessageElement extends LitElement {
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px">
|
<div style="border: 1px solid black; background-color: ${style_background}; margin-top: 8px; padding: 16px">
|
||||||
<div style="display: flex; flex-direction: row">
|
<div style="display: flex; flex-direction: row">
|
||||||
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
|
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
|
||||||
|
${is_encrypted}
|
||||||
<span style="flex: 1"></span>
|
<span style="flex: 1"></span>
|
||||||
<span style="padding-right: 8px"><a target="_top" href=${'#' + self.message.id}>%</a> ${new Date(this.message.timestamp).toLocaleString()}</span>
|
<span style="padding-right: 8px"><a target="_top" href=${'#' + self.message.id}>%</a> ${new Date(this.message.timestamp).toLocaleString()}</span>
|
||||||
<span>${raw_button}</span>
|
<span>${raw_button}</span>
|
||||||
@ -364,9 +429,16 @@ class TfMessageElement extends LitElement {
|
|||||||
this.expanded[(this.message.id || '') + ':blog'] ?
|
this.expanded[(this.message.id || '') + ':blog'] ?
|
||||||
html`<div>${this.blog_data ? unsafeHTML(tfutils.markdown(this.blog_data)) : 'Loading...'}</div>` :
|
html`<div>${this.blog_data ? unsafeHTML(tfutils.markdown(this.blog_data)) : 'Loading...'}</div>` :
|
||||||
undefined;
|
undefined;
|
||||||
let body = this.raw ?
|
let body;
|
||||||
this.render_raw() :
|
switch (this.format) {
|
||||||
html`
|
case 'raw':
|
||||||
|
body = this.render_raw();
|
||||||
|
break;
|
||||||
|
case 'md':
|
||||||
|
body = content.summary;
|
||||||
|
break;
|
||||||
|
case 'message':
|
||||||
|
body = html`
|
||||||
<div
|
<div
|
||||||
style="border: 1px solid #fff; border-radius: 1em; padding: 8px; margin: 4px; cursor: pointer"
|
style="border: 1px solid #fff; border-radius: 1em; padding: 8px; margin: 4px; cursor: pointer"
|
||||||
@click=${x => self.toggle_expanded(':blog')}>
|
@click=${x => self.toggle_expanded(':blog')}>
|
||||||
@ -378,6 +450,8 @@ class TfMessageElement extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
${payload}
|
${payload}
|
||||||
`;
|
`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
code {
|
code {
|
||||||
@ -426,7 +500,14 @@ class TfMessageElement extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
} else if (typeof(this.message.content) == 'string') {
|
} else if (typeof(this.message.content) == 'string') {
|
||||||
|
if (this.decrypted) {
|
||||||
|
return small_frame(html`<span>🔓</span><pre>${JSON.stringify(this.decrypted, null, 2)}</pre>`);
|
||||||
|
} else if (this.decrypted === undefined) {
|
||||||
|
this.try_decrypt(content);
|
||||||
|
return small_frame(html`<span>🔐</span>`);
|
||||||
|
} else {
|
||||||
return small_frame(html`<span>🔒</span>`);
|
return small_frame(html`<span>🔒</span>`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return small_frame(html`<div><b>type</b>: ${content.type}</div>`);
|
return small_frame(html`<div><b>type</b>: ${content.type}</div>`);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ class TfNewsElement extends LitElement {
|
|||||||
following: {type: Array},
|
following: {type: Array},
|
||||||
drafts: {type: Object},
|
drafts: {type: Object},
|
||||||
expanded: {type: Object},
|
expanded: {type: Object},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = styles;
|
static styles = styles;
|
||||||
|
@ -11,7 +11,7 @@ class TfProfileElement extends LitElement {
|
|||||||
id: {type: String},
|
id: {type: String},
|
||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
size: {type: Number},
|
size: {type: Number},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = styles;
|
static styles = styles;
|
||||||
@ -33,7 +33,7 @@ class TfProfileElement extends LitElement {
|
|||||||
contact: this.id,
|
contact: this.id,
|
||||||
}, change)).catch(function(error) {
|
}, change)).catch(function(error) {
|
||||||
alert(error?.message);
|
alert(error?.message);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
follow() {
|
follow() {
|
||||||
@ -148,6 +148,10 @@ class TfProfileElement extends LitElement {
|
|||||||
<div><label for="description">Description:</label></div>
|
<div><label for="description">Description:</label></div>
|
||||||
<textarea id="description" @input=${event => this.editing = Object.assign({}, this.editing, {description: event.srcElement.value})}>${this.editing.description}</textarea>
|
<textarea id="description" @input=${event => this.editing = Object.assign({}, this.editing, {description: event.srcElement.value})}>${this.editing.description}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<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>
|
||||||
<input type="button" value="Attach Image" @click=${this.attach_image}></input>
|
<input type="button" value="Attach Image" @click=${this.attach_image}></input>
|
||||||
</div>` : null;
|
</div>` : null;
|
||||||
let image = typeof(profile.image) == 'string' ? profile.image : profile.image?.link;
|
let image = typeof(profile.image) == 'string' ? profile.image : profile.image?.link;
|
||||||
|
@ -36,4 +36,13 @@ img {
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.img_caption {
|
||||||
|
color: #888;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.img_caption::after {
|
||||||
|
content: ' ±';
|
||||||
|
}
|
||||||
`;
|
`;
|
@ -9,7 +9,7 @@ class TfTabConnectionsElement extends LitElement {
|
|||||||
connections: {type: Array},
|
connections: {type: Array},
|
||||||
stored_connections: {type: Array},
|
stored_connections: {type: Array},
|
||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -71,7 +71,7 @@ class TfTabConnectionsElement extends LitElement {
|
|||||||
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
|
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
|
||||||
${this.render_connection_summary(connection)}
|
${this.render_connection_summary(connection)}
|
||||||
</li>
|
</li>
|
||||||
`
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async forget_stored_connection(connection) {
|
async forget_stored_connection(connection) {
|
||||||
|
65
apps/ssb/tf-tab-mentions.js
Normal file
65
apps/ssb/tf-tab-mentions.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||||
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
|
class TfTabMentionsElement extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
whoami: {type: String},
|
||||||
|
users: {type: Object},
|
||||||
|
following: {type: Array},
|
||||||
|
expanded: {type: Object},
|
||||||
|
messages: {type: Array},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = styles;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
let self = this;
|
||||||
|
this.whoami = null;
|
||||||
|
this.users = {};
|
||||||
|
this.following = [];
|
||||||
|
this.expanded = {};
|
||||||
|
this.messages = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
console.log('Loading...', this.whoami);
|
||||||
|
let results = await tfrpc.rpc.query(`
|
||||||
|
SELECT messages.*
|
||||||
|
FROM messages_fts(?)
|
||||||
|
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||||
|
JOIN json_each(?) AS following ON messages.author = following.value
|
||||||
|
WHERE messages.author != ?
|
||||||
|
ORDER BY timestamp DESC limit 20
|
||||||
|
`,
|
||||||
|
['"' + this.whoami.replace('"', '""') + '"', JSON.stringify(this.following), this.whoami]);
|
||||||
|
console.log('Done.');
|
||||||
|
this.messages = results;
|
||||||
|
}
|
||||||
|
|
||||||
|
on_expand(event) {
|
||||||
|
if (event.detail.expanded) {
|
||||||
|
let expand = {};
|
||||||
|
expand[event.detail.id] = true;
|
||||||
|
this.expanded = Object.assign({}, this.expanded, expand);
|
||||||
|
} else {
|
||||||
|
delete this.expanded[event.detail.id];
|
||||||
|
this.expanded = Object.assign({}, this.expanded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let self = this;
|
||||||
|
if (!this.loading) {
|
||||||
|
this.loading = true;
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} @tf-expand=${this.on_expand}></tf-news>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define('tf-tab-mentions', TfTabMentionsElement);
|
159
apps/ssb/tf-tab-news-feed.js
Normal file
159
apps/ssb/tf-tab-news-feed.js
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import {LitElement, html, unsafeHTML, until} from './lit-all.min.js';
|
||||||
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
|
class TfTabNewsFeedElement extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
whoami: {type: String},
|
||||||
|
users: {type: Object},
|
||||||
|
hash: {type: String},
|
||||||
|
following: {type: Array},
|
||||||
|
messages: {type: Array},
|
||||||
|
drafts: {type: Object},
|
||||||
|
expanded: {type: Object},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = styles;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
let self = this;
|
||||||
|
this.whoami = null;
|
||||||
|
this.users = {};
|
||||||
|
this.hash = '#';
|
||||||
|
this.following = [];
|
||||||
|
this.drafts = {};
|
||||||
|
this.expanded = {};
|
||||||
|
this.start_time = new Date().valueOf() - 24 * 60 * 60 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetch_messages() {
|
||||||
|
if (this.hash.startsWith('#@')) {
|
||||||
|
let r = await tfrpc.rpc.query(
|
||||||
|
`
|
||||||
|
WITH mine AS (SELECT messages.*
|
||||||
|
FROM messages
|
||||||
|
WHERE messages.author = ?
|
||||||
|
ORDER BY sequence DESC
|
||||||
|
LIMIT 20)
|
||||||
|
SELECT messages.*
|
||||||
|
FROM mine
|
||||||
|
JOIN messages_refs ON mine.id = messages_refs.ref
|
||||||
|
JOIN messages ON messages_refs.message = messages.id
|
||||||
|
UNION
|
||||||
|
SELECT * FROM mine
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
this.hash.substring(1),
|
||||||
|
]);
|
||||||
|
return r;
|
||||||
|
} else if (this.hash.startsWith('#%')) {
|
||||||
|
return await tfrpc.rpc.query(
|
||||||
|
`
|
||||||
|
SELECT messages.*
|
||||||
|
FROM messages
|
||||||
|
WHERE id = ?1
|
||||||
|
UNION
|
||||||
|
SELECT messages.*
|
||||||
|
FROM messages JOIN messages_refs
|
||||||
|
ON messages.id = messages_refs.message
|
||||||
|
WHERE messages_refs.ref = ?1
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
this.hash.substring(1),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return await tfrpc.rpc.query(
|
||||||
|
`
|
||||||
|
WITH news AS (SELECT messages.*
|
||||||
|
FROM messages
|
||||||
|
JOIN json_each(?) AS following ON messages.author = following.value
|
||||||
|
WHERE messages.timestamp > ? AND messages.timestamp < ?
|
||||||
|
ORDER BY messages.timestamp DESC)
|
||||||
|
SELECT messages.*
|
||||||
|
FROM news
|
||||||
|
JOIN messages_refs ON news.id = messages_refs.ref
|
||||||
|
JOIN messages ON messages_refs.message = messages.id
|
||||||
|
UNION
|
||||||
|
SELECT messages.*
|
||||||
|
FROM news
|
||||||
|
JOIN messages_refs ON news.id = messages_refs.message
|
||||||
|
JOIN messages ON messages_refs.ref = messages.id
|
||||||
|
UNION
|
||||||
|
SELECT news.* FROM news
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
JSON.stringify(this.following),
|
||||||
|
this.start_time,
|
||||||
|
/*
|
||||||
|
** Don't show messages more than a day into the future to prevent
|
||||||
|
** messages with far-future timestamps from staying at the top forever.
|
||||||
|
*/
|
||||||
|
new Date().valueOf() + 24 * 60 * 60 * 1000,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async load_more() {
|
||||||
|
let last_start_time = this.start_time;
|
||||||
|
this.start_time = last_start_time - 24 * 60 * 60 * 1000;
|
||||||
|
let more = await tfrpc.rpc.query(
|
||||||
|
`
|
||||||
|
WITH news AS (SELECT messages.*
|
||||||
|
FROM messages
|
||||||
|
JOIN json_each(?) AS following ON messages.author = following.value
|
||||||
|
WHERE messages.timestamp > ?
|
||||||
|
AND messages.timestamp <= ?
|
||||||
|
ORDER BY messages.timestamp DESC)
|
||||||
|
SELECT messages.*
|
||||||
|
FROM news
|
||||||
|
JOIN messages_refs ON news.id = messages_refs.ref
|
||||||
|
JOIN messages ON messages_refs.message = messages.id
|
||||||
|
UNION
|
||||||
|
SELECT messages.*
|
||||||
|
FROM news
|
||||||
|
JOIN messages_refs ON news.id = messages_refs.message
|
||||||
|
JOIN messages ON messages_refs.ref = messages.id
|
||||||
|
UNION
|
||||||
|
SELECT news.* FROM news
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
JSON.stringify(this.following),
|
||||||
|
this.start_time,
|
||||||
|
last_start_time,
|
||||||
|
]);
|
||||||
|
this.messages = [...more, ...this.messages];
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.messages ||
|
||||||
|
this._messages_hash !== this.hash ||
|
||||||
|
this._messages_following !== this.following) {
|
||||||
|
console.log(`loading messages for ${this.whoami}`);
|
||||||
|
let self = this;
|
||||||
|
this.messages = [];
|
||||||
|
this._messages_hash = this.hash;
|
||||||
|
this._messages_following = this.following;
|
||||||
|
this.fetch_messages().then(function(messages) {
|
||||||
|
self.messages = messages;
|
||||||
|
console.log(`loading mesages done for ${self.whoami}`);
|
||||||
|
}).catch(function(error) {
|
||||||
|
alert(JSON.stringify(error, null, 2));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let more;
|
||||||
|
if (!this.hash.startsWith('#@') && !this.hash.startsWith('#%')) {
|
||||||
|
more = html`
|
||||||
|
<input type="button" value="Load More" @click=${this.load_more}></input>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<tf-news id="news" whoami=${this.whoami} .users=${this.users} .messages=${this.messages} .following=${this.following} .drafts=${this.drafts} .expanded=${this.expanded}></tf-news>
|
||||||
|
${more}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('tf-tab-news-feed', TfTabNewsFeedElement);
|
@ -2,114 +2,6 @@ import {LitElement, html, unsafeHTML, until} from './lit-all.min.js';
|
|||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
import {styles} from './tf-styles.js';
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
class TfTabNewsFeedElement extends LitElement {
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
whoami: {type: String},
|
|
||||||
users: {type: Object},
|
|
||||||
hash: {type: String},
|
|
||||||
following: {type: Array},
|
|
||||||
messages: {type: Array},
|
|
||||||
drafts: {type: Object},
|
|
||||||
expanded: {type: Object},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = styles;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
let self = this;
|
|
||||||
this.whoami = null;
|
|
||||||
this.users = {};
|
|
||||||
this.hash = '#';
|
|
||||||
this.following = [];
|
|
||||||
this.drafts = {};
|
|
||||||
this.expanded = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetch_messages() {
|
|
||||||
if (this.hash.startsWith('#@')) {
|
|
||||||
let r = await tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
WITH mine AS (SELECT messages.*
|
|
||||||
FROM messages
|
|
||||||
WHERE messages.author = ?
|
|
||||||
ORDER BY sequence DESC
|
|
||||||
LIMIT 20)
|
|
||||||
SELECT messages.*
|
|
||||||
FROM mine
|
|
||||||
JOIN messages_refs ON mine.id = messages_refs.ref
|
|
||||||
JOIN messages ON messages_refs.message = messages.id
|
|
||||||
UNION
|
|
||||||
SELECT * FROM mine
|
|
||||||
`,
|
|
||||||
[
|
|
||||||
this.hash.substring(1),
|
|
||||||
]);
|
|
||||||
return r;
|
|
||||||
} else if (this.hash.startsWith('#%')) {
|
|
||||||
return await tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
SELECT messages.*
|
|
||||||
FROM messages
|
|
||||||
WHERE id = ?1
|
|
||||||
UNION
|
|
||||||
SELECT messages.*
|
|
||||||
FROM messages JOIN messages_refs
|
|
||||||
ON messages.id = messages_refs.message
|
|
||||||
WHERE messages_refs.ref = ?1
|
|
||||||
`,
|
|
||||||
[
|
|
||||||
this.hash.substring(1),
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
return await tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
WITH news AS (SELECT messages.*
|
|
||||||
FROM messages
|
|
||||||
JOIN json_each(?) AS following ON messages.author = following.value
|
|
||||||
WHERE messages.timestamp > ?
|
|
||||||
ORDER BY messages.timestamp DESC)
|
|
||||||
SELECT messages.*
|
|
||||||
FROM news
|
|
||||||
JOIN messages_refs ON news.id = messages_refs.ref
|
|
||||||
JOIN messages ON messages_refs.message = messages.id
|
|
||||||
UNION
|
|
||||||
SELECT messages.*
|
|
||||||
FROM news
|
|
||||||
JOIN messages_refs ON news.id = messages_refs.message
|
|
||||||
JOIN messages ON messages_refs.ref = messages.id
|
|
||||||
UNION
|
|
||||||
SELECT news.* FROM news
|
|
||||||
`,
|
|
||||||
[
|
|
||||||
JSON.stringify(this.following),
|
|
||||||
new Date().valueOf() - 24 * 60 * 60 * 1000,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (!this.messages ||
|
|
||||||
this._messages_hash !== this.hash ||
|
|
||||||
this._messages_following !== this.following) {
|
|
||||||
console.log(`loading messages for ${this.whoami}`);
|
|
||||||
let self = this;
|
|
||||||
this.messages = [];
|
|
||||||
this._messages_hash = this.hash;
|
|
||||||
this._messages_following = this.following;
|
|
||||||
this.fetch_messages().then(function(messages) {
|
|
||||||
self.messages = messages;
|
|
||||||
console.log(`loading mesages done for ${self.whoami}`);
|
|
||||||
}).catch(function(error) {
|
|
||||||
alert(JSON.stringify(error, null, 2));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return html`<tf-news id="news" whoami=${this.whoami} .users=${this.users} .messages=${this.messages} .following=${this.following} .drafts=${this.drafts} .expanded=${this.expanded}></tf-news>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TfTabNewsElement extends LitElement {
|
class TfTabNewsElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
@ -120,7 +12,7 @@ class TfTabNewsElement extends LitElement {
|
|||||||
following: {type: Array},
|
following: {type: Array},
|
||||||
drafts: {type: Object},
|
drafts: {type: Object},
|
||||||
expanded: {type: Object},
|
expanded: {type: Object},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = styles;
|
static styles = styles;
|
||||||
@ -141,9 +33,19 @@ class TfTabNewsElement extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
document.body.addEventListener('keypress', this.on_keypress.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
document.body.removeEventListener('keypress', this.on_keypress.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
show_more() {
|
show_more() {
|
||||||
let unread = this.unread;
|
let unread = this.unread;
|
||||||
let news = this.renderRoot?.getElementById('news');
|
let news = this.shadowRoot?.getElementById('news');
|
||||||
if (news) {
|
if (news) {
|
||||||
console.log('injecting messages', news.messages);
|
console.log('injecting messages', news.messages);
|
||||||
news.messages = Object.values(Object.fromEntries([...this.unread, ...news.messages].map(x => [x.id, x])));
|
news.messages = Object.values(Object.fromEntries([...this.unread, ...news.messages].map(x => [x.id, x])));
|
||||||
@ -193,6 +95,13 @@ class TfTabNewsElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
on_keypress(event) {
|
||||||
|
if (event.target === document.body &&
|
||||||
|
event.key == '.') {
|
||||||
|
this.show_more();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let profile = this.hash.startsWith('#@') ?
|
let profile = this.hash.startsWith('#@') ?
|
||||||
html`<tf-profile id=${this.hash.substring(1)} whoami=${this.whoami} .users=${this.users}></tf-profile>` : undefined;
|
html`<tf-profile id=${this.hash.substring(1)} whoami=${this.whoami} .users=${this.users}></tf-profile>` : undefined;
|
||||||
@ -200,12 +109,11 @@ class TfTabNewsElement extends LitElement {
|
|||||||
<div><input type="button" value=${this.new_messages_text()} @click=${this.show_more}></input></div>
|
<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>
|
<a target="_top" href="#" ?hidden=${this.hash.length <= 1}>🏠Home</a>
|
||||||
<div>Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!</div>
|
<div>Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!</div>
|
||||||
<div><tf-compose whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} @tf-draft=${this.draft}></tf-compose></div>
|
<div><tf-compose id="tf-compose" whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} @tf-draft=${this.draft}></tf-compose></div>
|
||||||
${profile}
|
${profile}
|
||||||
<tf-tab-news-feed id="news" whoami=${this.whoami} .users=${this.users} .following=${this.following} hash=${this.hash} .drafts=${this.drafts} .expanded=${this.expanded} @tf-draft=${this.draft} @tf-expand=${this.on_expand}></tf-tab-news-feed>
|
<tf-tab-news-feed id="news" whoami=${this.whoami} .users=${this.users} .following=${this.following} hash=${this.hash} .drafts=${this.drafts} .expanded=${this.expanded} @tf-draft=${this.draft} @tf-expand=${this.on_expand}></tf-tab-news-feed>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('tf-tab-news-feed', TfTabNewsFeedElement);
|
|
||||||
customElements.define('tf-tab-news', TfTabNewsElement);
|
customElements.define('tf-tab-news', TfTabNewsElement);
|
114
apps/ssb/tf-tab-query.js
Normal file
114
apps/ssb/tf-tab-query.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||||
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
|
class TfTabQueryElement extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
whoami: {type: String},
|
||||||
|
users: {type: Object},
|
||||||
|
following: {type: Array},
|
||||||
|
query: {type: String},
|
||||||
|
expanded: {type: Object},
|
||||||
|
results: {type: Array},
|
||||||
|
error: {type: Object},
|
||||||
|
duration: {type: Number},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = styles;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
let self = this;
|
||||||
|
this.whoami = null;
|
||||||
|
this.users = {};
|
||||||
|
this.following = [];
|
||||||
|
this.expanded = {};
|
||||||
|
this.duration = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(query) {
|
||||||
|
console.log('Searching...', this.whoami, query);
|
||||||
|
this.results = [];
|
||||||
|
this.error = undefined;
|
||||||
|
this.duration = undefined;
|
||||||
|
let search = this.renderRoot.getElementById('search');
|
||||||
|
if (search) {
|
||||||
|
search.value = query;
|
||||||
|
search.focus();
|
||||||
|
}
|
||||||
|
await tfrpc.rpc.setHash('#sql=' + encodeURIComponent(query));
|
||||||
|
let start_time = new Date();
|
||||||
|
try {
|
||||||
|
this.results = await tfrpc.rpc.query(query, [])
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
let end_time = new Date();
|
||||||
|
this.duration = (end_time - start_time).valueOf();
|
||||||
|
console.log('Done.');
|
||||||
|
search = this.renderRoot.getElementById('search');
|
||||||
|
if (search) {
|
||||||
|
search.value = query;
|
||||||
|
search.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
search_keydown(event) {
|
||||||
|
if (event.keyCode == 13 && event.ctrlKey) {
|
||||||
|
this.query = this.renderRoot.getElementById('search').value;
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
on_expand(event) {
|
||||||
|
if (event.detail.expanded) {
|
||||||
|
let expand = {};
|
||||||
|
expand[event.detail.id] = true;
|
||||||
|
this.expanded = Object.assign({}, this.expanded, expand);
|
||||||
|
} else {
|
||||||
|
delete this.expanded[event.detail.id];
|
||||||
|
this.expanded = Object.assign({}, this.expanded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render_results() {
|
||||||
|
if (!this.results?.length) {
|
||||||
|
return html`<div>No results.</div>`;
|
||||||
|
} else {
|
||||||
|
let keys = Object.keys(this.results[0]).sort();
|
||||||
|
return html`<table style="width: 100%; max-width: 100%">
|
||||||
|
<tr>${keys.map(key => html`<th>${key}</th>`)}</tr>
|
||||||
|
${this.results.map(row => html`<tr>${keys.map(key => html`<td>${row[key]}</td>`)}</tr>`)}
|
||||||
|
</table>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render_error() {
|
||||||
|
if (this.error) {
|
||||||
|
return html`<h2 style="color: red">${this.error.message}</h2>
|
||||||
|
<pre style="color: red">${this.error.stack}</pre>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.query !== this.last_query) {
|
||||||
|
this.last_query = this.query;
|
||||||
|
this.search(this.query);
|
||||||
|
}
|
||||||
|
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>
|
||||||
|
<div ?hidden=${this.duration === undefined}>Took ${this.duration / 1000.0} seconds.</div>
|
||||||
|
<div ?hidden=${this.duration !== undefined}>Executing...</div>
|
||||||
|
${this.render_error()}
|
||||||
|
${this.render_results()}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('tf-tab-query', TfTabQueryElement);
|
@ -9,7 +9,8 @@ class TfTabSearchElement extends LitElement {
|
|||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
following: {type: Array},
|
following: {type: Array},
|
||||||
query: {type: String},
|
query: {type: String},
|
||||||
}
|
expanded: {type: Object},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = styles;
|
static styles = styles;
|
||||||
@ -20,6 +21,7 @@ class TfTabSearchElement extends LitElement {
|
|||||||
this.whoami = null;
|
this.whoami = null;
|
||||||
this.users = {};
|
this.users = {};
|
||||||
this.following = [];
|
this.following = [];
|
||||||
|
this.expanded = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(query) {
|
async search(query) {
|
||||||
@ -55,8 +57,20 @@ class TfTabSearchElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
on_expand(event) {
|
||||||
|
if (event.detail.expanded) {
|
||||||
|
let expand = {};
|
||||||
|
expand[event.detail.id] = true;
|
||||||
|
this.expanded = Object.assign({}, this.expanded, expand);
|
||||||
|
} else {
|
||||||
|
delete this.expanded[event.detail.id];
|
||||||
|
this.expanded = Object.assign({}, this.expanded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.query !== this.last_query) {
|
if (this.query !== this.last_query) {
|
||||||
|
this.last_query = this.query;
|
||||||
this.search(this.query);
|
this.search(this.query);
|
||||||
}
|
}
|
||||||
let self = this;
|
let self = this;
|
||||||
@ -65,7 +79,7 @@ class TfTabSearchElement extends LitElement {
|
|||||||
<input type="text" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
|
<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>
|
<input type="button" value="Search" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}></input>
|
||||||
</div>
|
</div>
|
||||||
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users}></tf-news>
|
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} @tf-expand=${this.on_expand}></tf-news>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
apps/ssb/tf-tag.js
Normal file
24
apps/ssb/tf-tag.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||||
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
|
class TfTagElement extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
tag: {type: String},
|
||||||
|
count: {type: Number},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = styles;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let number = this.count ? html` (${this.count})` : undefined;
|
||||||
|
return html`<a href="#q=${this.tag}" style="display: inline-block; margin: 3px; border: 1px solid black; background-color: #444; padding: 4px; border-radius: 3px">${this.tag}${number}</a>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('tf-tag', TfTagElement);
|
@ -7,7 +7,7 @@ class TfUserElement extends LitElement {
|
|||||||
return {
|
return {
|
||||||
id: {type: String},
|
id: {type: String},
|
||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = styles;
|
static styles = styles;
|
||||||
|
@ -1,9 +1,54 @@
|
|||||||
import * as linkify from './commonmark-linkify.js';
|
import * as linkify from './commonmark-linkify.js';
|
||||||
import * as hashtagify from './commonmark-hashtag.js';
|
import * as hashtagify from './commonmark-hashtag.js';
|
||||||
|
|
||||||
|
function image(node, entering) {
|
||||||
|
if (node.firstChild?.type === 'text' &&
|
||||||
|
node.firstChild.literal.startsWith('video:')) {
|
||||||
|
if (entering) {
|
||||||
|
this.lit('<video style="max-width: 100%; max-height: 480px" title="' + this.esc(node.firstChild?.literal) + '" controls>');
|
||||||
|
this.lit('<source src="' + this.esc(node.destination) + '"></source>');
|
||||||
|
this.disableTags += 1;
|
||||||
|
} else {
|
||||||
|
this.disableTags -= 1;
|
||||||
|
this.lit('</video>');
|
||||||
|
}
|
||||||
|
} else if (node.firstChild?.type === 'text' &&
|
||||||
|
node.firstChild.literal.startsWith('audio:')) {
|
||||||
|
if (entering) {
|
||||||
|
this.lit('<audio style="height: 32px; max-width: 100%" title="' + this.esc(node.firstChild?.literal) + '" controls>');
|
||||||
|
this.lit('<source src="' + this.esc(node.destination) + '"></source>');
|
||||||
|
this.disableTags += 1;
|
||||||
|
} else {
|
||||||
|
this.disableTags -= 1;
|
||||||
|
this.lit('</audio>');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (entering) {
|
||||||
|
if (this.disableTags === 0) {
|
||||||
|
this.lit('<div class="img_caption">' + this.esc(node.firstChild?.literal || node.destination) + '</div>');
|
||||||
|
if (this.options.safe && potentiallyUnsafe(node.destination)) {
|
||||||
|
this.lit('<img src="" alt="');
|
||||||
|
} else {
|
||||||
|
this.lit('<img src="' + this.esc(node.destination) + '" alt="');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.disableTags += 1;
|
||||||
|
} else {
|
||||||
|
this.disableTags -= 1;
|
||||||
|
if (this.disableTags === 0) {
|
||||||
|
if (node.title) {
|
||||||
|
this.lit('" title="' + this.esc(node.title));
|
||||||
|
}
|
||||||
|
this.lit('" />');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function markdown(md) {
|
export function markdown(md) {
|
||||||
var reader = new commonmark.Parser({safe: true});
|
var reader = new commonmark.Parser({safe: true});
|
||||||
var writer = new commonmark.HtmlRenderer();
|
var writer = new commonmark.HtmlRenderer();
|
||||||
|
writer.image = image;
|
||||||
var parsed = reader.parse(md || '');
|
var parsed = reader.parse(md || '');
|
||||||
parsed = linkify.transform(parsed);
|
parsed = linkify.transform(parsed);
|
||||||
parsed = hashtagify.transform(parsed);
|
parsed = hashtagify.transform(parsed);
|
||||||
|
@ -114,13 +114,12 @@ class TodoListElement extends LitElement {
|
|||||||
@change=${event => self.input_change(event, item)}
|
@change=${event => self.input_change(event, item)}
|
||||||
@keydown=${event => self.input_keydown(event, item)}
|
@keydown=${event => self.input_keydown(event, item)}
|
||||||
@blur=${x => self.input_blur(item)}></input>
|
@blur=${x => self.input_blur(item)}></input>
|
||||||
<span @click=${x => self.remove_item(item)}>x</span></div>
|
<span @click=${x => self.remove_item(item)} style="cursor: pointer">❎</span></div>
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
return html`
|
return html`
|
||||||
<div><input type="checkbox" ?checked=${item.x} @change=${x => self.handle_check(x, item)}></input>
|
<div><input type="checkbox" ?checked=${item.x} @change=${x => self.handle_check(x, item)}></input>
|
||||||
<span @click=${x => self.editing = index}>${item.text}</span>
|
<span @click=${x => self.editing = index}>${item.text || '(empty)'}</span>
|
||||||
<span @click=${x => self.remove_item(item)} style="cursor: pointer">❎</span></div>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,7 +174,8 @@ class TodoListElement extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<div style="border: 3px solid black; padding: 8px; margin: 8px; border-radius: 8px; background-color: #444">
|
<div style="border: 3px solid black; padding: 8px; margin: 8px; border-radius: 8px; background-color: #444">
|
||||||
${name}
|
${name}
|
||||||
${(this.items || []).map(x => self.render_item(x))}
|
${(this.items || []).filter(item => !item.x).map(x => self.render_item(x))}
|
||||||
|
${(this.items || []).filter(item => item.x).map(x => self.render_item(x))}
|
||||||
<button @click=${self.add_item}>+ Item</button>
|
<button @click=${self.add_item}>+ Item</button>
|
||||||
<button @click=${self.remove_list}>- List</button>
|
<button @click=${self.remove_list}>- List</button>
|
||||||
</div>
|
</div>
|
||||||
|
12
core/app.js
12
core/app.js
@ -61,8 +61,7 @@ function socket(request, response, client) {
|
|||||||
let process;
|
let process;
|
||||||
let options = {};
|
let options = {};
|
||||||
let credentials = auth.query(request.headers);
|
let credentials = auth.query(request.headers);
|
||||||
let refresh_token = credentials?.refresh?.token;
|
let refresh = auth.make_refresh(credentials);
|
||||||
let refresh_interval = credentials?.refresh?.interval;
|
|
||||||
|
|
||||||
response.onClose = async function() {
|
response.onClose = async function() {
|
||||||
if (process && process.task) {
|
if (process && process.task) {
|
||||||
@ -125,10 +124,15 @@ function socket(request, response, client) {
|
|||||||
options.credentials = credentials;
|
options.credentials = credentials;
|
||||||
options.packageOwner = packageOwner;
|
options.packageOwner = packageOwner;
|
||||||
options.packageName = packageName;
|
options.packageName = packageName;
|
||||||
|
options.url = message.url;
|
||||||
let sessionId = makeSessionId();
|
let sessionId = makeSessionId();
|
||||||
if (blobId) {
|
if (blobId) {
|
||||||
|
if (message.edit_only) {
|
||||||
|
response.send(JSON.stringify({action: 'ready', edit_only: true}), 0x1);
|
||||||
|
} else {
|
||||||
process = await core.getSessionProcessBlob(blobId, sessionId, options);
|
process = await core.getSessionProcessBlob(blobId, sessionId, options);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (process) {
|
if (process) {
|
||||||
process.app.readOutput(function(message) {
|
process.app.readOutput(function(message) {
|
||||||
response.send(JSON.stringify(message), 0x1);
|
response.send(JSON.stringify(message), 0x1);
|
||||||
@ -198,9 +202,9 @@ function socket(request, response, client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refresh_token) {
|
if (refresh) {
|
||||||
return {
|
return {
|
||||||
'Set-Cookie': `session=${refresh_token}; path=/; Max-Age=${refresh_interval}; Secure; SameSite=Strict`,
|
'Set-Cookie': `session=${refresh.token}; path=/; Max-Age=${refresh.interval}; Secure; SameSite=Strict`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
126
core/auth.html
126
core/auth.html
@ -2,18 +2,130 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Tilde Friends Sign-in</title>
|
<title>Tilde Friends Sign-in</title>
|
||||||
<script>
|
|
||||||
function showHideConfirm() {
|
|
||||||
document.getElementById("confirmPassword").style.display = document.getElementById("register").checked ? "block" : "none";
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<link type="text/css" rel="stylesheet" href="/static/style.css">
|
<link type="text/css" rel="stylesheet" href="/static/style.css">
|
||||||
<link type="image/png" rel="shortcut icon" href="/static/favicon.png">
|
<link type="image/png" rel="shortcut icon" href="/static/favicon.png">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<!--HEAD-->
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1 style="text-align: center">Tilde Friends Sign-in</h1>
|
<h1 style="text-align: center">Tilde Friends Sign-in</h1>
|
||||||
<div id="content"><!--SESSION--></div>
|
<tf-auth id="auth"></tf-auth>
|
||||||
|
<script>window.litDisableBundleWarning = true;</script>
|
||||||
|
<script type="module">
|
||||||
|
import {LitElement, html} from '/static/lit/lit-all.min.js';
|
||||||
|
let g_data = $AUTH_DATA;
|
||||||
|
let app = document.getElementById('auth');
|
||||||
|
Object.assign(app, g_data);
|
||||||
|
|
||||||
|
class TfAuthElement extends LitElement {
|
||||||
|
static get_properties() {
|
||||||
|
return {
|
||||||
|
name: {type: String},
|
||||||
|
tab: {type: String},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.tab = 'login';
|
||||||
|
}
|
||||||
|
|
||||||
|
tab_changed(name) {
|
||||||
|
this.tab = name;
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let self = this;
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
[name="tab"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
[name="tab"]+label {
|
||||||
|
background-color: #586e75;
|
||||||
|
padding: 1em;
|
||||||
|
display: inline-block;
|
||||||
|
flex: 1 0;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
[name="tab"]+label:hover {
|
||||||
|
background-color: #dc322f;
|
||||||
|
}
|
||||||
|
[name="tab"]:checked+label {
|
||||||
|
background-color: #93a1a1;
|
||||||
|
border: 2px solid #eee8d5;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: #f00;
|
||||||
|
border: 1px solid #f00;
|
||||||
|
margin: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
form label {
|
||||||
|
padding-top: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
form input {
|
||||||
|
font-size: x-large;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
input[type="submit"] {
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div style="display: flex; flex-direction: column; max-width: 1280px; margin: auto">
|
||||||
|
<h1 ?hidden=${this.name}>Welcome.</h1>
|
||||||
|
<h1 ?hidden=${this.name === undefined}>Welcome, ${this.name}.</h1>
|
||||||
|
|
||||||
|
<div style="display: flex; flex-direction: row; width: 100%">
|
||||||
|
<input type="radio" name="tab" id="login" value="Login" ?checked=${this.tab == 'login'} @change=${() => self.tab_changed('login')}></input>
|
||||||
|
<label for="login" id="login_label">Login</label>
|
||||||
|
|
||||||
|
<input type="radio" name="tab" id="register" value="Register" ?checked=${this.tab == 'register'} @change=${() => self.tab_changed('register')}></input>
|
||||||
|
<label for="register" id="register_label">Register</label>
|
||||||
|
|
||||||
|
<input type="radio" name="tab" id="guest" value="Guest" ?checked=${this.tab == 'guest'} @change=${() => self.tab_changed('guest')}></input>
|
||||||
|
<label for="guest" id="guest_label">Guest</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ?hidden=${this.tab != 'login' && this.tab != 'register'}>
|
||||||
|
<div id="error" ?hidden=${this.error === undefined} class="error">
|
||||||
|
${this.error}
|
||||||
|
</div>
|
||||||
|
<form method="POST" style="display: flex; flex-direction: column">
|
||||||
|
<label for="name">Name:</label>
|
||||||
|
<input type="text" id="name" name="name"></input>
|
||||||
|
|
||||||
|
<label for="password">Password:</label>
|
||||||
|
<input type="password" id="password" name="password"></input>
|
||||||
|
|
||||||
|
<label ?hidden=${this.tab != 'register'} for="confirm">Confirm Password:</label>
|
||||||
|
<input ?hidden=${this.tab != 'register'} type="password" id="confirm" name="confirm"></input>
|
||||||
|
|
||||||
|
<input id="loginButton" type="submit" name="submit" value="Login"></input>
|
||||||
|
<input type="hidden" name="register" value="${this.tab == 'register' ? 1 : 0}"></input>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div ?hidden=${this.tab != 'guest'}>
|
||||||
|
<form method="POST" style="display: flex; flex-direction: column">
|
||||||
|
<input type="submit" id="guestButton" name="guestButton" value="Proceed as Guest"></input>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ?hidden=${this.have_administrator} class="notice">
|
||||||
|
There is currently no administrator. You will be made administrator.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Code of Conduct</h2>
|
||||||
|
<textarea readonly rows="20" cols="80" style="resize: none">${this.code_of_conduct}</textarea>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define('tf-auth', TfAuthElement);
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
66
core/auth.js
66
core/auth.js
@ -121,6 +121,7 @@ function handler(request, response) {
|
|||||||
|
|
||||||
let formData = form.decodeForm(request.query);
|
let formData = form.decodeForm(request.query);
|
||||||
|
|
||||||
|
print(request.method, utf8Decode(request.body), JSON.stringify(formData));
|
||||||
if (request.method == "POST" || formData.submit) {
|
if (request.method == "POST" || formData.submit) {
|
||||||
sessionIsNew = true;
|
sessionIsNew = true;
|
||||||
formData = form.decodeForm(utf8Decode(request.body), formData);
|
formData = form.decodeForm(utf8Decode(request.body), formData);
|
||||||
@ -178,46 +179,16 @@ function handler(request, response) {
|
|||||||
} else {
|
} else {
|
||||||
File.readFile("core/auth.html").then(function(data) {
|
File.readFile("core/auth.html").then(function(data) {
|
||||||
let html = utf8Decode(data);
|
let html = utf8Decode(data);
|
||||||
let contents = "";
|
let auth_data = {
|
||||||
|
session_is_new: sessionIsNew,
|
||||||
if (entry) {
|
name: entry?.name,
|
||||||
if (sessionIsNew) {
|
error: loginError,
|
||||||
contents += '<div>Welcome back, ' + entry.name + '.</div>\n';
|
code_of_conduct: core.globalSettings.code_of_conduct,
|
||||||
} else {
|
have_administrator: !noAdministrator(),
|
||||||
contents += '<div>You are already logged in, ' + entry.name + '.</div>\n';
|
};
|
||||||
}
|
html = utf8Encode(html.replace('$AUTH_DATA', JSON.stringify(auth_data)));
|
||||||
contents += '<div><a href="/login/logout">Logout</a></div>\n';
|
response.writeHead(200, {"Content-Type": "text/html; charset=utf-8", "Set-Cookie": cookie, "Content-Length": html.length});
|
||||||
} else {
|
response.end(html);
|
||||||
contents += '<form method="POST">\n';
|
|
||||||
if (loginError) {
|
|
||||||
contents += "<p>" + loginError + "</p>\n";
|
|
||||||
}
|
|
||||||
contents += '<div id="auth_greeting"><b>Halt. Who goes there?</b></div>\n'
|
|
||||||
contents += '<div id="auth">\n';
|
|
||||||
contents += '<div id="auth_login">\n'
|
|
||||||
if (noAdministrator()) {
|
|
||||||
contents += '<div class="notice">There is currently no administrator. You will be made administrator.</div>\n';
|
|
||||||
}
|
|
||||||
contents += '<div><label for="name">Name:</label> <input type="text" id="name" name="name" value=""></div>\n';
|
|
||||||
contents += '<div><label for="password">Password:</label> <input type="password" id="password" name="password" value=""></div>\n';
|
|
||||||
contents += '<div id="confirmPassword" style="display: none"><label for="confirm">Confirm:</label> <input type="password" id="confirm" name="confirm" value=""></div>\n';
|
|
||||||
contents += '<div><input type="checkbox" id="register" name="register" value="1" onchange="showHideConfirm()"> <label for="register">Register a new account</label></div>\n';
|
|
||||||
contents += '<div><input id="loginButton" type="submit" name="submit" value="Login"></div>\n';
|
|
||||||
contents += '</div>';
|
|
||||||
contents += '<div class="auth_or"> - or - </div>';
|
|
||||||
contents += '<div id="auth_guest">\n';
|
|
||||||
contents += '<input id="guestButton" type="submit" name="submit" value="Proceeed as Guest">\n';
|
|
||||||
contents += '</div>\n';
|
|
||||||
contents += '</div>\n';
|
|
||||||
contents += '<div style="text-align: center">\n';
|
|
||||||
contents += '<h2>Code of Conduct</h2>\n';
|
|
||||||
contents += `<div><textarea readonly rows=20 cols=80>${core.globalSettings.code_of_conduct}</textarea></div>\n`;
|
|
||||||
contents += '</div>\n';
|
|
||||||
contents += '</form>';
|
|
||||||
}
|
|
||||||
let text = html.replace("<!--SESSION-->", contents);
|
|
||||||
response.writeHead(200, {"Content-Type": "text/html; charset=utf-8", "Set-Cookie": cookie, "Content-Length": text.length});
|
|
||||||
response.end(text);
|
|
||||||
}).catch(function(error) {
|
}).catch(function(error) {
|
||||||
response.writeHead(404, {"Content-Type": "text/plain; charset=utf-8", "Connection": "close"});
|
response.writeHead(404, {"Content-Type": "text/plain; charset=utf-8", "Connection": "close"});
|
||||||
response.end("404 File not found");
|
response.end("404 File not found");
|
||||||
@ -260,12 +231,17 @@ function query(headers) {
|
|||||||
return {
|
return {
|
||||||
session: entry,
|
session: entry,
|
||||||
permissions: autologin ? getPermissionsForUser(autologin) : getPermissions(session),
|
permissions: autologin ? getPermissionsForUser(autologin) : getPermissions(session),
|
||||||
refresh: {
|
|
||||||
token: makeJwt({name: entry.name}),
|
|
||||||
interval: kRefreshInterval,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { handler, query };
|
function make_refresh(credentials) {
|
||||||
|
if (credentials?.session?.name) {
|
||||||
|
return {
|
||||||
|
token: makeJwt({name: credentials.session.name}),
|
||||||
|
interval: kRefreshInterval,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { handler, query, make_refresh };
|
||||||
|
675
core/client.js
675
core/client.js
@ -1,14 +1,11 @@
|
|||||||
|
import {LitElement, html, css, svg} from '/static/lit/lit-all.min.js';
|
||||||
|
|
||||||
let gSocket;
|
let gSocket;
|
||||||
let gCredentials;
|
|
||||||
let gPermissions;
|
|
||||||
|
|
||||||
let gCurrentFile;
|
let gCurrentFile;
|
||||||
let gFiles = {};
|
let gFiles = {};
|
||||||
let gApp = {files: {}, emoji: '📦'};
|
let gApp = {files: {}, emoji: '📦'};
|
||||||
let gEditor;
|
let gEditor;
|
||||||
let gSplit;
|
|
||||||
let gGraphs = {};
|
|
||||||
let gTimeSeries = {};
|
|
||||||
let gOriginalInput;
|
let gOriginalInput;
|
||||||
|
|
||||||
let kErrorColor = "#dc322f";
|
let kErrorColor = "#dc322f";
|
||||||
@ -26,6 +23,310 @@ const k_api = {
|
|||||||
setHash: {args: ['hash'], func: api_setHash},
|
setHash: {args: ['hash'], func: api_setHash},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const k_global_style = css`
|
||||||
|
a:link {
|
||||||
|
color: #268bd2;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: #6c71c4;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #859900;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:active {
|
||||||
|
color: #2aa198;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
class TfNavigationElement extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
credentials: {type: Object},
|
||||||
|
permissions: {type: Object},
|
||||||
|
show_permissions: {type: Boolean},
|
||||||
|
status: {type: Object},
|
||||||
|
spark_lines: {type: Object},
|
||||||
|
version: {type: Object},
|
||||||
|
show_version: {type: Boolean},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.permissions = {};
|
||||||
|
this.show_permissions = false;
|
||||||
|
this.status = {};
|
||||||
|
this.spark_lines = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle_edit(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (editing()) {
|
||||||
|
closeEditor();
|
||||||
|
} else {
|
||||||
|
edit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_permission(key) {
|
||||||
|
send({action: "resetPermission", permission: key});
|
||||||
|
}
|
||||||
|
|
||||||
|
get_spark_line(key, options) {
|
||||||
|
if (!this.spark_lines[key]) {
|
||||||
|
let spark_line = document.createElement('tf-sparkline');
|
||||||
|
spark_line.style.display = 'flex';
|
||||||
|
spark_line.style.flexDirection = 'row';
|
||||||
|
spark_line.style.flex = '0 100 10em';
|
||||||
|
spark_line.title = key;
|
||||||
|
if (options) {
|
||||||
|
if (options.max) {
|
||||||
|
spark_line.max = options.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.spark_lines[key] = spark_line;
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
return this.spark_lines[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
render_login() {
|
||||||
|
if (this?.credentials?.session?.name) {
|
||||||
|
return html`<a id="login" href="/login/logout?return=${url() + hash()}">logout ${this.credentials.session.name}</a>`;
|
||||||
|
} else {
|
||||||
|
return html`<a id="login" href="/login?return=${url() + hash()}">login</a>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render_permissions() {
|
||||||
|
if (this.show_permissions) {
|
||||||
|
return html`
|
||||||
|
<div style="position: absolute; top: 0; padding: 0; margin: 0; z-index: 100; display: flex; justify-content: center; width: 100%">
|
||||||
|
<div style="background-color: #444; padding: 1em; margin: 0 auto; border-left: 4px solid #fff; border-right: 4px solid #fff; border-bottom: 4px solid #fff">
|
||||||
|
<div>This app has the following permissions:</div>
|
||||||
|
${Object.keys(this.permissions).map(key => html`
|
||||||
|
<div>
|
||||||
|
<span>${key}</span>: ${this.permissions[key] ? '✅ Allowed' : '❌ Denied'}
|
||||||
|
<button @click=${() => this.reset_permission(key)}>Reset</button>
|
||||||
|
</div>
|
||||||
|
`)}
|
||||||
|
<button @click=${() => this.show_permissions = false}>Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let self = this;
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
${k_global_style}
|
||||||
|
</style>
|
||||||
|
<div style="margin: 4px; display: flex; flex-direction: row; flex-wrap: nowrap; gap: 3px">
|
||||||
|
<span style="cursor: pointer" @click=${() => this.show_version = !this.show_version}>😎</span>
|
||||||
|
<span ?hidden=${!this.show_version} style="flex: 0 0; white-space: nowrap" title=${this.version?.name + ' ' + Object.entries(this.version || {}).filter(x => ['name', 'number'].indexOf(x[0]) == -1).map(x => `\n* ${x[0]}: ${x[1]}`)}>${this.version?.number}</span>
|
||||||
|
<a accesskey="h" data-tip="Open home app." href="/" style="color: #fff; white-space: nowrap">TF</a>
|
||||||
|
<a accesskey="a" data-tip="Open apps list." href="/~core/apps/">apps</a>
|
||||||
|
<a accesskey="e" data-tip="Toggle the app editor." href="#" @click=${this.toggle_edit}>edit</a>
|
||||||
|
<a accesskey="p" data-tip="View and change permissions." href="#" @click=${() => self.show_permissions = !self.show_permissions}>🎛️</a>
|
||||||
|
<span style="display: inline-block; vertical-align: top; white-space: pre; color: ${this.status.color ?? kErrorColor}">${this.status.message}</span>
|
||||||
|
<span id="requests"></span>
|
||||||
|
${this.render_permissions()}
|
||||||
|
<span style="flex: 1 1; display: flex; flex-direction: row; white-space: nowrap; margin: 0; padding: 0">${Object.keys(this.spark_lines).sort().map(x => this.spark_lines[x]).map(x => [x.dataset.emoji, x])}</span>
|
||||||
|
<span style="flex: 0 0; white-space: nowrap">${this.render_login()}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define('tf-navigation', TfNavigationElement);
|
||||||
|
|
||||||
|
class TfFilesElement extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
current: {type: String},
|
||||||
|
files: {type: Object},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.files = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
file_click(file) {
|
||||||
|
this.dispatchEvent(new CustomEvent('file_click', {
|
||||||
|
detail: {
|
||||||
|
file: file,
|
||||||
|
},
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
render_file(file) {
|
||||||
|
let classes = ['file'];
|
||||||
|
if (file == this.current) {
|
||||||
|
classes.push('current');
|
||||||
|
}
|
||||||
|
if (!this.files[file].clean) {
|
||||||
|
classes.push('dirty');
|
||||||
|
}
|
||||||
|
return html`<div class="${classes.join(' ')}" @click=${x => this.file_click(file)}>${file}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let self = this;
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
div.file {
|
||||||
|
padding: 0.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
div.file:hover {
|
||||||
|
background-color: #1a9188;
|
||||||
|
}
|
||||||
|
div.file::before {
|
||||||
|
content: '📄 ';
|
||||||
|
}
|
||||||
|
|
||||||
|
div.file.current {
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #2aa198;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.file.dirty::after {
|
||||||
|
content: '*';
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div>
|
||||||
|
${Object.keys(this.files).sort().map(x => self.render_file(x))}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define('tf-files', TfFilesElement);
|
||||||
|
|
||||||
|
class TfFilesPaneElement extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
expanded: {type: Boolean},
|
||||||
|
current: {type: String},
|
||||||
|
files: {type: Object},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.expanded = window.localStorage.getItem('files') != '0';
|
||||||
|
this.files = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
set_expanded(expanded) {
|
||||||
|
this.expanded = expanded;
|
||||||
|
window.localStorage.setItem('files', expanded ? '1' : '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let self = this;
|
||||||
|
let expander = this.expanded ?
|
||||||
|
html`<span @click=${() => self.set_expanded(false)} class="expander">«</span>` :
|
||||||
|
html`<span @click=${() => self.set_expanded(true)} class="expander">»</span>`;
|
||||||
|
let content = html`
|
||||||
|
<div id="files_content">
|
||||||
|
<tf-files .files=${self.files} current=${self.current} @file_click=${event => openFile(event.detail.file)}></tf-files>
|
||||||
|
<br>
|
||||||
|
<div><button @click=${() => newFile()}>New File</button></div>
|
||||||
|
<div><button @click=${() => removeFile()}>Remove File</button></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
.expander {
|
||||||
|
font-weight: bold;
|
||||||
|
width: 100%;
|
||||||
|
right: 0;
|
||||||
|
flex: 0;
|
||||||
|
padding: 0.25em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div>
|
||||||
|
<div style="display: flex; flex-direction: row">
|
||||||
|
${this.expanded ? html`<span style="font-weight: bold; text-align: center; flex: 1">Files</span>` : undefined}
|
||||||
|
${expander}
|
||||||
|
</div>
|
||||||
|
${this.expanded ? content : undefined}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define('tf-files-pane', TfFilesPaneElement);
|
||||||
|
|
||||||
|
class TfSparkLineElement extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
lines: {type: Array},
|
||||||
|
min: {type: Number},
|
||||||
|
max: {type: Number},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.min = 0;
|
||||||
|
this.max = 1.0;
|
||||||
|
this.lines = [];
|
||||||
|
this.k_values_max = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
append(key, value) {
|
||||||
|
let line = null;
|
||||||
|
for (let it of this.lines) {
|
||||||
|
if (it.name == key) {
|
||||||
|
line = it;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!line) {
|
||||||
|
const k_colors = ['#0f0', '#88f', '#ff0', '#f0f', '#0ff', '#f00', '#888'];
|
||||||
|
line = {
|
||||||
|
name: key,
|
||||||
|
style: k_colors[this.lines.length % k_colors.length],
|
||||||
|
values: Array(this.k_values_max).fill(0),
|
||||||
|
};
|
||||||
|
this.lines.push(line);
|
||||||
|
}
|
||||||
|
if (line.values.length >= this.k_values_max) {
|
||||||
|
line.values.shift();
|
||||||
|
}
|
||||||
|
line.values.push(value);
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
render_line(line) {
|
||||||
|
if (line?.values?.length >= 2) {
|
||||||
|
let max = Math.max(this.max, ...line.values);
|
||||||
|
let points = [].concat(...line.values.map((x, i) => [100.0 * i / (line.values.length - 1), 10.0 - 10.0 * (x - this.min) / (max - this.min)]));
|
||||||
|
return svg`<polyline points=${points.join(' ')} stroke=${line.style} fill="none"/>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let max = Math.round(10.0 * Math.max(...this.lines.map(line => line.values[line.values.length - 1]))) / 10.0;
|
||||||
|
return html`
|
||||||
|
<svg style="width-auto: object-fit: cover; margin: 0; padding: 0; background: #000" viewBox="0 0 100 10" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
${this.lines.map(x => this.render_line(x))}
|
||||||
|
<text x="0" y="1em" style="font: 8px sans-serif; fill: #fff">${max}</text>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define('tf-sparkline', TfSparkLineElement);
|
||||||
|
|
||||||
window.addEventListener("keydown", function(event) {
|
window.addEventListener("keydown", function(event) {
|
||||||
if (event.keyCode == 83 && (event.altKey || event.ctrlKey)) {
|
if (event.keyCode == 83 && (event.altKey || event.ctrlKey)) {
|
||||||
if (editing()) {
|
if (editing()) {
|
||||||
@ -81,12 +382,8 @@ function editing() {
|
|||||||
return document.getElementById("editPane").style.display != 'none';
|
return document.getElementById("editPane").style.display != 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleEdit() {
|
function is_edit_only() {
|
||||||
if (editing()) {
|
return window.location.search == '?editonly=1' || window.innerWidth < 1024;
|
||||||
closeEditor();
|
|
||||||
} else {
|
|
||||||
edit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function edit() {
|
function edit() {
|
||||||
@ -95,11 +392,8 @@ function edit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.localStorage.setItem('editing', '1');
|
window.localStorage.setItem('editing', '1');
|
||||||
if (gSplit) {
|
document.getElementById("editPane").style.display = 'flex';
|
||||||
gSplit.destroy();
|
document.getElementById('viewPane').style.display = is_edit_only() ? 'none' : 'flex';
|
||||||
gSplit = undefined;
|
|
||||||
}
|
|
||||||
gSplit = Split(['#editPane', '#viewPane'], {minSize: 0});
|
|
||||||
|
|
||||||
ensureLoaded([
|
ensureLoaded([
|
||||||
{tagName: "script", attributes: {src: "/codemirror/codemirror.min.js"}},
|
{tagName: "script", attributes: {src: "/codemirror/codemirror.min.js"}},
|
||||||
@ -107,6 +401,7 @@ function edit() {
|
|||||||
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/matchesonscrollbar.min.css"}},
|
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/matchesonscrollbar.min.css"}},
|
||||||
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/dialog.min.css"}},
|
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/dialog.min.css"}},
|
||||||
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/codemirror.min.css"}},
|
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/codemirror.min.css"}},
|
||||||
|
{tagName: "link", attributes: {rel: "stylesheet", href: "/codemirror/lint.css"}},
|
||||||
{tagName: "script", attributes: {src: "/codemirror/trailingspace.min.js"}},
|
{tagName: "script", attributes: {src: "/codemirror/trailingspace.min.js"}},
|
||||||
{tagName: "script", attributes: {src: "/codemirror/dialog.min.js"}},
|
{tagName: "script", attributes: {src: "/codemirror/dialog.min.js"}},
|
||||||
{tagName: "script", attributes: {src: "/codemirror/search.min.js"}},
|
{tagName: "script", attributes: {src: "/codemirror/search.min.js"}},
|
||||||
@ -118,6 +413,9 @@ function edit() {
|
|||||||
{tagName: "script", attributes: {src: "/codemirror/css.min.js"}},
|
{tagName: "script", attributes: {src: "/codemirror/css.min.js"}},
|
||||||
{tagName: "script", attributes: {src: "/codemirror/xml.min.js"}},
|
{tagName: "script", attributes: {src: "/codemirror/xml.min.js"}},
|
||||||
{tagName: "script", attributes: {src: "/codemirror/htmlmixed.min.js"}},
|
{tagName: "script", attributes: {src: "/codemirror/htmlmixed.min.js"}},
|
||||||
|
{tagName: "script", attributes: {src: "/codemirror/lint.js"}},
|
||||||
|
{tagName: "script", attributes: {src: "/codemirror/jshint.js"}},
|
||||||
|
{tagName: "script", attributes: {src: "/codemirror/javascript-lint.min.js"}},
|
||||||
], function() {
|
], function() {
|
||||||
load().catch(function(error) {
|
load().catch(function(error) {
|
||||||
alert(error);
|
alert(error);
|
||||||
@ -126,40 +424,10 @@ function edit() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideFiles() {
|
|
||||||
window.localStorage.setItem('files', '0');
|
|
||||||
document.getElementById('filesPane').classList.add('collapsed');
|
|
||||||
}
|
|
||||||
|
|
||||||
function showFiles() {
|
|
||||||
window.localStorage.setItem('files', '1');
|
|
||||||
document.getElementById('filesPane').classList.remove('collapsed');
|
|
||||||
}
|
|
||||||
|
|
||||||
function trace() {
|
function trace() {
|
||||||
window.open(`/speedscope/#profileURL=${encodeURIComponent('/trace')}`);
|
window.open(`/speedscope/#profileURL=${encodeURIComponent('/trace')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stats() {
|
|
||||||
window.localStorage.setItem('stats', '1');
|
|
||||||
document.getElementById("statsPane").style.display = 'flex';
|
|
||||||
send({action: 'enableStats', enabled: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeStats() {
|
|
||||||
window.localStorage.setItem('stats', '0');
|
|
||||||
document.getElementById("statsPane").style.display = 'none';
|
|
||||||
send({action: 'enableStats', enabled: false});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleStats() {
|
|
||||||
if (document.getElementById("statsPane").style.display == 'none') {
|
|
||||||
stats();
|
|
||||||
} else {
|
|
||||||
closeStats();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function guessMode(name) {
|
function guessMode(name) {
|
||||||
return name.endsWith(".js") ? "javascript" :
|
return name.endsWith(".js") ? "javascript" :
|
||||||
name.endsWith(".html") ? "htmlmixed" :
|
name.endsWith(".html") ? "htmlmixed" :
|
||||||
@ -175,7 +443,6 @@ function loadFile(name, id) {
|
|||||||
}).then(function(text) {
|
}).then(function(text) {
|
||||||
gFiles[name].doc = new CodeMirror.Doc(text, guessMode(name));
|
gFiles[name].doc = new CodeMirror.Doc(text, guessMode(name));
|
||||||
if (!Object.values(gFiles).some(x => !x.doc)) {
|
if (!Object.values(gFiles).some(x => !x.doc)) {
|
||||||
document.getElementById("editPane").style.display = 'flex';
|
|
||||||
openFile(Object.keys(gFiles).sort()[0]);
|
openFile(Object.keys(gFiles).sort()[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -200,6 +467,13 @@ function load(path) {
|
|||||||
'indentUnit': 4,
|
'indentUnit': 4,
|
||||||
'indentWithTabs': true,
|
'indentWithTabs': true,
|
||||||
'showTrailingSpace': true,
|
'showTrailingSpace': true,
|
||||||
|
'gutters': ['CodeMirror-lint-markers'],
|
||||||
|
'mode': {'js': 'javascript'}[(path || url()).split('.').pop()],
|
||||||
|
'lint': {
|
||||||
|
'options': {
|
||||||
|
'esversion': 2021,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
gEditor.on('changes', function() {
|
gEditor.on('changes', function() {
|
||||||
updateFiles();
|
updateFiles();
|
||||||
@ -238,10 +512,7 @@ function load(path) {
|
|||||||
function closeEditor() {
|
function closeEditor() {
|
||||||
window.localStorage.setItem('editing', '0');
|
window.localStorage.setItem('editing', '0');
|
||||||
document.getElementById("editPane").style.display = 'none';
|
document.getElementById("editPane").style.display = 'none';
|
||||||
if (gSplit) {
|
document.getElementById('viewPane').style.display = 'flex';
|
||||||
gSplit.destroy();
|
|
||||||
gSplit = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function explodePath() {
|
function explodePath() {
|
||||||
@ -401,7 +672,8 @@ function api_localStorageGet(key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function api_requestPermission(permission, id) {
|
function api_requestPermission(permission, id) {
|
||||||
let permissions = document.getElementById('permissions');
|
let outer = document.createElement('div');
|
||||||
|
outer.classList.add('permissions');
|
||||||
|
|
||||||
let container = document.createElement('div');
|
let container = document.createElement('div');
|
||||||
container.classList.add('permissions_contents');
|
container.classList.add('permissions_contents');
|
||||||
@ -428,11 +700,13 @@ function api_requestPermission(permission, id) {
|
|||||||
|
|
||||||
const k_options = [
|
const k_options = [
|
||||||
{
|
{
|
||||||
|
id: 'allow',
|
||||||
text: '✅ Allow',
|
text: '✅ Allow',
|
||||||
grant: ['allow once', 'allow'],
|
grant: ['allow once', 'allow'],
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 'deny',
|
||||||
text: '❌ Deny',
|
text: '❌ Deny',
|
||||||
grant: ['deny once', 'deny'],
|
grant: ['deny once', 'deny'],
|
||||||
},
|
},
|
||||||
@ -443,19 +717,17 @@ function api_requestPermission(permission, id) {
|
|||||||
for (let option of k_options) {
|
for (let option of k_options) {
|
||||||
let button = document.createElement('button');
|
let button = document.createElement('button');
|
||||||
button.innerText = option.text;
|
button.innerText = option.text;
|
||||||
|
button.id = option.id;
|
||||||
button.onclick = function() {
|
button.onclick = function() {
|
||||||
resolve(option.grant[check.checked ? 1 : 0]);
|
resolve(option.grant[check.checked ? 1 : 0]);
|
||||||
while (permissions.firstChild) {
|
document.body.removeChild(outer);
|
||||||
permissions.removeChild(permissions.firstChild);
|
|
||||||
}
|
|
||||||
permissions.style.visibility = 'hidden';
|
|
||||||
}
|
}
|
||||||
div.appendChild(button);
|
div.appendChild(button);
|
||||||
}
|
}
|
||||||
container.appendChild(div);
|
container.appendChild(div);
|
||||||
|
outer.appendChild(container);
|
||||||
|
|
||||||
permissions.appendChild(container);
|
document.body.appendChild(outer);
|
||||||
permissions.style.visibility = 'visible';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -467,85 +739,20 @@ function api_setHash(hash) {
|
|||||||
window.location.hash = hash;
|
window.location.hash = hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hidePermissions() {
|
|
||||||
let permissions = document.getElementById('permissions_settings');
|
|
||||||
while (permissions.firstChild) {
|
|
||||||
permissions.removeChild(permissions.firstChild);
|
|
||||||
}
|
|
||||||
permissions.style.visibility = 'hidden';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showPermissions() {
|
|
||||||
let permissions = document.getElementById('permissions_settings');
|
|
||||||
|
|
||||||
let container = document.createElement('div');
|
|
||||||
container.classList.add('permissions_contents');
|
|
||||||
|
|
||||||
let div = document.createElement('div');
|
|
||||||
div.appendChild(document.createTextNode('This app has the following permission:'));
|
|
||||||
for (let key of Object.keys(gPermissions || {})) {
|
|
||||||
let row = document.createElement('div');
|
|
||||||
|
|
||||||
let span = document.createElement('span');
|
|
||||||
span.appendChild(document.createTextNode(key));
|
|
||||||
row.appendChild(span);
|
|
||||||
|
|
||||||
span = document.createElement('span');
|
|
||||||
span.appendChild(document.createTextNode(': '));
|
|
||||||
row.appendChild(span);
|
|
||||||
|
|
||||||
span = document.createElement('span');
|
|
||||||
span.appendChild(document.createTextNode(gPermissions[key] ? '✅ Allowed' : '❌ Denied'));
|
|
||||||
row.appendChild(span);
|
|
||||||
|
|
||||||
span = document.createElement('span');
|
|
||||||
span.appendChild(document.createTextNode(' '));
|
|
||||||
row.appendChild(span);
|
|
||||||
|
|
||||||
let button = document.createElement('button');
|
|
||||||
button.innerText = 'Reset';
|
|
||||||
button.onclick = function() {
|
|
||||||
send({action: "resetPermission", permission: key});
|
|
||||||
};
|
|
||||||
row.appendChild(button);
|
|
||||||
div.appendChild(row);
|
|
||||||
}
|
|
||||||
container.appendChild(div);
|
|
||||||
|
|
||||||
div = document.createElement('div');
|
|
||||||
let button = document.createElement('button');
|
|
||||||
button.innerText = 'Close';
|
|
||||||
button.onclick = function() {
|
|
||||||
hidePermissions();
|
|
||||||
}
|
|
||||||
div.appendChild(button);
|
|
||||||
container.appendChild(div);
|
|
||||||
|
|
||||||
permissions.appendChild(container);
|
|
||||||
permissions.style.visibility = 'visible';
|
|
||||||
}
|
|
||||||
|
|
||||||
function _receive_websocket_message(message) {
|
function _receive_websocket_message(message) {
|
||||||
if (message && message.action == "session") {
|
if (message && message.action == "session") {
|
||||||
setStatusMessage("🟢 Executing...", kStatusColor);
|
setStatusMessage("🟢 Executing...", kStatusColor);
|
||||||
gCredentials = message.credentials;
|
document.getElementsByTagName('tf-navigation')[0].credentials = message.credentials;
|
||||||
updateLogin();
|
|
||||||
} else if (message && message.action == 'permissions') {
|
} else if (message && message.action == 'permissions') {
|
||||||
gPermissions = message.permissions;
|
document.getElementsByTagName('tf-navigation')[0].permissions = message.permissions ?? {};
|
||||||
let permissions = document.getElementById('permissions_settings');
|
|
||||||
if (permissions.firstChild) {
|
|
||||||
hidePermissions();
|
|
||||||
showPermissions();
|
|
||||||
}
|
|
||||||
} else if (message && message.action == "ready") {
|
} else if (message && message.action == "ready") {
|
||||||
setStatusMessage(null);
|
setStatusMessage(null);
|
||||||
if (window.location.hash) {
|
if (window.location.hash) {
|
||||||
send({event: "hashChange", hash: window.location.hash});
|
send({event: "hashChange", hash: window.location.hash});
|
||||||
}
|
}
|
||||||
if (window.localStorage.getItem('stats') == '1') {
|
document.getElementsByTagName('tf-navigation')[0].version = message.version;
|
||||||
/* Stats were opened before we connected. */
|
document.getElementById('viewPane').style.display = message.edit_only ? 'none' : 'flex';
|
||||||
send({action: 'enableStats', enabled: true});
|
send({action: 'enableStats', enabled: true});
|
||||||
}
|
|
||||||
} else if (message && message.action == "ping") {
|
} else if (message && message.action == "ping") {
|
||||||
send({action: "pong"});
|
send({action: "pong"});
|
||||||
} else if (message && message.action == "stats") {
|
} else if (message && message.action == "stats") {
|
||||||
@ -566,8 +773,8 @@ function _receive_websocket_message(message) {
|
|||||||
tls_malloc_percent: {group: 'memory', name: 'tls'},
|
tls_malloc_percent: {group: 'memory', name: 'tls'},
|
||||||
uv_malloc_percent: {group: 'memory', name: 'uv'},
|
uv_malloc_percent: {group: 'memory', name: 'uv'},
|
||||||
|
|
||||||
messages_stored: {group: 'stored', name: 'messages'},
|
messages_stored: {group: 'store', name: 'messages'},
|
||||||
blobs_stored: {group: 'stored', name: 'blobs'},
|
blobs_stored: {group: 'store', name: 'blobs'},
|
||||||
|
|
||||||
socket_count: {group: 'socket', name: 'total'},
|
socket_count: {group: 'socket', name: 'total'},
|
||||||
socket_open_count: {group: 'socket', name: 'open'},
|
socket_open_count: {group: 'socket', name: 'open'},
|
||||||
@ -577,56 +784,15 @@ function _receive_websocket_message(message) {
|
|||||||
};
|
};
|
||||||
const k_colors = ['#0f0', '#88f', '#ff0', '#f0f', '#0ff', '#f00', '#888'];
|
const k_colors = ['#0f0', '#88f', '#ff0', '#f0f', '#0ff', '#f00', '#888'];
|
||||||
let graph_key = k_groups[key]?.group || key;
|
let graph_key = k_groups[key]?.group || key;
|
||||||
let graph = gGraphs[graph_key];
|
if (graph_key == 'cpu' || graph_key == 'rpc' || graph_key == 'store') {
|
||||||
if (!graph) {
|
let line = document.getElementsByTagName('tf-navigation')[0].get_spark_line(graph_key, { max: 100 });
|
||||||
graph = {
|
line.dataset.emoji = {
|
||||||
chart: new SmoothieChart({
|
'cpu': '💻',
|
||||||
millisPerPixel: 100,
|
'rpc': '🔁',
|
||||||
minValue: 0,
|
'store': '💾',
|
||||||
grid: {
|
}[graph_key];
|
||||||
millisPerLine: 1000,
|
line.append(key, message.stats[key]);
|
||||||
verticalSections: 10,
|
|
||||||
},
|
|
||||||
tooltip: true,
|
|
||||||
}),
|
|
||||||
canvas: document.createElement('canvas'),
|
|
||||||
title: document.createElement('div'),
|
|
||||||
series: [],
|
|
||||||
};
|
|
||||||
gGraphs[graph_key] = graph;
|
|
||||||
graph.canvas.width = 240;
|
|
||||||
graph.canvas.height = 64;
|
|
||||||
graph.title.innerText = graph_key;
|
|
||||||
graph.title.style.flex = '0';
|
|
||||||
document.getElementById('graphs').appendChild(graph.title);
|
|
||||||
document.getElementById('graphs').appendChild(graph.canvas);
|
|
||||||
graph.chart.streamTo(graph.canvas, 1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeseries = gTimeSeries[key];
|
|
||||||
if (!timeseries) {
|
|
||||||
let is_multi = key != graph_key || graph.series.length > 1;
|
|
||||||
timeseries = new TimeSeries();
|
|
||||||
gTimeSeries[key] = timeseries;
|
|
||||||
graph.chart.addTimeSeries(timeseries, {lineWidth: 2, strokeStyle: is_multi ? k_colors[graph.series.length] : '#fff'});
|
|
||||||
graph.series.push(k_groups[key]?.name || key);
|
|
||||||
if (is_multi) {
|
|
||||||
while (graph.title.firstChild) {
|
|
||||||
graph.title.removeChild(graph.title.firstChild);
|
|
||||||
}
|
|
||||||
function makeColoredText(text, color) {
|
|
||||||
let element = document.createElement('span');
|
|
||||||
element.style.color = color;
|
|
||||||
element.innerText = text;
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
graph.title.appendChild(makeColoredText(graph_key + ':', '#fff'));
|
|
||||||
for (let series of graph.series) {
|
|
||||||
graph.title.appendChild(makeColoredText(' ' + series, k_colors[graph.series.indexOf(series)]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
timeseries.append(now, message.stats[key]);
|
|
||||||
}
|
}
|
||||||
} else if (message &&
|
} else if (message &&
|
||||||
message.message === 'tfrpc' &&
|
message.message === 'tfrpc' &&
|
||||||
@ -652,27 +818,8 @@ function _receive_websocket_message(message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyEvent(event) {
|
|
||||||
send({
|
|
||||||
event: "key",
|
|
||||||
type: event.type,
|
|
||||||
which: event.which,
|
|
||||||
keyCode: event.keyCode,
|
|
||||||
charCode: event.charCode,
|
|
||||||
character: String.fromCharCode(event.keyCode || event.which),
|
|
||||||
altKey: event.altKey,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setStatusMessage(message, color) {
|
function setStatusMessage(message, color) {
|
||||||
let node = document.getElementById("status");
|
document.getElementsByTagName('tf-navigation')[0].status = {message: message, color: color};
|
||||||
while (node.firstChild) {
|
|
||||||
node.removeChild(node.firstChild);
|
|
||||||
}
|
|
||||||
if (message) {
|
|
||||||
node.appendChild(document.createTextNode(message));
|
|
||||||
node.setAttribute("style", "display: inline-block; vertical-align: top; white-space: pre; color: " + (color || kErrorColor));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function send(value) {
|
function send(value) {
|
||||||
@ -685,39 +832,6 @@ function send(value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLogin() {
|
|
||||||
let login = document.getElementById("login");
|
|
||||||
while (login.firstChild) {
|
|
||||||
login.removeChild(login.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
let a = document.createElement("a");
|
|
||||||
if (gCredentials && gCredentials.session) {
|
|
||||||
a.appendChild(document.createTextNode("logout " + gCredentials.session.name));
|
|
||||||
a.setAttribute("href", "/login/logout?return=" + encodeURIComponent(url() + hash()));
|
|
||||||
} else {
|
|
||||||
a.appendChild(document.createTextNode("login"));
|
|
||||||
a.setAttribute("href", "/login?return=" + encodeURIComponent(url() + hash()));
|
|
||||||
}
|
|
||||||
login.appendChild(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
function dragHover(event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
let input = document.getElementById("input");
|
|
||||||
if (event.type == "dragover") {
|
|
||||||
if (!input.classList.contains("drop")) {
|
|
||||||
input.classList.add("drop");
|
|
||||||
gOriginalInput = input.value;
|
|
||||||
input.value = "drop file to upload";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
input.classList.remove("drop");
|
|
||||||
input.value = gOriginalInput;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function fixImage(sourceData, maxWidth, maxHeight, callback) {
|
function fixImage(sourceData, maxWidth, maxHeight, callback) {
|
||||||
let result = sourceData;
|
let result = sourceData;
|
||||||
let image = new Image();
|
let image = new Image();
|
||||||
@ -747,52 +861,6 @@ function sendImage(image) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function fileDropRead(event) {
|
|
||||||
sendImage(event.target.result);
|
|
||||||
}
|
|
||||||
|
|
||||||
function fileDrop(event) {
|
|
||||||
dragHover(event);
|
|
||||||
|
|
||||||
let done = false;
|
|
||||||
if (!done) {
|
|
||||||
let files = event.target.files || event.dataTransfer.files;
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
let file = files[i];
|
|
||||||
if (file.type.substring(0, "image/".length) == "image/") {
|
|
||||||
let reader = new FileReader();
|
|
||||||
reader.onloadend = fileDropRead;
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!done) {
|
|
||||||
let html = event.dataTransfer.getData("text/html");
|
|
||||||
let match = /<img.*src="([^"]+)"/.exec(html);
|
|
||||||
if (match) {
|
|
||||||
sendImage(match[1]);
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!done) {
|
|
||||||
let text = event.dataTransfer.getData("text/plain");
|
|
||||||
if (text) {
|
|
||||||
send(text);
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function enableDragDrop() {
|
|
||||||
let body = document.body;
|
|
||||||
body.addEventListener("dragover", dragHover);
|
|
||||||
body.addEventListener("dragleave", dragHover);
|
|
||||||
body.addEventListener("drop", fileDrop);
|
|
||||||
}
|
|
||||||
|
|
||||||
function hashChange() {
|
function hashChange() {
|
||||||
send({event: 'hashChange', hash: window.location.hash});
|
send({event: 'hashChange', hash: window.location.hash});
|
||||||
}
|
}
|
||||||
@ -871,6 +939,8 @@ function connectSocket(path) {
|
|||||||
gSocket.send(JSON.stringify({
|
gSocket.send(JSON.stringify({
|
||||||
action: "hello",
|
action: "hello",
|
||||||
path: connect_path,
|
path: connect_path,
|
||||||
|
url: window.location.href,
|
||||||
|
edit_only: editing() && is_edit_only(),
|
||||||
api: Object.entries(k_api).map(([key, value]) => [].concat([key], value.args)),
|
api: Object.entries(k_api).map(([key, value]) => [].concat([key], value.args)),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -911,27 +981,13 @@ function openFile(name) {
|
|||||||
gEditor.focus();
|
gEditor.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFileClicked(event) {
|
|
||||||
openFile(event.target.textContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateFiles() {
|
function updateFiles() {
|
||||||
let node = document.getElementById("files");
|
let files = document.getElementsByTagName("tf-files-pane")[0];
|
||||||
while (node.firstChild) {
|
if (files) {
|
||||||
node.removeChild(node.firstChild);
|
files.files = Object.fromEntries(Object.keys(gFiles).map(file => [file, {
|
||||||
}
|
clean: gFiles[file].doc.isClean(gFiles[file].generation),
|
||||||
|
}]));
|
||||||
for (let file of Object.keys(gFiles).sort()) {
|
files.current = gCurrentFile;
|
||||||
let li = document.createElement("li");
|
|
||||||
li.onclick = onFileClicked;
|
|
||||||
li.appendChild(document.createTextNode(file));
|
|
||||||
if (file == gCurrentFile) {
|
|
||||||
li.classList.add("current");
|
|
||||||
}
|
|
||||||
if (!gFiles[file].doc.isClean(gFiles[file].generation)) {
|
|
||||||
li.classList.add("dirty");
|
|
||||||
}
|
|
||||||
node.appendChild(li);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gEditor.focus();
|
gEditor.focus();
|
||||||
@ -966,14 +1022,6 @@ window.addEventListener("load", function() {
|
|||||||
window.addEventListener("message", message, false);
|
window.addEventListener("message", message, false);
|
||||||
window.addEventListener("online", connectSocket);
|
window.addEventListener("online", connectSocket);
|
||||||
document.getElementById("name").value = window.location.pathname;
|
document.getElementById("name").value = window.location.pathname;
|
||||||
document.getElementById('edit_link').addEventListener('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
toggleEdit();
|
|
||||||
});
|
|
||||||
document.getElementById('show_permissions_link').addEventListener('click', () => showPermissions());
|
|
||||||
document.getElementById('files_hide').addEventListener('click', () => hideFiles());
|
|
||||||
document.getElementById('files_show').addEventListener('click', () => showFiles());
|
|
||||||
document.getElementById('closeStats').addEventListener('click', () => closeStats());
|
|
||||||
document.getElementById('closeEditor').addEventListener('click', () => closeEditor());
|
document.getElementById('closeEditor').addEventListener('click', () => closeEditor());
|
||||||
document.getElementById('save').addEventListener('click', () => save());
|
document.getElementById('save').addEventListener('click', () => save());
|
||||||
document.getElementById('icon').addEventListener('click', () => changeIcon());
|
document.getElementById('icon').addEventListener('click', () => changeIcon());
|
||||||
@ -982,12 +1030,6 @@ window.addEventListener("load", function() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
trace();
|
trace();
|
||||||
});
|
});
|
||||||
document.getElementById('stats_button').addEventListener('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
toggleStats();
|
|
||||||
});
|
|
||||||
document.getElementById('new_file_button').addEventListener('click', () => newFile());
|
|
||||||
document.getElementById('remove_file_button').addEventListener('click', () => removeFile());
|
|
||||||
for (let tag of document.getElementsByTagName('a')) {
|
for (let tag of document.getElementsByTagName('a')) {
|
||||||
if (tag.accessKey) {
|
if (tag.accessKey) {
|
||||||
tag.classList.add('tooltip_parent');
|
tag.classList.add('tooltip_parent');
|
||||||
@ -1012,7 +1054,6 @@ window.addEventListener("load", function() {
|
|||||||
tag.appendChild(tooltip);
|
tag.appendChild(tooltip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enableDragDrop();
|
|
||||||
connectSocket(window.location.pathname);
|
connectSocket(window.location.pathname);
|
||||||
|
|
||||||
if (window.localStorage.getItem('editing') == '1') {
|
if (window.localStorage.getItem('editing') == '1') {
|
||||||
@ -1020,14 +1061,4 @@ window.addEventListener("load", function() {
|
|||||||
} else {
|
} else {
|
||||||
closeEditor();
|
closeEditor();
|
||||||
}
|
}
|
||||||
if (window.localStorage.getItem('files') == '1') {
|
|
||||||
showFiles();
|
|
||||||
} else {
|
|
||||||
hideFiles();
|
|
||||||
}
|
|
||||||
if (window.localStorage.getItem('stats') == '1') {
|
|
||||||
stats();
|
|
||||||
} else {
|
|
||||||
closeStats();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
343
core/core.js
343
core/core.js
@ -1,12 +1,46 @@
|
|||||||
import * as app from './app.js';
|
import * as app from './app.js';
|
||||||
import * as auth from './auth.js';
|
import * as auth from './auth.js';
|
||||||
import * as form from './form.js';
|
import * as form from './form.js';
|
||||||
|
import * as http from './http.js';
|
||||||
import * as httpd from './httpd.js';
|
import * as httpd from './httpd.js';
|
||||||
|
|
||||||
let gProcessIndex = 0;
|
let gProcessIndex = 0;
|
||||||
let gProcesses = {};
|
let gProcesses = {};
|
||||||
let gStatsTimer = false;
|
let gStatsTimer = false;
|
||||||
|
|
||||||
|
const k_mime_types = {
|
||||||
|
'css': 'text/css',
|
||||||
|
'html': 'text/html',
|
||||||
|
'js': 'text/javascript',
|
||||||
|
'json': 'text/json',
|
||||||
|
'map': 'application/json',
|
||||||
|
'svg': 'image/svg+xml',
|
||||||
|
};
|
||||||
|
|
||||||
|
const k_magic_bytes = [
|
||||||
|
{bytes: [0xff, 0xd8, 0xff, 0xdb], type: 'image/jpeg'},
|
||||||
|
{bytes: [0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01], type: 'image/jpeg'},
|
||||||
|
{bytes: [0xff, 0xd8, 0xff, 0xee], type: 'image/jpeg'},
|
||||||
|
{bytes: [0xff, 0xd8, 0xff, 0xe1, null, null, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00], type: 'image/jpeg'},
|
||||||
|
{bytes: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], type: 'image/png'},
|
||||||
|
{bytes: [0x47, 0x49, 0x46, 0x38, 0x37, 0x61], type: 'image/gif'},
|
||||||
|
{bytes: [0x47, 0x49, 0x46, 0x38, 0x39, 0x61], type: 'image/gif'},
|
||||||
|
{bytes: [0x52, 0x49, 0x46, 0x46, null, null, null, null, 0x57, 0x45, 0x42, 0x50], type: 'image/webp'},
|
||||||
|
{bytes: [0x3c, 0x73, 0x76, 0x67], type: 'image/svg+xml'},
|
||||||
|
{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'},
|
||||||
|
];
|
||||||
|
|
||||||
|
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 = {
|
const k_global_settings = {
|
||||||
index: {
|
index: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -33,6 +67,21 @@ const k_global_settings = {
|
|||||||
default_value: undefined,
|
default_value: undefined,
|
||||||
description: 'If connecting by HTTP and HTTPS is configured, Location header prefix (ie, "https://example.com")',
|
description: 'If connecting by HTTP and HTTPS is configured, Location header prefix (ie, "https://example.com")',
|
||||||
},
|
},
|
||||||
|
fetch_hosts: {
|
||||||
|
type: 'string',
|
||||||
|
default_value: undefined,
|
||||||
|
description: 'Comma-separated list of host names to which HTTP fetch requests are allowed. None if empty.',
|
||||||
|
},
|
||||||
|
blob_fetch_age_seconds: {
|
||||||
|
type: 'integer',
|
||||||
|
default_value: (platform() == 'android' ? 0.5 * 365 * 24 * 60 * 60 : undefined),
|
||||||
|
description: 'Only blobs mentioned more recently than this age will be automatically fetched.',
|
||||||
|
},
|
||||||
|
blob_expire_age_seconds: {
|
||||||
|
type: 'integer',
|
||||||
|
default_value: (platform() == 'android' ? 1.0 * 365 * 24 * 60 * 60 : undefined),
|
||||||
|
description: 'Blobs older than this will be automatically deleted.',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let gGlobalSettings = {
|
let gGlobalSettings = {
|
||||||
@ -46,8 +95,8 @@ function printError(out, error) {
|
|||||||
out.print(error.fileName + ":" + error.lineNumber + ": " + error.message);
|
out.print(error.fileName + ":" + error.lineNumber + ": " + error.message);
|
||||||
out.print(error.stackTrace);
|
out.print(error.stackTrace);
|
||||||
} else {
|
} else {
|
||||||
for (let i in error) {
|
for (let [k, v] of Object.entries(error)) {
|
||||||
out.print(i);
|
out.print(k, v);
|
||||||
}
|
}
|
||||||
out.print(error.toString());
|
out.print(error.toString());
|
||||||
}
|
}
|
||||||
@ -159,7 +208,9 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
process.credentials = options.credentials || {};
|
process.credentials = options.credentials || {};
|
||||||
process.task = new Task();
|
process.task = new Task();
|
||||||
process.eventHandlers = {};
|
process.eventHandlers = {};
|
||||||
|
if (!options?.script || options?.script === 'app.js') {
|
||||||
process.app = new app.App();
|
process.app = new app.App();
|
||||||
|
}
|
||||||
process.lastActive = Date.now();
|
process.lastActive = Date.now();
|
||||||
process.lastPing = null;
|
process.lastPing = null;
|
||||||
process.timeout = options.timeout;
|
process.timeout = options.timeout;
|
||||||
@ -243,7 +294,7 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
throw Error(`Permission denied: ${permission}.`);
|
throw Error(`Permission denied: ${permission}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else if (process.app) {
|
||||||
return process.app.makeFunction(['requestPermission'])(permission).then(function(value) {
|
return process.app.makeFunction(['requestPermission'])(permission).then(function(value) {
|
||||||
if (value == 'allow') {
|
if (value == 'allow') {
|
||||||
storePermission(user, options.packageOwner, options.packageName, permission, true);
|
storePermission(user, options.packageOwner, options.packageName, permission, true);
|
||||||
@ -260,8 +311,11 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
}
|
}
|
||||||
throw Error(`Permission denied: ${permission}.`);
|
throw Error(`Permission denied: ${permission}.`);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
throw Error(`Permission denied: ${permission}.`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
url: options?.url,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (process.credentials?.permissions?.administration) {
|
if (process.credentials?.permissions?.administration) {
|
||||||
@ -284,7 +338,7 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
print('Done.');
|
print('Done.');
|
||||||
};
|
};
|
||||||
imports.core.deleteUser = function(user) {
|
imports.core.deleteUser = function(user) {
|
||||||
return imports.core.permissionTest('delete_user').then(function() {
|
return Promise.resolve(imports.core.permissionTest('delete_user')).then(function() {
|
||||||
let db = new Database('auth');
|
let db = new Database('auth');
|
||||||
|
|
||||||
db.remove('user:' + user);
|
db.remove('user:' + user);
|
||||||
@ -310,14 +364,23 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
imports.app[api[0]] = process.app.makeFunction(api);
|
imports.app[api[0]] = process.app.makeFunction(api);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (let [name, f] of Object.entries(options?.imports || {})) {
|
||||||
|
imports[name] = f;
|
||||||
|
}
|
||||||
process.task.onPrint = function(args) {
|
process.task.onPrint = function(args) {
|
||||||
|
if (imports.app) {
|
||||||
imports.app.print(...args);
|
imports.app.print(...args);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
process.task.onError = function(error) {
|
process.task.onError = function(error) {
|
||||||
try {
|
try {
|
||||||
|
if (process.app) {
|
||||||
process.app.makeFunction(['error'])(error);
|
process.app.makeFunction(['error'])(error);
|
||||||
} catch(e) {
|
} else {
|
||||||
print(e);
|
printError({print: print}, error);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
printError({print: print}, error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
imports.ssb = Object.fromEntries(Object.keys(ssb).map(key => [key, ssb[key].bind(ssb)]));
|
imports.ssb = Object.fromEntries(Object.keys(ssb).map(key => [key, ssb[key].bind(ssb)]));
|
||||||
@ -339,7 +402,7 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
if (process.credentials &&
|
if (process.credentials &&
|
||||||
process.credentials.session &&
|
process.credentials.session &&
|
||||||
process.credentials.session.name) {
|
process.credentials.session.name) {
|
||||||
return imports.core.permissionTest('ssb_append').then(function() {
|
return Promise.resolve(imports.core.permissionTest('ssb_append')).then(function() {
|
||||||
return ssb.appendMessageWithIdentity(process.credentials.session.name, id, message);
|
return ssb.appendMessageWithIdentity(process.credentials.session.name, id, message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -358,6 +421,9 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
return ssb.privateMessageDecrypt(process.credentials.session.name, id, message);
|
return ssb.privateMessageDecrypt(process.credentials.session.name, id, message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
imports.fetch = function(url, options) {
|
||||||
|
return http.fetch(url, options, gGlobalSettings.fetch_hosts);
|
||||||
|
}
|
||||||
|
|
||||||
if (process.credentials &&
|
if (process.credentials &&
|
||||||
process.credentials.session &&
|
process.credentials.session &&
|
||||||
@ -396,7 +462,7 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
try {
|
try {
|
||||||
let appObject = JSON.parse(appSource);
|
let appObject = JSON.parse(appSource);
|
||||||
if (appObject.type == "tildefriends-app") {
|
if (appObject.type == "tildefriends-app") {
|
||||||
appSourceName = 'app.js';
|
appSourceName = options?.script ?? 'app.js';
|
||||||
let id = appObject.files[appSourceName];
|
let id = appObject.files[appSourceName];
|
||||||
let blob = await getBlobOrContent(id);
|
let blob = await getBlobOrContent(id);
|
||||||
appSource = utf8Decode(blob);
|
appSource = utf8Decode(blob);
|
||||||
@ -409,19 +475,23 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
printError({print: print}, e);
|
printError({print: print}, e);
|
||||||
}
|
}
|
||||||
broadcastEvent('onSessionBegin', [getUser(process, process)]);
|
broadcastEvent('onSessionBegin', [getUser(process, process)]);
|
||||||
resolveReady(process);
|
|
||||||
if (process.app) {
|
if (process.app) {
|
||||||
process.app.send({action: "ready"});
|
process.app.send({action: "ready", version: version()});
|
||||||
process.sendPermissions();
|
process.sendPermissions();
|
||||||
}
|
}
|
||||||
await process.task.execute({name: appSourceName, source: appSource});
|
await process.task.execute({name: appSourceName, source: appSource});
|
||||||
|
resolveReady(process);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (process.app) {
|
||||||
if (process?.task?.onError) {
|
if (process?.task?.onError) {
|
||||||
process.task.onError(error);
|
process.task.onError(error);
|
||||||
} else {
|
} else {
|
||||||
printError({print: print}, error);
|
printError({print: print}, error);
|
||||||
}
|
}
|
||||||
rejectReady();
|
} else {
|
||||||
|
printError({print: print}, error);
|
||||||
|
}
|
||||||
|
rejectReady(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return process;
|
return process;
|
||||||
@ -436,20 +506,11 @@ function setGlobalSettings(settings) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let kStaticFiles = [
|
|
||||||
{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'},
|
|
||||||
];
|
|
||||||
|
|
||||||
function startsWithBytes(data, bytes) {
|
function startsWithBytes(data, bytes) {
|
||||||
if (data.byteLength >= bytes.length) {
|
if (data.byteLength >= bytes.length) {
|
||||||
let dataBytes = new Uint8Array(data.slice(0, bytes.length));
|
let dataBytes = new Uint8Array(data.slice(0, bytes.length));
|
||||||
for (let i = 0; i < bytes.length; i++) {
|
for (let i = 0; i < bytes.length; i++) {
|
||||||
if (dataBytes[i] != bytes[i] && bytes[i] !== null) {
|
if (dataBytes[i] !== bytes[i] && bytes[i] !== null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -458,16 +519,16 @@ function startsWithBytes(data, bytes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function staticFileHandler(request, response, blobId, uri) {
|
async function staticFileHandler(request, response, blobId, uri) {
|
||||||
for (let i in kStaticFiles) {
|
for (let i in k_static_files) {
|
||||||
if (uri === kStaticFiles[i].uri) {
|
if (uri === k_static_files[i].uri) {
|
||||||
let path = kStaticFiles[i].path || uri.substring(1);
|
let path = k_static_files[i].path || uri.substring(1);
|
||||||
let type = kStaticFiles[i].type || guessType(path);
|
let type = k_static_files[i].type || guessTypeFromName(path);
|
||||||
|
|
||||||
let stat = await File.stat('core/' + path);
|
let stat = await File.stat('core/' + path);
|
||||||
let id = `${stat.mtime}_${stat.size}`;
|
let id = `${stat.mtime}_${stat.size}`;
|
||||||
|
|
||||||
if (request.headers['if-none-match'] === '"' + id + '"') {
|
if (request.headers['if-none-match'] === '"' + id + '"') {
|
||||||
response.writeHead(304, {});
|
response.writeHead(304, {'Content-Length': '0'});
|
||||||
response.end();
|
response.end();
|
||||||
} else {
|
} else {
|
||||||
let data = await File.readFile('core/' + path);
|
let data = await File.readFile('core/' + path);
|
||||||
@ -477,7 +538,7 @@ async function staticFileHandler(request, response, blobId, uri) {
|
|||||||
'Content-Length': data.byteLength,
|
'Content-Length': data.byteLength,
|
||||||
'etag': '"' + id + '"',
|
'etag': '"' + id + '"',
|
||||||
},
|
},
|
||||||
kStaticFiles[i].headers || {}));
|
k_static_files[i].headers || {}));
|
||||||
response.end(data);
|
response.end(data);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -488,14 +549,6 @@ async function staticFileHandler(request, response, blobId, uri) {
|
|||||||
response.end("File not found");
|
response.end("File not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const k_mime_types = {
|
|
||||||
'json': 'text/json',
|
|
||||||
'js': 'text/javascript',
|
|
||||||
'html': 'text/html',
|
|
||||||
'css': 'text/css',
|
|
||||||
'map': 'application/json',
|
|
||||||
};
|
|
||||||
|
|
||||||
async function staticDirectoryHandler(request, response, directory, uri) {
|
async function staticDirectoryHandler(request, response, directory, uri) {
|
||||||
let filename = uri || 'index.html';
|
let filename = uri || 'index.html';
|
||||||
if (filename.indexOf('..') != -1) {
|
if (filename.indexOf('..') != -1) {
|
||||||
@ -509,7 +562,7 @@ async function staticDirectoryHandler(request, response, directory, uri) {
|
|||||||
let id = `${stat.mtime}_${stat.size}`;
|
let id = `${stat.mtime}_${stat.size}`;
|
||||||
|
|
||||||
if (request.headers['if-none-match'] === '"' + id + '"') {
|
if (request.headers['if-none-match'] === '"' + id + '"') {
|
||||||
response.writeHead(304, {});
|
response.writeHead(304, {'Content-Length': '0'});
|
||||||
response.end();
|
response.end();
|
||||||
} else {
|
} else {
|
||||||
let data = await File.readFile(directory + filename);
|
let data = await File.readFile(directory + filename);
|
||||||
@ -537,40 +590,25 @@ async function wellKnownHandler(request, response, path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendData(response, data, type, headers) {
|
function guessTypeFromName(path) {
|
||||||
if (data) {
|
let extension = path.split('.').pop();
|
||||||
if (startsWithBytes(data, [0xff, 0xd8, 0xff, 0xdb]) ||
|
return k_mime_types[extension];
|
||||||
startsWithBytes(data, [0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01]) ||
|
}
|
||||||
startsWithBytes(data, [0xff, 0xd8, 0xff, 0xee]) ||
|
|
||||||
startsWithBytes(data, [0xff, 0xd8, 0xff, 0xe1, null, null, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00])) {
|
function guessTypeFromMagicBytes(data) {
|
||||||
response.writeHead(200, Object.assign({"Content-Type": "image/jpeg", "Content-Length": data.byteLength}, headers || {}));
|
for (let magic of k_magic_bytes) {
|
||||||
response.end(data);
|
if (startsWithBytes(data, magic.bytes)) {
|
||||||
} else if (startsWithBytes(data, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
|
return magic.type;
|
||||||
response.writeHead(200, Object.assign({"Content-Type": "image/png", "Content-Length": data.byteLength}, headers || {}));
|
|
||||||
response.end(data);
|
|
||||||
} else if (startsWithBytes(data, [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]) ||
|
|
||||||
startsWithBytes(data, [0x47, 0x49, 0x46, 0x38, 0x39, 0x61])) {
|
|
||||||
response.writeHead(200, Object.assign({"Content-Type": "image/gif", "Content-Length": data.byteLength}, headers || {}));
|
|
||||||
response.end(data);
|
|
||||||
} else if (startsWithBytes(data, [0x52, 0x49, 0x46, 0x46, null, null, null, null, 0x57, 0x45, 0x42, 0x50])) {
|
|
||||||
response.writeHead(200, Object.assign({"Content-Type": "image/webp", "Content-Length": data.byteLength}, headers || {}));
|
|
||||||
response.end(data);
|
|
||||||
} else if (startsWithBytes(data, [0x3c, 0x73, 0x76, 0x67])) {
|
|
||||||
response.writeHead(200, Object.assign({"Content-Type": "image/svg+xml", "Content-Length": data.byteLength}, headers || {}));
|
|
||||||
response.end(data);
|
|
||||||
} else if (startsWithBytes(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32])) {
|
|
||||||
response.writeHead(200, Object.assign({"Content-Type": "audio/mpeg", "Content-Length": data.byteLength}, headers || {}));
|
|
||||||
response.end(data);
|
|
||||||
} else if (startsWithBytes(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d]) ||
|
|
||||||
startsWithBytes(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32])) {
|
|
||||||
response.writeHead(200, Object.assign({"Content-Type": "video/mp4", "Content-Length": data.byteLength}, headers || {}));
|
|
||||||
response.end(data);
|
|
||||||
} else {
|
|
||||||
response.writeHead(200, Object.assign({"Content-Type": type || "application/binary", "Content-Length": data.byteLength}, headers || {}));
|
|
||||||
response.end(data);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 || {}));
|
||||||
|
response.end(data);
|
||||||
} else {
|
} else {
|
||||||
response.writeHead(404, Object.assign({"Content-Type": "text/plain; charset=utf-8", "Content-Length": "File not found".length}, headers || {}));
|
response.writeHead(status_code ?? 404, Object.assign({"Content-Type": "text/plain; charset=utf-8", "Content-Length": "File not found".length}, headers || {}));
|
||||||
response.end("File not found");
|
response.end("File not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -585,35 +623,58 @@ async function getBlobOrContent(id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function guessType(path) {
|
let g_handler_index = 0;
|
||||||
const k_extension_to_type = {
|
async function useAppHandler(response, handler_blob_id, path, query, headers, packageOwner, packageName) {
|
||||||
'css': 'text/css',
|
print('useAppHandler', packageOwner, packageName);
|
||||||
'html': 'text/html',
|
let do_resolve;
|
||||||
'js': 'text/javascript',
|
let promise = new Promise(async function(resolve, reject) {
|
||||||
'svg': 'image/svg+xml',
|
do_resolve = resolve;
|
||||||
};
|
});
|
||||||
let extension = path.split('.').pop();
|
let process;
|
||||||
return k_extension_to_type[extension];
|
let result;
|
||||||
|
try {
|
||||||
|
process = await getProcessBlob(handler_blob_id, 'handler_' + g_handler_index++, {
|
||||||
|
script: 'handler.js',
|
||||||
|
imports: {
|
||||||
|
request: {
|
||||||
|
path: path,
|
||||||
|
query: query,
|
||||||
|
},
|
||||||
|
respond: do_resolve,
|
||||||
|
},
|
||||||
|
credentials: auth.query(headers),
|
||||||
|
packageOwner: packageOwner,
|
||||||
|
packageName: packageName,
|
||||||
|
});
|
||||||
|
await process.ready;
|
||||||
|
|
||||||
|
result = await promise;
|
||||||
|
} finally {
|
||||||
|
if (process?.task) {
|
||||||
|
await process.task.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function blobHandler(request, response, blobId, uri) {
|
async function blobHandler(request, response, blobId, uri) {
|
||||||
for (let i in kStaticFiles) {
|
for (let i in k_static_files) {
|
||||||
if (uri === kStaticFiles[i].uri && kStaticFiles[i].path) {
|
if (uri === k_static_files[i].uri && k_static_files[i].path) {
|
||||||
let stat = await File.stat('core/' + kStaticFiles[i].path);
|
let stat = await File.stat('core/' + k_static_files[i].path);
|
||||||
let id = `${stat.mtime}_${stat.size}`;
|
let id = `${stat.mtime}_${stat.size}`;
|
||||||
|
|
||||||
if (request.headers['if-none-match'] === '"' + id + '"') {
|
if (request.headers['if-none-match'] === '"' + id + '"') {
|
||||||
response.writeHead(304, {});
|
response.writeHead(304, {'Content-Length': '0'});
|
||||||
response.end();
|
response.end();
|
||||||
} else {
|
} else {
|
||||||
let data = await File.readFile('core/' + kStaticFiles[i].path);
|
let data = await File.readFile('core/' + k_static_files[i].path);
|
||||||
response.writeHead(200, Object.assign(
|
response.writeHead(200, Object.assign(
|
||||||
{
|
{
|
||||||
'Content-Type': kStaticFiles[i].type,
|
'Content-Type': k_static_files[i].type,
|
||||||
'Content-Length': data.byteLength,
|
'Content-Length': data.byteLength,
|
||||||
'etag': '"' + id + '"',
|
'etag': '"' + id + '"',
|
||||||
},
|
},
|
||||||
kStaticFiles[i].headers || {}));
|
k_static_files[i].headers || {}));
|
||||||
response.end(data);
|
response.end(data);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -631,7 +692,9 @@ async function blobHandler(request, response, blobId, uri) {
|
|||||||
let data;
|
let data;
|
||||||
let match;
|
let match;
|
||||||
let query = form.decodeForm(request.query);
|
let query = form.decodeForm(request.query);
|
||||||
let headers = {};
|
let headers = {
|
||||||
|
'Content-Security-Policy': 'sandbox',
|
||||||
|
};
|
||||||
if (query.filename && query.filename.match(/^[A-Za-z0-9\.-]*$/)) {
|
if (query.filename && query.filename.match(/^[A-Za-z0-9\.-]*$/)) {
|
||||||
headers['Content-Disposition'] = `attachment; filename=${query.filename}`;
|
headers['Content-Disposition'] = `attachment; filename=${query.filename}`;
|
||||||
}
|
}
|
||||||
@ -639,6 +702,7 @@ async function blobHandler(request, response, blobId, uri) {
|
|||||||
let id = await new Database(match[1]).get('path:' + match[2]);
|
let id = await new Database(match[1]).get('path:' + match[2]);
|
||||||
if (id) {
|
if (id) {
|
||||||
if (request.headers['if-none-match'] === '"' + id + '"') {
|
if (request.headers['if-none-match'] === '"' + id + '"') {
|
||||||
|
headers['Content-Length'] = '0';
|
||||||
response.writeHead(304, headers);
|
response.writeHead(304, headers);
|
||||||
response.end();
|
response.end();
|
||||||
} else {
|
} else {
|
||||||
@ -651,6 +715,7 @@ async function blobHandler(request, response, blobId, uri) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (request.headers['if-none-match'] === '"' + blobId + '"') {
|
if (request.headers['if-none-match'] === '"' + blobId + '"') {
|
||||||
|
headers['Content-Length'] = '0';
|
||||||
response.writeHead(304, headers);
|
response.writeHead(304, headers);
|
||||||
response.end();
|
response.end();
|
||||||
} else {
|
} else {
|
||||||
@ -659,6 +724,7 @@ async function blobHandler(request, response, blobId, uri) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (request.headers['if-none-match'] === '"' + blobId + '"') {
|
if (request.headers['if-none-match'] === '"' + blobId + '"') {
|
||||||
|
headers['Content-Length'] = '0';
|
||||||
response.writeHead(304, headers);
|
response.writeHead(304, headers);
|
||||||
response.end();
|
response.end();
|
||||||
} else {
|
} else {
|
||||||
@ -738,43 +804,58 @@ async function blobHandler(request, response, blobId, uri) {
|
|||||||
response.end('OK');
|
response.end('OK');
|
||||||
} else {
|
} else {
|
||||||
let data;
|
let data;
|
||||||
let type;
|
|
||||||
let headers;
|
|
||||||
let match;
|
let match;
|
||||||
|
let id;
|
||||||
|
let app_id = blobId;
|
||||||
|
let packageOwner;
|
||||||
|
let packageName;
|
||||||
if (match = /^\/\~(\w+)\/(\w+)$/.exec(blobId)) {
|
if (match = /^\/\~(\w+)\/(\w+)$/.exec(blobId)) {
|
||||||
|
packageOwner = match[1];
|
||||||
|
packageName = match[2];
|
||||||
let db = new Database(match[1]);
|
let db = new Database(match[1]);
|
||||||
let id = await db.get('path:' + match[2]);
|
app_id = await db.get('path:' + match[2]);
|
||||||
if (id) {
|
}
|
||||||
if (request.headers['if-none-match'] && request.headers['if-none-match'] == '"' + id + '"') {
|
|
||||||
headers = {
|
let app_object = JSON.parse(utf8Decode(await getBlobOrContent(app_id)));
|
||||||
|
id = app_object.files[uri.substring(1)];
|
||||||
|
if (!id && app_object.files['handler.js']) {
|
||||||
|
let answer;
|
||||||
|
try {
|
||||||
|
answer = await useAppHandler(response, app_id, uri.substring(1), request.query ? form.decodeForm(request.query) : undefined, request.headers, packageOwner, packageName);
|
||||||
|
} catch (error) {
|
||||||
|
data = utf8Encode(`Internal Server Error\n\n${error?.message}\n${error?.stack}`);
|
||||||
|
response.writeHead(500, {'Content-Type': 'text/plain; charset=utf-8', 'Content-Length': data.length});
|
||||||
|
response.end(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (answer && typeof answer.data == 'string') {
|
||||||
|
answer.data = utf8Encode(answer.data);
|
||||||
|
}
|
||||||
|
sendData(response, answer?.data, answer?.content_type, Object.assign(answer?.headers ?? {}, {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Content-Security-Policy': 'sandbox',
|
||||||
|
}), 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-Length': '0',
|
||||||
};
|
};
|
||||||
response.writeHead(304, headers);
|
response.writeHead(304, headers);
|
||||||
response.end();
|
response.end();
|
||||||
} else {
|
} else {
|
||||||
data = utf8Decode(await getBlobOrContent(id));
|
let headers = {
|
||||||
let appObject = JSON.parse(data);
|
|
||||||
data = appObject.files[uri.substring(1)];
|
|
||||||
data = await getBlobOrContent(data);
|
|
||||||
type = guessType(uri);
|
|
||||||
headers = {
|
|
||||||
'ETag': '"' + id + '"',
|
'ETag': '"' + id + '"',
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Content-Security-Policy': 'sandbox',
|
||||||
};
|
};
|
||||||
|
data = await getBlobOrContent(id);
|
||||||
|
let type = guessTypeFromName(uri) || guessTypeFromMagicBytes(data);
|
||||||
sendData(response, data, type, headers);
|
sendData(response, data, type, headers);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sendData(response, data, type, headers);
|
sendData(response, data, undefined, {});
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data = utf8Decode(await getBlobOrContent(blobId));
|
|
||||||
let appObject = JSON.parse(data);
|
|
||||||
data = appObject.files[uri.substring(1)];
|
|
||||||
data = await getBlobOrContent(data);
|
|
||||||
headers = {
|
|
||||||
'Access-Control-Allow-Origin': '*',
|
|
||||||
};
|
|
||||||
sendData(response, data, type, headers);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -826,6 +907,16 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
loadSettings().then(function() {
|
loadSettings().then(function() {
|
||||||
httpd.all("/login", auth.handler);
|
httpd.all("/login", auth.handler);
|
||||||
httpd.all("", function(request, response) {
|
httpd.all("", function(request, response) {
|
||||||
@ -837,40 +928,28 @@ loadSettings().then(function() {
|
|||||||
return blobHandler(request, response, match[1], match[2]);
|
return blobHandler(request, response, match[1], match[2]);
|
||||||
} else if (match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri)) {
|
} else if (match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri)) {
|
||||||
return blobHandler(request, response, match[1], match[2]);
|
return blobHandler(request, response, match[1], match[2]);
|
||||||
} else if (match = /^\/static(\/.*)/.exec(request.uri)) {
|
} else if (match = /^\/static\/lit\/([\.\w-/]*)$/.exec(request.uri)) {
|
||||||
return staticFileHandler(request, response, null, match[1]);
|
return staticDirectoryHandler(request, response, 'deps/lit/', match[1]);
|
||||||
} else if (match = /^\/codemirror\/([\.\w-/]*)$/.exec(request.uri)) {
|
} else if (match = /^\/codemirror\/([\.\w-/]*)$/.exec(request.uri)) {
|
||||||
return staticDirectoryHandler(request, response, 'deps/codemirror/', match[1]);
|
return staticDirectoryHandler(request, response, 'deps/codemirror/', match[1]);
|
||||||
} else if (match = /^\/speedscope\/([\.\w-/]*)$/.exec(request.uri)) {
|
} else if (match = /^\/speedscope\/([\.\w-/]*)$/.exec(request.uri)) {
|
||||||
return staticDirectoryHandler(request, response, 'deps/speedscope/', match[1]);
|
return staticDirectoryHandler(request, response, 'deps/speedscope/', match[1]);
|
||||||
} else if (match = /^\/split\/([\.\w-/]*)$/.exec(request.uri)) {
|
} else if (match = /^\/static(\/.*)/.exec(request.uri)) {
|
||||||
return staticDirectoryHandler(request, response, 'deps/split/', match[1]);
|
return staticFileHandler(request, response, null, match[1]);
|
||||||
} else if (match = /^\/smoothie\/([\.\w-/]*)$/.exec(request.uri)) {
|
} else if (request.uri == "/robots.txt") {
|
||||||
return staticDirectoryHandler(request, response, 'deps/smoothie/', match[1]);
|
return staticFileHandler(request, response, null, request.uri);
|
||||||
} else if (match = /^(.*)(\/(?:save|delete)?)$/.exec(request.uri)) {
|
} else if (match = /^(.*)(\/(?:save|delete)?)$/.exec(request.uri)) {
|
||||||
return blobHandler(request, response, match[1], match[2]);
|
return blobHandler(request, response, match[1], match[2]);
|
||||||
} else if (match = /^\/trace$/.exec(request.uri)) {
|
} else if (match = /^\/trace$/.exec(request.uri)) {
|
||||||
let data = trace();
|
return stringResponse(response, trace());
|
||||||
response.writeHead(200, {"Content-Type": "application/json; charset=utf-8", "Content-Length": data.length.toString()});
|
|
||||||
return response.end(data);
|
|
||||||
} else if (match = /^\/disconnections$/.exec(request.uri)) {
|
} else if (match = /^\/disconnections$/.exec(request.uri)) {
|
||||||
let data = utf8Encode(JSON.stringify(disconnectionsDebug(), null, 2));
|
return stringResponse(response, JSON.stringify(disconnectionsDebug(), null, 2));
|
||||||
response.writeHead(200, {"Content-Type": "application/json; charset=utf-8", "Content-Length": data.byteLength.toString()});
|
|
||||||
return response.end(data);
|
|
||||||
} else if (match = /^\/debug$/.exec(request.uri)) {
|
} else if (match = /^\/debug$/.exec(request.uri)) {
|
||||||
let data = JSON.stringify(getDebug(), null, 2);
|
return stringResponse(response, JSON.stringify(getDebug(), null, 2));
|
||||||
response.writeHead(200, {"Content-Type": "application/json; charset=utf-8", "Content-Length": data.length.toString()});
|
} else if (match = /^\/hitches$/.exec(request.uri)) {
|
||||||
return response.end(data);
|
return stringResponse(response, JSON.stringify(getHitches(), null, 2));
|
||||||
} else if (match = /^\/mem$/.exec(request.uri)) {
|
} else if (match = /^\/mem$/.exec(request.uri)) {
|
||||||
let data = JSON.stringify(getAllocations(), null, 2);
|
return stringResponse(response, JSON.stringify(getAllocations(), null, 2));
|
||||||
response.writeHead(200, {
|
|
||||||
"Content-Type": "application/json; charset=utf-8",
|
|
||||||
"Content-Length": data.length.toString(),
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
});
|
|
||||||
return response.end(data);
|
|
||||||
} else if (request.uri == "/robots.txt") {
|
|
||||||
return blobHandler(request, response, null, request.uri);
|
|
||||||
} else if ((match = /^\/.well-known\/(.*)/.exec(request.uri)) && request.uri.indexOf("..") == -1) {
|
} else if ((match = /^\/.well-known\/(.*)/.exec(request.uri)) && request.uri.indexOf("..") == -1) {
|
||||||
return wellKnownHandler(request, response, match[1]);
|
return wellKnownHandler(request, response, match[1]);
|
||||||
} else {
|
} else {
|
||||||
|
86
core/http.js
Normal file
86
core/http.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
function parseUrl(url) {
|
||||||
|
// XXX: Hack.
|
||||||
|
let match = url.match(new RegExp("(\\w+)://([^/:]+)(?::(\\d+))?(.*)"));
|
||||||
|
return {
|
||||||
|
protocol: match[1],
|
||||||
|
host: match[2],
|
||||||
|
path: match[4],
|
||||||
|
port: match[3] ? parseInt(match[3]) : match[1] == "http" ? 80 : 443,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseResponse(data) {
|
||||||
|
let firstLine;
|
||||||
|
let headers = {};
|
||||||
|
while (true) {
|
||||||
|
let endLine = data.indexOf('\r\n');
|
||||||
|
let line = data.substring(0, endLine);
|
||||||
|
data = data.substring(endLine + 2);
|
||||||
|
if (!line.length) {
|
||||||
|
break;
|
||||||
|
} else if (!firstLine) {
|
||||||
|
firstLine = line;
|
||||||
|
} else {
|
||||||
|
let colon = line.indexOf(":");
|
||||||
|
headers[line.substring(colon)] = line.substring(colon + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {body: data};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetch(url, options, allowed_hosts) {
|
||||||
|
let parsed = parseUrl(url);
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
if ((allowed_hosts ?? []).indexOf(parsed.host) == -1) {
|
||||||
|
throw new Error(`fetch() request to host ${parsed.host} is not allowed.`);
|
||||||
|
}
|
||||||
|
let socket = new Socket();
|
||||||
|
let buffer = new Uint8Array(0);
|
||||||
|
|
||||||
|
return socket.connect(parsed.host, parsed.port).then(function() {
|
||||||
|
socket.read(function(data) {
|
||||||
|
if (data && data.length) {
|
||||||
|
let newBuffer = new Uint8Array(buffer.length + data.length);
|
||||||
|
newBuffer.set(buffer, 0);
|
||||||
|
newBuffer.set(data, buffer.length);
|
||||||
|
buffer = newBuffer;
|
||||||
|
} else {
|
||||||
|
let result = parseHttpResponse(buffer);
|
||||||
|
if (!result) {
|
||||||
|
reject(new Exception('Parse failed.'));
|
||||||
|
}
|
||||||
|
if (typeof result == 'number') {
|
||||||
|
if (result == -2) {
|
||||||
|
reject('Incomplete request.');
|
||||||
|
} else {
|
||||||
|
reject('Bad request.');
|
||||||
|
}
|
||||||
|
} else if (typeof result == 'object') {
|
||||||
|
resolve({
|
||||||
|
body: buffer.slice(result.bytes_parsed),
|
||||||
|
status: result.status,
|
||||||
|
message: result.message,
|
||||||
|
headers: result.headers,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject(new Exception('Unexpected parse result.'));
|
||||||
|
}
|
||||||
|
resolve(parseResponse(utf8Decode(buffer)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (parsed.port == 443) {
|
||||||
|
return socket.startTls();
|
||||||
|
}
|
||||||
|
}).then(function() {
|
||||||
|
let body = typeof options?.body == 'string' ? utf8Encode(options.body) : (options.body || new Uint8Array(0));
|
||||||
|
let headers = utf8Encode(`${options?.method ?? 'GET'} ${parsed.path} HTTP/1.0\r\nHost: ${parsed.host}\r\nConnection: close\r\nContent-Length: ${body.length}\r\n\r\n`);
|
||||||
|
let fullRequest = new Uint8Array(headers.length + body.length);
|
||||||
|
fullRequest.set(headers, 0);
|
||||||
|
fullRequest.set(body, headers.length);
|
||||||
|
socket.write(fullRequest);
|
||||||
|
}).catch(function(error) {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -473,7 +473,7 @@ function handleConnection(client) {
|
|||||||
|
|
||||||
if (parsing_header)
|
if (parsing_header)
|
||||||
{
|
{
|
||||||
let result = parseHttp(inputBuffer, inputBuffer.length - data.length);
|
let result = parseHttpRequest(inputBuffer, inputBuffer.length - data.length);
|
||||||
if (result) {
|
if (result) {
|
||||||
if (typeof result === 'number') {
|
if (typeof result === 'number') {
|
||||||
if (result == -2) {
|
if (result == -2) {
|
||||||
@ -552,7 +552,7 @@ function handleConnection(client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let kBacklog = 8;
|
let kBacklog = 8;
|
||||||
let kHost = "0.0.0.0"
|
let kHost = '::';
|
||||||
|
|
||||||
let socket = new Socket();
|
let socket = new Socket();
|
||||||
socket.bind(kHost, tildefriends.http_port).then(function(port) {
|
socket.bind(kHost, tildefriends.http_port).then(function(port) {
|
||||||
|
@ -7,26 +7,9 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
</head>
|
</head>
|
||||||
<body style="display: flex; flex-flow: column">
|
<body style="display: flex; flex-flow: column">
|
||||||
<div class="navigation">
|
<tf-navigation></tf-navigation>
|
||||||
<span>😎</span>
|
|
||||||
<a accesskey="h" data-tip="Open home app." href="/" style="color: #fff">Tilde Friends</a>
|
|
||||||
<a accesskey="a" data-tip="Open apps list." href="/~core/apps/">apps</a>
|
|
||||||
<a accesskey="e" data-tip="Toggle the app editor." href="#" id="edit_link">edit</a>
|
|
||||||
<a accesskey="p" data-tip="View and change permissions." href="#" id="show_permissions_link">🎛️</a>
|
|
||||||
<span id="status"></span>
|
|
||||||
<span id="requests"></span>
|
|
||||||
<span id="permissions_settings"></span>
|
|
||||||
<span id="permissions"></span>
|
|
||||||
<span id="login"></span>
|
|
||||||
</div>
|
|
||||||
<div id="content" class="hbox" style="flex: 1 1; width: 100%">
|
<div id="content" class="hbox" style="flex: 1 1; width: 100%">
|
||||||
<div id="statsPane" class="vbox" style="display: none; flex 1 1">
|
<div id="editPane" class="vbox" style="flex: 1 1; display: none">
|
||||||
<div class="hbox">
|
|
||||||
<input type="button" id="closeStats" name="closeStats" value="Close">
|
|
||||||
</div>
|
|
||||||
<div id="graphs" class="vbox" style="height: 100%"></div>
|
|
||||||
</div>
|
|
||||||
<div id="editPane" class="vbox" style="display: none">
|
|
||||||
<div class="navigation hbox">
|
<div class="navigation hbox">
|
||||||
<input type="button" id="closeEditor" name="closeEditor" value="Close">
|
<input type="button" id="closeEditor" name="closeEditor" value="Close">
|
||||||
<input type="button" id="save" name="save" value="Save">
|
<input type="button" id="save" name="save" value="Save">
|
||||||
@ -34,22 +17,9 @@
|
|||||||
<input type="text" id="name" name="name" style="flex: 1 1; min-width: 1em"></input>
|
<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="delete" name="delete" value="Delete">
|
||||||
<input type="button" id="trace_button" value="Trace">
|
<input type="button" id="trace_button" value="Trace">
|
||||||
<input type="button" id="stats_button" value="Stats">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="hbox" style="height: 100%">
|
<div class="hbox" style="height: 100%">
|
||||||
<div id="filesPane">
|
<tf-files-pane></tf-files-pane>
|
||||||
<div class="hbox">
|
|
||||||
<span id="files_header">Files</span>
|
|
||||||
<span id="files_hide">«</span>
|
|
||||||
<span id="files_show">»</span>
|
|
||||||
</div>
|
|
||||||
<div id="files_content">
|
|
||||||
<ul id="files"></ul>
|
|
||||||
<br>
|
|
||||||
<div><button id="new_file_button">New File</button></div>
|
|
||||||
<div><button id="remove_file_button">Remove File</button></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="docPane" style="display: flex; flex: 1 1 50%; flex-flow: column">
|
<div id="docPane" style="display: flex; flex: 1 1 50%; flex-flow: column">
|
||||||
<div style="flex: 1 1 50%; position: relative">
|
<div style="flex: 1 1 50%; position: relative">
|
||||||
<textarea id="editor" class="main"></textarea>
|
<textarea id="editor" class="main"></textarea>
|
||||||
@ -57,12 +27,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="viewPane" class="vbox" style="flex: 1 0; overflow: auto">
|
<div id="viewPane" class="vbox" style="flex: 1 1; overflow: auto">
|
||||||
<iframe id="document" sandbox="allow-forms allow-scripts allow-top-navigation allow-modals allow-downloads" style="width: 100%; height: 100%; border: 0"></iframe>
|
<iframe id="document" sandbox="allow-forms allow-scripts allow-top-navigation allow-modals allow-downloads" style="width: 100%; height: 100%; border: 0"></iframe>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script>window.litDisableBundleWarning = true;</script>
|
||||||
<script src="/split/split.min.js"></script>
|
<script src="/split/split.min.js"></script>
|
||||||
<script src="/smoothie/smoothie.js"></script>
|
|
||||||
<script src="/static/client.js" type="module"></script>
|
<script src="/static/client.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
104
core/style.css
104
core/style.css
@ -15,11 +15,6 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation {
|
|
||||||
height: auto;
|
|
||||||
margin: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:link {
|
a:link {
|
||||||
color: #268bd2;
|
color: #268bd2;
|
||||||
}
|
}
|
||||||
@ -69,11 +64,6 @@ a:active {
|
|||||||
color: #eee8d5;
|
color: #eee8d5;
|
||||||
}
|
}
|
||||||
|
|
||||||
#input.drop {
|
|
||||||
border: 2px solid;
|
|
||||||
color: #cb4b16;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -110,33 +100,6 @@ a:active {
|
|||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
#auth_greeting {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#auth {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row;
|
|
||||||
align-content: center;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#auth_login {
|
|
||||||
flex: 0 1 auto;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth_or {
|
|
||||||
flex: 0 1 auto;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#auth_guest {
|
|
||||||
flex: 0 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notice {
|
.notice {
|
||||||
color: #cb4b16;
|
color: #cb4b16;
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
@ -162,70 +125,6 @@ a:active {
|
|||||||
.cyan { color: #2aa198; }
|
.cyan { color: #2aa198; }
|
||||||
.green { color: #859900; }
|
.green { color: #859900; }
|
||||||
|
|
||||||
#files_header {
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#files_hide {
|
|
||||||
font-weight: bold;
|
|
||||||
width: 100%;
|
|
||||||
right: 0;
|
|
||||||
flex: 0;
|
|
||||||
padding: 0.25em;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#files_show {
|
|
||||||
display: none;
|
|
||||||
padding: 0.25em;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#filesPane {
|
|
||||||
flex: 1 1 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#filesPane.collapsed {
|
|
||||||
flex: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapsed #files_header {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapsed #files_content {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapsed #files_hide {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapsed #files_show {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#files {
|
|
||||||
list-style-type: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#files > li {
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#files > li.current {
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: #2aa198;
|
|
||||||
}
|
|
||||||
|
|
||||||
#files > li.dirty::after {
|
|
||||||
content: '*';
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip {
|
.tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@ -254,8 +153,7 @@ kbd {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
#permissions, #permissions_settings {
|
.permissions {
|
||||||
visibility: hidden;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: block;
|
display: block;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -39,9 +39,9 @@ function send(response) {
|
|||||||
|
|
||||||
function call_rpc(message) {
|
function call_rpc(message) {
|
||||||
if (message && message.message === 'tfrpc') {
|
if (message && message.message === 'tfrpc') {
|
||||||
let method = g_api[message.method];
|
|
||||||
let id = message.id;
|
let id = message.id;
|
||||||
if (message.method) {
|
if (message.method) {
|
||||||
|
let method = g_api[message.method];
|
||||||
if (method) {
|
if (method) {
|
||||||
try {
|
try {
|
||||||
Promise.resolve(method(...message.params)).then(function(result) {
|
Promise.resolve(method(...message.params)).then(function(result) {
|
||||||
@ -53,7 +53,7 @@ function call_rpc(message) {
|
|||||||
send({message: 'tfrpc', id: id, error: error});
|
send({message: 'tfrpc', id: id, error: error});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(message.method + ' not found.');
|
send({message: 'tfrpc', id: id, error: `Method '${message.method}' not found.`});
|
||||||
}
|
}
|
||||||
} else if (message.error !== undefined) {
|
} else if (message.error !== undefined) {
|
||||||
if (g_calls[id]) {
|
if (g_calls[id]) {
|
||||||
|
2
deps/codemirror/annotatescrollbar.min.js
vendored
2
deps/codemirror/annotatescrollbar.min.js
vendored
@ -1 +1 @@
|
|||||||
!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)}});
|
!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)}});
|
2
deps/codemirror/codemirror.min.js
vendored
2
deps/codemirror/codemirror.min.js
vendored
File diff suppressed because one or more lines are too long
2
deps/codemirror/css.min.js
vendored
2
deps/codemirror/css.min.js
vendored
File diff suppressed because one or more lines are too long
2
deps/codemirror/dialog.min.js
vendored
2
deps/codemirror/dialog.min.js
vendored
@ -1 +1 @@
|
|||||||
!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})});
|
!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})});
|
2
deps/codemirror/htmlmixed.min.js
vendored
2
deps/codemirror/htmlmixed.min.js
vendored
@ -1 +1 @@
|
|||||||
!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")});
|
!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
Normal file
1
deps/codemirror/javascript-lint.min.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
!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})});
|
2
deps/codemirror/javascript.min.js
vendored
2
deps/codemirror/javascript.min.js
vendored
File diff suppressed because one or more lines are too long
32076
deps/codemirror/jshint.js
vendored
Normal file
32076
deps/codemirror/jshint.js
vendored
Normal file
File diff suppressed because one or more lines are too long
80
deps/codemirror/lint.css
vendored
Normal file
80
deps/codemirror/lint.css
vendored
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/* 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
Normal file
292
deps/codemirror/lint.js
vendored
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
// 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
2
deps/codemirror/search.min.js
vendored
2
deps/codemirror/search.min.js
vendored
File diff suppressed because one or more lines are too long
2
deps/codemirror/searchcursor.min.js
vendored
2
deps/codemirror/searchcursor.min.js
vendored
File diff suppressed because one or more lines are too long
22
deps/codemirror/update.sh
vendored
Executable file
22
deps/codemirror/update.sh
vendored
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
LINKS="
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/dialog/dialog.min.css
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/dialog/dialog.min.js
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/edit/trailingspace.min.js
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/lint/javascript-lint.min.js
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/scroll/annotatescrollbar.min.js
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/search/matchesonscrollbar.min.css
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/search/matchesonscrollbar.min.js
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/search/search.min.js
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/addon/search/searchcursor.min.js
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/codemirror.min.css
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/codemirror.min.js
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/mode/css/css.min.js
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/mode/htmlmixed/htmlmixed.min.js
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/mode/javascript/javascript.min.js
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/mode/xml/xml.min.js
|
||||||
|
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.14/theme/base16-dark.min.css
|
||||||
|
"
|
||||||
|
|
||||||
|
for link in $LINKS; do
|
||||||
|
wget $link -O `basename $link`
|
||||||
|
done
|
2
deps/codemirror/xml.min.js
vendored
2
deps/codemirror/xml.min.js
vendored
File diff suppressed because one or more lines are too long
25
deps/libuv/.github/workflows/CI-docs.yml
vendored
Normal file
25
deps/libuv/.github/workflows/CI-docs.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
name: CI-docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'docs/**'
|
||||||
|
- '!docs/code/**'
|
||||||
|
- '.github/workflows/CI-docs.yml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docs-src:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
cache: 'pip' # caching pip dependencies
|
||||||
|
- run: pip install -r docs/requirements.txt
|
||||||
|
- name: html
|
||||||
|
run: |
|
||||||
|
make -C docs html
|
||||||
|
- name: linkcheck
|
||||||
|
run: |
|
||||||
|
make -C docs linkcheck
|
29
deps/libuv/.github/workflows/CI-unix.yml
vendored
29
deps/libuv/.github/workflows/CI-unix.yml
vendored
@ -14,6 +14,19 @@ on:
|
|||||||
- master
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
build-linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: configure
|
||||||
|
run: |
|
||||||
|
./autogen.sh
|
||||||
|
mkdir build
|
||||||
|
(cd build && ../configure)
|
||||||
|
- name: distcheck
|
||||||
|
run: |
|
||||||
|
make -C build distcheck
|
||||||
|
|
||||||
build-android:
|
build-android:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: reactnativecommunity/react-native-android:2020-5-20
|
container: reactnativecommunity/react-native-android:2020-5-20
|
||||||
@ -33,14 +46,14 @@ jobs:
|
|||||||
ls -lh build
|
ls -lh build
|
||||||
|
|
||||||
build-macos:
|
build-macos:
|
||||||
runs-on: macos-10.15
|
runs-on: macos-11
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Envinfo
|
- name: Envinfo
|
||||||
run: npx envinfo
|
run: npx envinfo
|
||||||
- name: Setup
|
- name: Setup
|
||||||
run: |
|
run: |
|
||||||
brew install ninja
|
brew install ninja automake libtool
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: |
|
run: |
|
||||||
mkdir build
|
mkdir build
|
||||||
@ -59,9 +72,16 @@ jobs:
|
|||||||
- name: Test
|
- name: Test
|
||||||
run: |
|
run: |
|
||||||
cd build && ctest -V
|
cd build && ctest -V
|
||||||
|
- name: Autotools configure
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
./autogen.sh
|
||||||
|
mkdir build-auto
|
||||||
|
(cd build-auto && ../configure)
|
||||||
|
make -C build-auto -j4
|
||||||
|
|
||||||
build-ios:
|
build-ios:
|
||||||
runs-on: macos-10.15
|
runs-on: macos-11
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Configure
|
- name: Configure
|
||||||
@ -94,7 +114,6 @@ jobs:
|
|||||||
- {target: mips64, toolchain: gcc-mips64-linux-gnuabi64, cc: mips64-linux-gnuabi64-gcc, qemu: qemu-mips64-static }
|
- {target: mips64, toolchain: gcc-mips64-linux-gnuabi64, cc: mips64-linux-gnuabi64-gcc, qemu: qemu-mips64-static }
|
||||||
- {target: mipsel, toolchain: gcc-mipsel-linux-gnu, cc: mipsel-linux-gnu-gcc, qemu: qemu-mipsel-static }
|
- {target: mipsel, toolchain: gcc-mipsel-linux-gnu, cc: mipsel-linux-gnu-gcc, qemu: qemu-mipsel-static }
|
||||||
- {target: mips64el,toolchain: gcc-mips64el-linux-gnuabi64, cc: mips64el-linux-gnuabi64-gcc,qemu: qemu-mips64el-static }
|
- {target: mips64el,toolchain: gcc-mips64el-linux-gnuabi64, cc: mips64el-linux-gnuabi64-gcc,qemu: qemu-mips64el-static }
|
||||||
- {target: alpha, toolchain: gcc-alpha-linux-gnu, cc: alpha-linux-gnu-gcc, qemu: qemu-alpha-static }
|
|
||||||
- {target: arm (u64 slots), toolchain: gcc-arm-linux-gnueabi, cc: arm-linux-gnueabi-gcc, qemu: qemu-arm-static}
|
- {target: arm (u64 slots), toolchain: gcc-arm-linux-gnueabi, cc: arm-linux-gnueabi-gcc, qemu: qemu-arm-static}
|
||||||
- {target: aarch64 (u64 slots), toolchain: gcc-aarch64-linux-gnu, cc: aarch64-linux-gnu-gcc, qemu: qemu-aarch64-static}
|
- {target: aarch64 (u64 slots), toolchain: gcc-aarch64-linux-gnu, cc: aarch64-linux-gnu-gcc, qemu: qemu-aarch64-static}
|
||||||
- {target: ppc (u64 slots), toolchain: gcc-powerpc-linux-gnu, cc: powerpc-linux-gnu-gcc, qemu: qemu-ppc-static}
|
- {target: ppc (u64 slots), toolchain: gcc-powerpc-linux-gnu, cc: powerpc-linux-gnu-gcc, qemu: qemu-ppc-static}
|
||||||
@ -106,7 +125,7 @@ jobs:
|
|||||||
# this ensure install latest qemu on ubuntu, apt get version is old
|
# this ensure install latest qemu on ubuntu, apt get version is old
|
||||||
env:
|
env:
|
||||||
QEMU_SRC: "http://archive.ubuntu.com/ubuntu/pool/universe/q/qemu"
|
QEMU_SRC: "http://archive.ubuntu.com/ubuntu/pool/universe/q/qemu"
|
||||||
QEMU_VER: "qemu-user-static_4\\.2-.*_amd64.deb$"
|
QEMU_VER: "qemu-user-static_7\\.0+dfsg-.*_amd64.deb$"
|
||||||
run: |
|
run: |
|
||||||
DEB=`curl -s $QEMU_SRC/ | grep -o -E 'href="([^"#]+)"' | cut -d'"' -f2 | grep $QEMU_VER | tail -1`
|
DEB=`curl -s $QEMU_SRC/ | grep -o -E 'href="([^"#]+)"' | cut -d'"' -f2 | grep $QEMU_VER | tail -1`
|
||||||
wget $QEMU_SRC/$DEB
|
wget $QEMU_SRC/$DEB
|
||||||
|
93
deps/libuv/.github/workflows/CI-win.yml
vendored
93
deps/libuv/.github/workflows/CI-win.yml
vendored
@ -16,7 +16,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build-windows:
|
build-windows:
|
||||||
runs-on: windows-${{ matrix.config.server }}
|
runs-on: windows-${{ matrix.config.server }}
|
||||||
name: build-${{ matrix.config.toolchain}}-${{ matrix.config.arch}}
|
name: build-${{ join(matrix.config.*, '-') }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@ -25,27 +25,98 @@ jobs:
|
|||||||
- {toolchain: Visual Studio 16 2019, arch: x64, server: 2019}
|
- {toolchain: Visual Studio 16 2019, arch: x64, server: 2019}
|
||||||
- {toolchain: Visual Studio 17 2022, arch: Win32, server: 2022}
|
- {toolchain: Visual Studio 17 2022, arch: Win32, server: 2022}
|
||||||
- {toolchain: Visual Studio 17 2022, arch: x64, server: 2022}
|
- {toolchain: Visual Studio 17 2022, arch: x64, server: 2022}
|
||||||
|
- {toolchain: Visual Studio 17 2022, arch: x64, server: 2022, config: ASAN}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Envinfo
|
- name: Envinfo
|
||||||
run: npx envinfo
|
run: npx envinfo
|
||||||
- name: Build
|
- name: Build
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run:
|
||||||
mkdir -p build
|
cmake -S . -B build -DBUILD_TESTING=ON
|
||||||
cd build
|
-G "${{ matrix.config.toolchain }}" -A ${{ matrix.config.arch }}
|
||||||
cmake .. -DBUILD_TESTING=ON -G "${{ matrix.config.toolchain }}" -A ${{ matrix.config.arch }}
|
${{ matrix.config.config == 'ASAN' && '-DASAN=on -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded' || '' }}
|
||||||
cmake --build .
|
|
||||||
|
cmake --build build --config RelWithDebInfo
|
||||||
|
|
||||||
|
ls -l build
|
||||||
- name: platform_output
|
- name: platform_output
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run:
|
||||||
build\\Debug\\uv_run_tests.exe platform_output
|
build\\RelWithDebInfo\\uv_run_tests.exe platform_output
|
||||||
- name: platform_output_a
|
- name: platform_output_a
|
||||||
shell: cmd
|
shell: cmd
|
||||||
|
run:
|
||||||
|
build\\RelWithDebInfo\\uv_run_tests_a.exe platform_output
|
||||||
|
- name: Test
|
||||||
|
# only valid with libuv-master with the fix for
|
||||||
|
# https://github.com/libuv/leps/blob/master/005-windows-handles-not-fd.md
|
||||||
|
if: ${{ matrix.config.config != 'ASAN' }}
|
||||||
|
shell: cmd
|
||||||
|
run:
|
||||||
|
cd build
|
||||||
|
|
||||||
|
ctest -C RelWithDebInfo -V
|
||||||
|
- name: Test only static
|
||||||
|
if: ${{ matrix.config.config == 'ASAN' }}
|
||||||
|
shell: cmd
|
||||||
|
run:
|
||||||
|
build\\RelWithDebInfo\\uv_run_tests_a.exe
|
||||||
|
|
||||||
|
build-mingw:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: build-mingw-${{ matrix.config.arch }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
config:
|
||||||
|
- {arch: i686, server: 2022, libgcc: dw2 }
|
||||||
|
- {arch: x86_64, server: 2022, libgcc: seh }
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Install mingw32 environment
|
||||||
run: |
|
run: |
|
||||||
build\\Debug\\uv_run_tests_a.exe platform_output
|
sudo apt update
|
||||||
|
sudo apt install mingw-w64 ninja-build -y
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
cmake -S . -B build -G Ninja -DHOST_ARCH=${{ matrix.config.arch }} -DBUILD_TESTING=ON -DCMAKE_TOOLCHAIN_FILE=cmake-toolchains/cross-mingw32.cmake
|
||||||
|
cmake --build build
|
||||||
|
cmake --install build --prefix "`pwd`/build/usr"
|
||||||
|
mkdir -p build/usr/test build/usr/bin
|
||||||
|
cp -av test/fixtures build/usr/test
|
||||||
|
cp -av build/uv_run_tests_a.exe build/uv_run_tests.exe \
|
||||||
|
`${{ matrix.config.arch }}-w64-mingw32-gcc -print-file-name=libgcc_s_${{ matrix.config.libgcc }}-1.dll` \
|
||||||
|
`${{ matrix.config.arch }}-w64-mingw32-gcc -print-file-name=libwinpthread-1.dll` \
|
||||||
|
`${{ matrix.config.arch }}-w64-mingw32-gcc -print-file-name=libatomic-1.dll` \
|
||||||
|
build/usr/bin
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: mingw-${{ matrix.config.arch }}
|
||||||
|
path: build/usr/**/*
|
||||||
|
retention-days: 2
|
||||||
|
|
||||||
|
test-mingw:
|
||||||
|
runs-on: windows-${{ matrix.config.server }}
|
||||||
|
name: test-mingw-${{ matrix.config.arch }}
|
||||||
|
needs: build-mingw
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
config:
|
||||||
|
- {arch: i686, server: 2022}
|
||||||
|
- {arch: x86_64, server: 2022}
|
||||||
|
steps:
|
||||||
|
- name: Download build artifacts
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: mingw-${{ matrix.config.arch }}
|
||||||
- name: Test
|
- name: Test
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
cd build
|
bin\uv_run_tests_a.exe
|
||||||
ctest -C Debug -V
|
- name: Test
|
||||||
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
bin\uv_run_tests.exe
|
||||||
|
41
deps/libuv/.github/workflows/sanitizer.yml
vendored
41
deps/libuv/.github/workflows/sanitizer.yml
vendored
@ -14,7 +14,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sanitizers:
|
sanitizers:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Setup
|
- name: Setup
|
||||||
@ -22,15 +22,7 @@ jobs:
|
|||||||
sudo apt-get install ninja-build
|
sudo apt-get install ninja-build
|
||||||
- name: Envinfo
|
- name: Envinfo
|
||||||
run: npx envinfo
|
run: npx envinfo
|
||||||
- name: TSAN Build
|
|
||||||
run: |
|
|
||||||
mkdir build-tsan
|
|
||||||
(cd build-tsan && cmake .. -G Ninja -DBUILD_TESTING=ON -DTSAN=ON -DCMAKE_BUILD_TYPE=Release)
|
|
||||||
cmake --build build-tsan
|
|
||||||
- name: TSAN Test
|
|
||||||
continue-on-error: true # currently permit failures
|
|
||||||
run: |
|
|
||||||
./build-tsan/uv_run_tests_a
|
|
||||||
- name: ASAN Build
|
- name: ASAN Build
|
||||||
run: |
|
run: |
|
||||||
mkdir build-asan
|
mkdir build-asan
|
||||||
@ -39,3 +31,32 @@ jobs:
|
|||||||
- name: ASAN Test
|
- name: ASAN Test
|
||||||
run: |
|
run: |
|
||||||
./build-asan/uv_run_tests_a
|
./build-asan/uv_run_tests_a
|
||||||
|
|
||||||
|
- name: MSAN Build
|
||||||
|
run: |
|
||||||
|
mkdir build-msan
|
||||||
|
(cd build-msan && cmake .. -G Ninja -DBUILD_TESTING=ON -DMSAN=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang)
|
||||||
|
cmake --build build-msan
|
||||||
|
- name: MSAN Test
|
||||||
|
run: |
|
||||||
|
./build-msan/uv_run_tests_a
|
||||||
|
|
||||||
|
- name: TSAN Build
|
||||||
|
run: |
|
||||||
|
mkdir build-tsan
|
||||||
|
(cd build-tsan && cmake .. -G Ninja -DBUILD_TESTING=ON -DTSAN=ON -DCMAKE_BUILD_TYPE=Release)
|
||||||
|
cmake --build build-tsan
|
||||||
|
- name: TSAN Test
|
||||||
|
# Note: path must be absolute because some tests chdir.
|
||||||
|
# TSan exits with an error when it can't find the file.
|
||||||
|
run: |
|
||||||
|
env TSAN_OPTIONS="suppressions=$PWD/tsansupp.txt" ./build-tsan/uv_run_tests_a
|
||||||
|
|
||||||
|
- name: UBSAN Build
|
||||||
|
run: |
|
||||||
|
mkdir build-ubsan
|
||||||
|
(cd build-ubsan && cmake .. -G Ninja -DBUILD_TESTING=ON -DUBSAN=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang)
|
||||||
|
cmake --build build-ubsan
|
||||||
|
- name: UBSAN Test
|
||||||
|
run: |
|
||||||
|
./build-ubsan/uv_run_tests_a
|
||||||
|
3
deps/libuv/.mailmap
vendored
3
deps/libuv/.mailmap
vendored
@ -29,6 +29,7 @@ Keno Fischer <kenof@stanford.edu> <kfischer+github@college.harvard.edu>
|
|||||||
Keno Fischer <kenof@stanford.edu> <kfischer@college.harvard.edu>
|
Keno Fischer <kenof@stanford.edu> <kfischer@college.harvard.edu>
|
||||||
Leith Bade <leith@leithalweapon.geek.nz> <leith@mapbox.com>
|
Leith Bade <leith@leithalweapon.geek.nz> <leith@mapbox.com>
|
||||||
Leonard Hecker <leonard.hecker91@gmail.com> <leonard@hecker.io>
|
Leonard Hecker <leonard.hecker91@gmail.com> <leonard@hecker.io>
|
||||||
|
Lewis Russell <me@lewisr.dev> <lewis6991@gmail.com>
|
||||||
Maciej Małecki <maciej.malecki@notimplemented.org> <me@mmalecki.com>
|
Maciej Małecki <maciej.malecki@notimplemented.org> <me@mmalecki.com>
|
||||||
Marc Schlaich <marc.schlaich@googlemail.com> <marc.schlaich@gmail.com>
|
Marc Schlaich <marc.schlaich@googlemail.com> <marc.schlaich@gmail.com>
|
||||||
Michael <michael_dawson@ca.ibm.com>
|
Michael <michael_dawson@ca.ibm.com>
|
||||||
@ -60,5 +61,7 @@ gengjiawen <technicalcute@gmail.com>
|
|||||||
jBarz <jBarz@users.noreply.github.com> <jbarboza@ca.ibm.com>
|
jBarz <jBarz@users.noreply.github.com> <jbarboza@ca.ibm.com>
|
||||||
jBarz <jBarz@users.noreply.github.com> <jbarz@users.noreply.github.com>
|
jBarz <jBarz@users.noreply.github.com> <jbarz@users.noreply.github.com>
|
||||||
ptlomholt <pt@lomholt.com>
|
ptlomholt <pt@lomholt.com>
|
||||||
|
theanarkh <2923878201@qq.com> <theratliter@gmail.com>
|
||||||
tjarlama <59913901+tjarlama@users.noreply.github.com> <tjarlama@gmail.com>
|
tjarlama <59913901+tjarlama@users.noreply.github.com> <tjarlama@gmail.com>
|
||||||
|
ywave620 <rogertyang@tencent.com> <60539365+ywave620@users.noreply.github.com>
|
||||||
zlargon <zlargon1988@gmail.com>
|
zlargon <zlargon1988@gmail.com>
|
||||||
|
6
deps/libuv/.readthedocs.yaml
vendored
6
deps/libuv/.readthedocs.yaml
vendored
@ -5,7 +5,11 @@ sphinx:
|
|||||||
configuration: null
|
configuration: null
|
||||||
fail_on_warning: false
|
fail_on_warning: false
|
||||||
|
|
||||||
|
build:
|
||||||
|
os: "ubuntu-22.04"
|
||||||
|
tools:
|
||||||
|
python: "3.9"
|
||||||
|
|
||||||
python:
|
python:
|
||||||
version: 3.8
|
|
||||||
install:
|
install:
|
||||||
- requirements: docs/requirements.txt
|
- requirements: docs/requirements.txt
|
||||||
|
31
deps/libuv/AUTHORS
vendored
31
deps/libuv/AUTHORS
vendored
@ -517,3 +517,34 @@ chucksilvers <chuq@chuq.com>
|
|||||||
Sergey Fedorov <vital.had@gmail.com>
|
Sergey Fedorov <vital.had@gmail.com>
|
||||||
theanarkh <2923878201@qq.com>
|
theanarkh <2923878201@qq.com>
|
||||||
Samuel Cabrero <samuelcabrero@gmail.com>
|
Samuel Cabrero <samuelcabrero@gmail.com>
|
||||||
|
自发对称破缺 <429839446@qq.com>
|
||||||
|
Luan Devecchi <luan@engineer.com>
|
||||||
|
Steven Schveighoffer <schveiguy@gmail.com>
|
||||||
|
number201724 <number201724@me.com>
|
||||||
|
Daniel <reymond315qq@gmail.com>
|
||||||
|
Christian Clason <christian.clason@uni-due.de>
|
||||||
|
ywave620 <rogertyang@tencent.com>
|
||||||
|
jensbjorgensen <jbj1@ultraemail.net>
|
||||||
|
daomingq <daoming.qiu@intel.com>
|
||||||
|
Qix <Qix-@users.noreply.github.com>
|
||||||
|
Edward Humes <29870961+aurxenon@users.noreply.github.com>
|
||||||
|
Tim Besard <tim.besard@gmail.com>
|
||||||
|
Sergey Rubanov <chi187@gmail.com>
|
||||||
|
Stefan Stojanovic <StefanStojanovic@users.noreply.github.com>
|
||||||
|
Zvicii <zvicii@qq.com>
|
||||||
|
dundargoc <33953936+dundargoc@users.noreply.github.com>
|
||||||
|
Jack·Boos·Yu <47264268+JackBoosY@users.noreply.github.com>
|
||||||
|
panran <310762957@qq.com>
|
||||||
|
Tamás Bálint Misius <lbphacker@gmail.com>
|
||||||
|
Bruno Passeri <Varstahl@users.noreply.github.com>
|
||||||
|
Jason Zhang <xzha4350@gmail.com>
|
||||||
|
Lewis Russell <me@lewisr.dev>
|
||||||
|
sivadeilra <arlie.davis@gmail.com>
|
||||||
|
cui fliter <imcusg@gmail.com>
|
||||||
|
Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com>
|
||||||
|
Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
|
||||||
|
Stefan Karpinski <stefan@karpinski.org>
|
||||||
|
liuxiang88 <94350585+liuxiang88@users.noreply.github.com>
|
||||||
|
Jeffrey H. Johnson <trnsz@pobox.com>
|
||||||
|
Abdirahim Musse <33973272+abmusse@users.noreply.github.com>
|
||||||
|
小明 <7737673+caobug@users.noreply.github.com>
|
||||||
|
156
deps/libuv/CMakeLists.txt
vendored
156
deps/libuv/CMakeLists.txt
vendored
@ -1,8 +1,13 @@
|
|||||||
cmake_minimum_required(VERSION 3.4)
|
cmake_minimum_required(VERSION 3.4)
|
||||||
project(libuv LANGUAGES C)
|
|
||||||
|
|
||||||
cmake_policy(SET CMP0057 NEW) # Enable IN_LIST operator
|
if(POLICY CMP0091)
|
||||||
cmake_policy(SET CMP0064 NEW) # Support if (TEST) operator
|
cmake_policy(SET CMP0091 NEW) # Enable MSVC_RUNTIME_LIBRARY setting
|
||||||
|
endif()
|
||||||
|
if(POLICY CMP0092)
|
||||||
|
cmake_policy(SET CMP0092 NEW) # disable /W3 warning, if possible
|
||||||
|
endif()
|
||||||
|
|
||||||
|
project(libuv LANGUAGES C)
|
||||||
|
|
||||||
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
|
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
@ -17,9 +22,13 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
|
|||||||
set(CMAKE_C_EXTENSIONS ON)
|
set(CMAKE_C_EXTENSIONS ON)
|
||||||
set(CMAKE_C_STANDARD 90)
|
set(CMAKE_C_STANDARD 90)
|
||||||
|
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
option(LIBUV_BUILD_SHARED "Build shared lib" ON)
|
||||||
|
|
||||||
cmake_dependent_option(LIBUV_BUILD_TESTS
|
cmake_dependent_option(LIBUV_BUILD_TESTS
|
||||||
"Build the unit tests when BUILD_TESTING is enabled and we are the root project" ON
|
"Build the unit tests when BUILD_TESTING is enabled and we are the root project" ON
|
||||||
"BUILD_TESTING;CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF)
|
"BUILD_TESTING;LIBUV_BUILD_SHARED;CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF)
|
||||||
cmake_dependent_option(LIBUV_BUILD_BENCH
|
cmake_dependent_option(LIBUV_BUILD_BENCH
|
||||||
"Build the benchmarks when building unit tests and we are the root project" ON
|
"Build the benchmarks when building unit tests and we are the root project" ON
|
||||||
"LIBUV_BUILD_TESTS" OFF)
|
"LIBUV_BUILD_TESTS" OFF)
|
||||||
@ -27,28 +36,61 @@ cmake_dependent_option(LIBUV_BUILD_BENCH
|
|||||||
# Qemu Build
|
# Qemu Build
|
||||||
option(QEMU "build for qemu" OFF)
|
option(QEMU "build for qemu" OFF)
|
||||||
if(QEMU)
|
if(QEMU)
|
||||||
add_definitions(-D__QEMU__=1)
|
list(APPEND uv_defines __QEMU__=1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Note: these are mutually exclusive.
|
||||||
option(ASAN "Enable AddressSanitizer (ASan)" OFF)
|
option(ASAN "Enable AddressSanitizer (ASan)" OFF)
|
||||||
|
option(MSAN "Enable MemorySanitizer (MSan)" OFF)
|
||||||
option(TSAN "Enable ThreadSanitizer (TSan)" OFF)
|
option(TSAN "Enable ThreadSanitizer (TSan)" OFF)
|
||||||
|
option(UBSAN "Enable UndefinedBehaviorSanitizer (UBSan)" OFF)
|
||||||
|
|
||||||
if((ASAN OR TSAN) AND NOT (CMAKE_C_COMPILER_ID MATCHES "AppleClang|GNU|Clang"))
|
if(MSAN AND NOT CMAKE_C_COMPILER_ID MATCHES "AppleClang|Clang")
|
||||||
message(SEND_ERROR "Sanitizer support requires clang or gcc. Try again with -DCMAKE_C_COMPILER.")
|
message(SEND_ERROR "MemorySanitizer requires clang. Try again with -DCMAKE_C_COMPILER=clang")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(ASAN)
|
if(ASAN)
|
||||||
add_definitions(-D__ASAN__=1)
|
list(APPEND uv_defines __ASAN__=1)
|
||||||
|
if(CMAKE_C_COMPILER_ID MATCHES "AppleClang|GNU|Clang")
|
||||||
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=address")
|
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=address")
|
||||||
set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=address")
|
set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=address")
|
||||||
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=address")
|
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=address")
|
||||||
|
elseif(MSVC)
|
||||||
|
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address")
|
||||||
|
else()
|
||||||
|
message(SEND_ERROR "AddressSanitizer support requires clang, gcc, or msvc. Try again with -DCMAKE_C_COMPILER.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(MSAN)
|
||||||
|
list(APPEND uv_defines __MSAN__=1)
|
||||||
|
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=memory")
|
||||||
|
set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=memory")
|
||||||
|
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=memory")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(TSAN)
|
if(TSAN)
|
||||||
add_definitions(-D__TSAN__=1)
|
list(APPEND uv_defines __TSAN__=1)
|
||||||
|
if(CMAKE_C_COMPILER_ID MATCHES "AppleClang|GNU|Clang")
|
||||||
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=thread")
|
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=thread")
|
||||||
set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=thread")
|
set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=thread")
|
||||||
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=thread")
|
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=thread")
|
||||||
|
else()
|
||||||
|
message(SEND_ERROR "ThreadSanitizer support requires clang or gcc. Try again with -DCMAKE_C_COMPILER.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(UBSAN)
|
||||||
|
list(APPEND uv_defines __UBSAN__=1)
|
||||||
|
if(CMAKE_C_COMPILER_ID MATCHES "AppleClang|GNU|Clang")
|
||||||
|
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=undefined")
|
||||||
|
set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=undefined")
|
||||||
|
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=undefined")
|
||||||
|
elseif(MSVC)
|
||||||
|
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=undefined")
|
||||||
|
else()
|
||||||
|
message(SEND_ERROR "UndefinedBehaviorSanitizer support requires clang, gcc, or msvc. Try again with -DCMAKE_C_COMPILER.")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Compiler check
|
# Compiler check
|
||||||
@ -126,6 +168,7 @@ set(uv_sources
|
|||||||
src/random.c
|
src/random.c
|
||||||
src/strscpy.c
|
src/strscpy.c
|
||||||
src/strtok.c
|
src/strtok.c
|
||||||
|
src/thread-common.c
|
||||||
src/threadpool.c
|
src/threadpool.c
|
||||||
src/timer.c
|
src/timer.c
|
||||||
src/uv-common.c
|
src/uv-common.c
|
||||||
@ -140,7 +183,10 @@ if(WIN32)
|
|||||||
advapi32
|
advapi32
|
||||||
iphlpapi
|
iphlpapi
|
||||||
userenv
|
userenv
|
||||||
ws2_32)
|
ws2_32
|
||||||
|
dbghelp
|
||||||
|
ole32
|
||||||
|
uuid)
|
||||||
list(APPEND uv_sources
|
list(APPEND uv_sources
|
||||||
src/win/async.c
|
src/win/async.c
|
||||||
src/win/core.c
|
src/win/core.c
|
||||||
@ -216,15 +262,11 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Android")
|
|||||||
list(APPEND uv_defines _GNU_SOURCE)
|
list(APPEND uv_defines _GNU_SOURCE)
|
||||||
list(APPEND uv_libraries dl)
|
list(APPEND uv_libraries dl)
|
||||||
list(APPEND uv_sources
|
list(APPEND uv_sources
|
||||||
src/unix/linux-core.c
|
src/unix/linux.c
|
||||||
src/unix/linux-inotify.c
|
|
||||||
src/unix/linux-syscalls.c
|
|
||||||
src/unix/procfs-exepath.c
|
src/unix/procfs-exepath.c
|
||||||
src/unix/pthread-fixes.c
|
|
||||||
src/unix/random-getentropy.c
|
src/unix/random-getentropy.c
|
||||||
src/unix/random-getrandom.c
|
src/unix/random-getrandom.c
|
||||||
src/unix/random-sysctl-linux.c
|
src/unix/random-sysctl-linux.c)
|
||||||
src/unix/epoll.c)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(APPLE OR CMAKE_SYSTEM_NAME MATCHES "Android|Linux")
|
if(APPLE OR CMAKE_SYSTEM_NAME MATCHES "Android|Linux")
|
||||||
@ -270,22 +312,14 @@ if(CMAKE_SYSTEM_NAME STREQUAL "GNU")
|
|||||||
src/unix/hurd.c)
|
src/unix/hurd.c)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "kFreeBSD")
|
|
||||||
list(APPEND uv_defines _GNU_SOURCE)
|
|
||||||
list(APPEND uv_libraries dl freebsd-glue)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||||
list(APPEND uv_defines _GNU_SOURCE _POSIX_C_SOURCE=200112)
|
list(APPEND uv_defines _GNU_SOURCE _POSIX_C_SOURCE=200112)
|
||||||
list(APPEND uv_libraries dl rt)
|
list(APPEND uv_libraries dl rt)
|
||||||
list(APPEND uv_sources
|
list(APPEND uv_sources
|
||||||
src/unix/linux-core.c
|
src/unix/linux.c
|
||||||
src/unix/linux-inotify.c
|
|
||||||
src/unix/linux-syscalls.c
|
|
||||||
src/unix/procfs-exepath.c
|
src/unix/procfs-exepath.c
|
||||||
src/unix/random-getrandom.c
|
src/unix/random-getrandom.c
|
||||||
src/unix/random-sysctl-linux.c
|
src/unix/random-sysctl-linux.c)
|
||||||
src/unix/epoll.c)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "NetBSD")
|
if(CMAKE_SYSTEM_NAME STREQUAL "NetBSD")
|
||||||
@ -316,7 +350,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "OS390")
|
|||||||
list(APPEND uv_defines _XOPEN_SOURCE=600)
|
list(APPEND uv_defines _XOPEN_SOURCE=600)
|
||||||
list(APPEND uv_defines _XOPEN_SOURCE_EXTENDED)
|
list(APPEND uv_defines _XOPEN_SOURCE_EXTENDED)
|
||||||
list(APPEND uv_sources
|
list(APPEND uv_sources
|
||||||
src/unix/pthread-fixes.c
|
|
||||||
src/unix/os390.c
|
src/unix/os390.c
|
||||||
src/unix/os390-syscalls.c
|
src/unix/os390-syscalls.c
|
||||||
src/unix/os390-proctitle.c)
|
src/unix/os390-proctitle.c)
|
||||||
@ -354,6 +387,10 @@ if(CMAKE_SYSTEM_NAME STREQUAL "OS400")
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "SunOS")
|
if(CMAKE_SYSTEM_NAME STREQUAL "SunOS")
|
||||||
|
if(CMAKE_SYSTEM_VERSION STREQUAL "5.10")
|
||||||
|
list(APPEND uv_defines SUNOS_NO_IFADDRS)
|
||||||
|
list(APPEND uv_libraries rt)
|
||||||
|
endif()
|
||||||
list(APPEND uv_defines __EXTENSIONS__ _XOPEN_SOURCE=500 _REENTRANT)
|
list(APPEND uv_defines __EXTENSIONS__ _XOPEN_SOURCE=500 _REENTRANT)
|
||||||
list(APPEND uv_libraries kstat nsl sendfile socket)
|
list(APPEND uv_libraries kstat nsl sendfile socket)
|
||||||
list(APPEND uv_sources
|
list(APPEND uv_sources
|
||||||
@ -388,25 +425,42 @@ if(APPLE OR CMAKE_SYSTEM_NAME MATCHES "DragonFly|FreeBSD|Linux|NetBSD|OpenBSD")
|
|||||||
list(APPEND uv_test_libraries util)
|
list(APPEND uv_test_libraries util)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library(uv SHARED ${uv_sources})
|
if(CYGWIN OR MSYS)
|
||||||
target_compile_definitions(uv
|
list(APPEND uv_defines _GNU_SOURCE)
|
||||||
|
list(APPEND uv_sources
|
||||||
|
src/unix/cygwin.c
|
||||||
|
src/unix/bsd-ifaddrs.c
|
||||||
|
src/unix/no-fsevents.c
|
||||||
|
src/unix/no-proctitle.c
|
||||||
|
src/unix/posix-hrtime.c
|
||||||
|
src/unix/posix-poll.c
|
||||||
|
src/unix/procfs-exepath.c
|
||||||
|
src/unix/sysinfo-loadavg.c
|
||||||
|
src/unix/sysinfo-memory.c)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(LIBUV_BUILD_SHARED)
|
||||||
|
add_library(uv SHARED ${uv_sources})
|
||||||
|
target_compile_definitions(uv
|
||||||
INTERFACE
|
INTERFACE
|
||||||
USING_UV_SHARED=1
|
USING_UV_SHARED=1
|
||||||
PRIVATE
|
PRIVATE
|
||||||
BUILDING_UV_SHARED=1
|
BUILDING_UV_SHARED=1
|
||||||
${uv_defines})
|
${uv_defines})
|
||||||
target_compile_options(uv PRIVATE ${uv_cflags})
|
target_compile_options(uv PRIVATE ${uv_cflags})
|
||||||
target_include_directories(uv
|
target_include_directories(uv
|
||||||
PUBLIC
|
PUBLIC
|
||||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||||
PRIVATE
|
PRIVATE
|
||||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>)
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>)
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "OS390")
|
if(CMAKE_SYSTEM_NAME STREQUAL "OS390")
|
||||||
target_include_directories(uv PUBLIC $<BUILD_INTERFACE:${ZOSLIB_DIR}/include>)
|
target_include_directories(uv PUBLIC $<BUILD_INTERFACE:${ZOSLIB_DIR}/include>)
|
||||||
set_target_properties(uv PROPERTIES LINKER_LANGUAGE CXX)
|
set_target_properties(uv PROPERTIES LINKER_LANGUAGE CXX)
|
||||||
|
endif()
|
||||||
|
target_link_libraries(uv ${uv_libraries})
|
||||||
|
set_target_properties(uv PROPERTIES OUTPUT_NAME "uv")
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(uv ${uv_libraries})
|
|
||||||
|
|
||||||
add_library(uv_a STATIC ${uv_sources})
|
add_library(uv_a STATIC ${uv_sources})
|
||||||
target_compile_definitions(uv_a PRIVATE ${uv_defines})
|
target_compile_definitions(uv_a PRIVATE ${uv_defines})
|
||||||
@ -422,6 +476,10 @@ if(CMAKE_SYSTEM_NAME STREQUAL "OS390")
|
|||||||
set_target_properties(uv_a PROPERTIES LINKER_LANGUAGE CXX)
|
set_target_properties(uv_a PROPERTIES LINKER_LANGUAGE CXX)
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(uv_a ${uv_libraries})
|
target_link_libraries(uv_a ${uv_libraries})
|
||||||
|
set_target_properties(uv_a PROPERTIES OUTPUT_NAME "uv")
|
||||||
|
if(MSVC)
|
||||||
|
set_target_properties(uv_a PROPERTIES PREFIX "lib")
|
||||||
|
endif()
|
||||||
|
|
||||||
if(LIBUV_BUILD_TESTS)
|
if(LIBUV_BUILD_TESTS)
|
||||||
# Small hack: use ${uv_test_sources} now to get the runner skeleton,
|
# Small hack: use ${uv_test_sources} now to get the runner skeleton,
|
||||||
@ -584,6 +642,7 @@ if(LIBUV_BUILD_TESTS)
|
|||||||
test/test-tcp-rst.c
|
test/test-tcp-rst.c
|
||||||
test/test-tcp-shutdown-after-write.c
|
test/test-tcp-shutdown-after-write.c
|
||||||
test/test-tcp-try-write.c
|
test/test-tcp-try-write.c
|
||||||
|
test/test-tcp-write-in-a-row.c
|
||||||
test/test-tcp-try-write-error.c
|
test/test-tcp-try-write-error.c
|
||||||
test/test-tcp-unexpected-read.c
|
test/test-tcp-unexpected-read.c
|
||||||
test/test-tcp-write-after-connect.c
|
test/test-tcp-write-after-connect.c
|
||||||
@ -592,6 +651,7 @@ if(LIBUV_BUILD_TESTS)
|
|||||||
test/test-tcp-write-to-half-open-connection.c
|
test/test-tcp-write-to-half-open-connection.c
|
||||||
test/test-tcp-writealot.c
|
test/test-tcp-writealot.c
|
||||||
test/test-test-macros.c
|
test/test-test-macros.c
|
||||||
|
test/test-thread-affinity.c
|
||||||
test/test-thread-equal.c
|
test/test-thread-equal.c
|
||||||
test/test-thread.c
|
test/test-thread.c
|
||||||
test/test-threadpool-cancel.c
|
test/test-threadpool-cancel.c
|
||||||
@ -624,6 +684,7 @@ if(LIBUV_BUILD_TESTS)
|
|||||||
test/test-udp-sendmmsg-error.c
|
test/test-udp-sendmmsg-error.c
|
||||||
test/test-udp-send-unreachable.c
|
test/test-udp-send-unreachable.c
|
||||||
test/test-udp-try-send.c
|
test/test-udp-try-send.c
|
||||||
|
test/test-udp-recv-in-a-row.c
|
||||||
test/test-uname.c
|
test/test-uname.c
|
||||||
test/test-walk-handles.c
|
test/test-walk-handles.c
|
||||||
test/test-watcher-cross-stop.c)
|
test/test-watcher-cross-stop.c)
|
||||||
@ -667,27 +728,36 @@ string(REPLACE ";" " " LIBS "${LIBS}")
|
|||||||
file(STRINGS configure.ac configure_ac REGEX ^AC_INIT)
|
file(STRINGS configure.ac configure_ac REGEX ^AC_INIT)
|
||||||
string(REGEX MATCH "([0-9]+)[.][0-9]+[.][0-9]+" PACKAGE_VERSION "${configure_ac}")
|
string(REGEX MATCH "([0-9]+)[.][0-9]+[.][0-9]+" PACKAGE_VERSION "${configure_ac}")
|
||||||
set(UV_VERSION_MAJOR "${CMAKE_MATCH_1}")
|
set(UV_VERSION_MAJOR "${CMAKE_MATCH_1}")
|
||||||
# The version in the filename is mirroring the behaviour of autotools.
|
|
||||||
set_target_properties(uv PROPERTIES
|
|
||||||
VERSION ${UV_VERSION_MAJOR}.0.0
|
|
||||||
SOVERSION ${UV_VERSION_MAJOR})
|
|
||||||
set(includedir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR})
|
set(includedir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR})
|
||||||
set(libdir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR})
|
set(libdir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR})
|
||||||
set(prefix ${CMAKE_INSTALL_PREFIX})
|
set(prefix ${CMAKE_INSTALL_PREFIX})
|
||||||
configure_file(libuv.pc.in libuv.pc @ONLY)
|
|
||||||
configure_file(libuv-static.pc.in libuv-static.pc @ONLY)
|
configure_file(libuv-static.pc.in libuv-static.pc @ONLY)
|
||||||
|
|
||||||
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||||
install(FILES ${PROJECT_BINARY_DIR}/libuv.pc ${PROJECT_BINARY_DIR}/libuv-static.pc
|
install(FILES LICENSE-extra DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||||
|
install(FILES ${PROJECT_BINARY_DIR}/libuv-static.pc
|
||||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||||
install(TARGETS uv EXPORT libuvConfig
|
install(TARGETS uv_a EXPORT libuvConfig
|
||||||
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
|
install(EXPORT libuvConfig
|
||||||
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libuv
|
||||||
|
NAMESPACE libuv::)
|
||||||
|
|
||||||
|
if(LIBUV_BUILD_SHARED)
|
||||||
|
# The version in the filename is mirroring the behaviour of autotools.
|
||||||
|
set_target_properties(uv PROPERTIES
|
||||||
|
VERSION ${UV_VERSION_MAJOR}.0.0
|
||||||
|
SOVERSION ${UV_VERSION_MAJOR})
|
||||||
|
configure_file(libuv.pc.in libuv.pc @ONLY)
|
||||||
|
install(FILES ${PROJECT_BINARY_DIR}/libuv.pc
|
||||||
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||||
|
install(TARGETS uv EXPORT libuvConfig
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
install(TARGETS uv_a EXPORT libuvConfig
|
endif()
|
||||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
|
||||||
install(EXPORT libuvConfig DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libuv)
|
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
set(CMAKE_DEBUG_POSTFIX d)
|
set(CMAKE_DEBUG_POSTFIX d)
|
||||||
|
354
deps/libuv/ChangeLog
vendored
354
deps/libuv/ChangeLog
vendored
@ -1,4 +1,356 @@
|
|||||||
2022.07.12, Version 1.44.2 (Stable)
|
2023.06.30, Version 1.46.0 (Stable)
|
||||||
|
|
||||||
|
Changes since version 1.45.0:
|
||||||
|
|
||||||
|
* Add SHA to ChangeLog (Santiago Gimeno)
|
||||||
|
|
||||||
|
* misc: update readthedocs config (Jameson Nash)
|
||||||
|
|
||||||
|
* test: remove erroneous RETURN_SKIP (Ben Noordhuis)
|
||||||
|
|
||||||
|
* android: disable io_uring support (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: add some more iouring backed fs ops (Santiago Gimeno)
|
||||||
|
|
||||||
|
* build: add autoconf option for disable-maintainer-mode (Jameson Nash)
|
||||||
|
|
||||||
|
* fs: use WTF-8 on Windows (Stefan Karpinski)
|
||||||
|
|
||||||
|
* unix,win: replace QUEUE with struct uv__queue (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: fs_read to use io_uring if iovcnt > IOV_MAX (Santiago Gimeno)
|
||||||
|
|
||||||
|
* ios: fix uv_getrusage() ru_maxrss calculation (Ben Noordhuis)
|
||||||
|
|
||||||
|
* include: update outdated code comment (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: support abstract unix sockets (Ben Noordhuis)
|
||||||
|
|
||||||
|
* unix,win: add UV_PIPE_NO_TRUNCATE flag (Ben Noordhuis)
|
||||||
|
|
||||||
|
* unix: add loongarch support (liuxiang88)
|
||||||
|
|
||||||
|
* doc: add DPS8M to LINKS.md (Jeffrey H. Johnson)
|
||||||
|
|
||||||
|
* include: add EUNATCH errno mapping (Abdirahim Musse)
|
||||||
|
|
||||||
|
* src: don't run timers if loop is stopped/unref'd (Trevor Norris)
|
||||||
|
|
||||||
|
* win: fix -Wpointer-to-int-cast warning (Ben Noordhuis)
|
||||||
|
|
||||||
|
* test,win: fix -Wunused-variable warning (Ben Noordhuis)
|
||||||
|
|
||||||
|
* test,win: fix -Wformat warning (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: work around io_uring IORING_OP_CLOSE bug (Ben Noordhuis)
|
||||||
|
|
||||||
|
* win: remove unused functions (Ben Noordhuis)
|
||||||
|
|
||||||
|
* bench: add bench to check uv_loop_alive (Trevor Norris)
|
||||||
|
|
||||||
|
* test: add uv_cancel test for threadpool (Trevor Norris)
|
||||||
|
|
||||||
|
* unix: skip prohibited syscalls on tvOS and watchOS (小明)
|
||||||
|
|
||||||
|
* unix,fs: make no_pwritev access thread-safe (Santiago Gimeno)
|
||||||
|
|
||||||
|
* unix: fix build for lower versions of Android (小明)
|
||||||
|
|
||||||
|
|
||||||
|
2023.05.19, Version 1.45.0 (Stable), 96e05543f53b19d9642b4b0dd73b86ad3cea313e
|
||||||
|
|
||||||
|
Changes since version 1.44.2:
|
||||||
|
|
||||||
|
* win: remove stdint-msvc2008.h (Ben Noordhuis)
|
||||||
|
|
||||||
|
* android: remove pthread-fixes.c (Ben Noordhuis)
|
||||||
|
|
||||||
|
* build: enable MSVC_RUNTIME_LIBRARY setting (自发对称破缺)
|
||||||
|
|
||||||
|
* unix: switch to c11 atomics (Ben Noordhuis)
|
||||||
|
|
||||||
|
* unix: don't accept() connections in a loop (Ben Noordhuis)
|
||||||
|
|
||||||
|
* win: fix off-by-1 buffer overrun in uv_exepath() (Ben Noordhuis)
|
||||||
|
|
||||||
|
* build: switch ci from macos-10.15 to macos-11 (Ben Noordhuis)
|
||||||
|
|
||||||
|
* win: fix thread race in uv_cwd() and uv_chdir() (Ben Noordhuis)
|
||||||
|
|
||||||
|
* unix,win: remove UV_HANDLE_SHUTTING flag (Santiago Gimeno)
|
||||||
|
|
||||||
|
* win: support Windows 11 in uv_os_uname() (Luan Devecchi)
|
||||||
|
|
||||||
|
* unix: fix uv_getrusage() ru_maxrss reporting (Ben Noordhuis)
|
||||||
|
|
||||||
|
* doc: add note about offset -1 in uv_fs_read/write (Steven Schveighoffer)
|
||||||
|
|
||||||
|
* test: fix musl libc.a dlerror() test expectation (Ben Noordhuis)
|
||||||
|
|
||||||
|
* kqueue: DRY file descriptor deletion logic (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: teach uv_get_constrained_memory() cgroupsv2 (Ben Noordhuis)
|
||||||
|
|
||||||
|
* build: upgrade qemu-user-static package (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: move epoll.c back into linux-core.c (Ben Noordhuis)
|
||||||
|
|
||||||
|
* unix: remove pre-macos 10.8 compatibility hack (Ben Noordhuis)
|
||||||
|
|
||||||
|
* unix,win: fix memory leak in uv_fs_scandir() (Ben Noordhuis)
|
||||||
|
|
||||||
|
* build: restore qemu download logic (Ben Noordhuis)
|
||||||
|
|
||||||
|
* win: fix uv__pipe_accept memory leak (number201724)
|
||||||
|
|
||||||
|
* doc: update LINKS.md (Daniel)
|
||||||
|
|
||||||
|
* unix: simplify atomic op in uv_tty_reset_mode() (Ben Noordhuis)
|
||||||
|
|
||||||
|
* build: add LIBUV_BUILD_SHARED cmake option (Christian Clason)
|
||||||
|
|
||||||
|
* linux: remove unused or obsolete syscall wrappers (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: merge files back into single file (Ben Noordhuis)
|
||||||
|
|
||||||
|
* stream: process more than one write req per loop tick (ywave620)
|
||||||
|
|
||||||
|
* unix,win: give thread pool threads an 8 MB stack (Ben Noordhuis)
|
||||||
|
|
||||||
|
* build: add MemorySanitizer (MSAN) support (Ben Noordhuis)
|
||||||
|
|
||||||
|
* doc: add uv_poll_cb status==UV_EBADF note (jensbjorgensen)
|
||||||
|
|
||||||
|
* build: support AddressSanitizer on MSVC (Jameson Nash)
|
||||||
|
|
||||||
|
* win,pipe: improve method of obtaining pid for ipc (number201724)
|
||||||
|
|
||||||
|
* thread: add support for affinity (daomingq)
|
||||||
|
|
||||||
|
* include: map ENODATA error code (Ben Noordhuis)
|
||||||
|
|
||||||
|
* build: remove bashism from autogen.sh (Santiago Gimeno)
|
||||||
|
|
||||||
|
* win,tcp,udp: remove "active streams" optimization (Saúl Ibarra Corretgé)
|
||||||
|
|
||||||
|
* win: drop code checking for Windows XP / Server 2k3 (Saúl Ibarra Corretgé)
|
||||||
|
|
||||||
|
* unix,win: fix 'sprintf' is deprecated warning (twosee)
|
||||||
|
|
||||||
|
* doc: mention close_cb can be NULL (Qix)
|
||||||
|
|
||||||
|
* win: optimize udp receive performance (ywave620)
|
||||||
|
|
||||||
|
* win: fix an incompatible types warning (twosee)
|
||||||
|
|
||||||
|
* doc: document 0 return value for free/total memory (Ben Noordhuis)
|
||||||
|
|
||||||
|
* darwin: use hw.cpufrequency again for frequency info (Jameson Nash)
|
||||||
|
|
||||||
|
* win,test: change format of TEST_PIPENAME's (Santiago Gimeno)
|
||||||
|
|
||||||
|
* win,pipe: fixes in uv_pipe_connect() (Santiago Gimeno)
|
||||||
|
|
||||||
|
* misc: fix return value of memory functions (theanarkh)
|
||||||
|
|
||||||
|
* src: add new metrics APIs (Trevor Norris)
|
||||||
|
|
||||||
|
* thread: add uv_thread_getcpu() (daomingq)
|
||||||
|
|
||||||
|
* build: don't use ifaddrs.h on solaris 10 (Edward Humes)
|
||||||
|
|
||||||
|
* unix,win: add uv_get_available_memory() (Tim Besard)
|
||||||
|
|
||||||
|
* test: fix -Wunused-but-set-variable warnings (Ben Noordhuis)
|
||||||
|
|
||||||
|
* doc: bump min supported linux and freebsd versions (Ben Noordhuis)
|
||||||
|
|
||||||
|
* Add Socket Runtime to the LINKS.md (Sergey Rubanov)
|
||||||
|
|
||||||
|
* unix: drop kfreebsd support (Ben Noordhuis)
|
||||||
|
|
||||||
|
* win: fix fstat for pipes and character files (Stefan Stojanovic)
|
||||||
|
|
||||||
|
* win: fix -Wunused-variable warning (Ben Noordhuis)
|
||||||
|
|
||||||
|
* win: fix -Wunused-function warning (Ben Noordhuis)
|
||||||
|
|
||||||
|
* build: drop qemu-alpha from ci matrix (Ben Noordhuis)
|
||||||
|
|
||||||
|
* win: move child_stdio_buffer out of uv_process_t (Santiago Gimeno)
|
||||||
|
|
||||||
|
* test: fix some unreachable code warnings (Santiago Gimeno)
|
||||||
|
|
||||||
|
* linux: simplify uv_uptime() (Ben Noordhuis)
|
||||||
|
|
||||||
|
* test: unflake fs_event_watch_dir test (Ben Noordhuis)
|
||||||
|
|
||||||
|
* darwin: remove unused fsevents symbol lookups (Ben Noordhuis)
|
||||||
|
|
||||||
|
* build: add define guard around UV_EXTERN (Zvicii)
|
||||||
|
|
||||||
|
* build: add UndefinedBehaviorSanitizer support (Ben Noordhuis)
|
||||||
|
|
||||||
|
* build: enable platform_output test on qemu (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: handle cpu hotplugging in uv_cpu_info() (Ben Noordhuis)
|
||||||
|
|
||||||
|
* build: remove unnecessary policy setting (dundargoc)
|
||||||
|
|
||||||
|
* docs: add vcpkg instruction step (Jack·Boos·Yu)
|
||||||
|
|
||||||
|
* win,fs: fix readlink errno for a non-symlink file (Darshan Sen)
|
||||||
|
|
||||||
|
* misc: extend getpw to take uid as an argument (Jameson Nash)
|
||||||
|
|
||||||
|
* unix,win: use static_assert when available (Ben Noordhuis)
|
||||||
|
|
||||||
|
* docs: delete code Makefile (Jameson Nash)
|
||||||
|
|
||||||
|
* docs: add CI for docs PRs (Jameson Nash)
|
||||||
|
|
||||||
|
* docs: update Sphinx version on RTD (Jameson Nash)
|
||||||
|
|
||||||
|
* doc: clean up license file (Ben Noordhuis)
|
||||||
|
|
||||||
|
* test: fix some warnings when compiling tests (panran)
|
||||||
|
|
||||||
|
* build,win: add mingw-w64 CI configuration (Jameson Nash)
|
||||||
|
|
||||||
|
* build: add CI for distcheck (Jameson Nash)
|
||||||
|
|
||||||
|
* unix: remove busy loop from uv_async_send (Jameson Nash)
|
||||||
|
|
||||||
|
* doc: document uv_fs_cb type (Tamás Bálint Misius)
|
||||||
|
|
||||||
|
* build: Improve build by cmake for Cygwin (erw7)
|
||||||
|
|
||||||
|
* build: add libuv:: namespace to libuvConfig.cmake (AJ Heller)
|
||||||
|
|
||||||
|
* test: fix ThreadSanitizer thread leak warning (Ben Noordhuis)
|
||||||
|
|
||||||
|
* test: fix ThreadSanitizer data race warning (Ben Noordhuis)
|
||||||
|
|
||||||
|
* test: fix ThreadSanitizer data race warning (Ben Noordhuis)
|
||||||
|
|
||||||
|
* test: fix ThreadSanitizer data race warning (Ben Noordhuis)
|
||||||
|
|
||||||
|
* test: cond-skip fork_threadpool_queue_work_simple (Ben Noordhuis)
|
||||||
|
|
||||||
|
* test: cond-skip signal_multiple_loops (Ben Noordhuis)
|
||||||
|
|
||||||
|
* test: cond-skip tcp_writealot (Ben Noordhuis)
|
||||||
|
|
||||||
|
* build: promote tsan ci to must-pass (Ben Noordhuis)
|
||||||
|
|
||||||
|
* build: add CI for OpenBSD and FreeBSD (James McCoy)
|
||||||
|
|
||||||
|
* build,test: fix distcheck errors (Jameson Nash)
|
||||||
|
|
||||||
|
* test: remove bad tty window size assumption (Ben Noordhuis)
|
||||||
|
|
||||||
|
* darwin,process: feed kevent the signal to reap children (Jameson Nash)
|
||||||
|
|
||||||
|
* unix: abort on clock_gettime() error (Ben Noordhuis)
|
||||||
|
|
||||||
|
* test: remove timing-sensitive check (Ben Noordhuis)
|
||||||
|
|
||||||
|
* unix: DRY and fix tcp bind error path (Jameson Nash)
|
||||||
|
|
||||||
|
* macos: fix fsevents thread race conditions (Ben Noordhuis)
|
||||||
|
|
||||||
|
* win: fix leak in uv_chdir (Trevor Norris)
|
||||||
|
|
||||||
|
* test: make valgrind happy (Trevor Norris)
|
||||||
|
|
||||||
|
* barrier: wait for prior out before next in (Jameson Nash)
|
||||||
|
|
||||||
|
* test: fix visual studio 2015 build error (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: fix ceph copy error truncating readonly files (Bruno Passeri)
|
||||||
|
|
||||||
|
* test: silence more valgrind warnings (Trevor Norris)
|
||||||
|
|
||||||
|
* doc: add entries to LINKS.md (Trevor Norris)
|
||||||
|
|
||||||
|
* win,unix: change execution order of timers (Trevor Norris)
|
||||||
|
|
||||||
|
* doc: add trevnorris to maintainers (Trevor Norris)
|
||||||
|
|
||||||
|
* linux: remove epoll_pwait() emulation code path (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: replace unsafe macro with inline function (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: remove arm oabi support (Ben Noordhuis)
|
||||||
|
|
||||||
|
* unix,sunos: SO_REUSEPORT not valid on all sockets (Stacey Marshall)
|
||||||
|
|
||||||
|
* doc: consistent single backquote in misc.rst (Jason Zhang)
|
||||||
|
|
||||||
|
* src: switch to use C11 atomics where available (Trevor Norris)
|
||||||
|
|
||||||
|
* test: don't use static buffer for formatting (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: introduce io_uring support (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: fix academic valgrind warning (Ben Noordhuis)
|
||||||
|
|
||||||
|
* test: disable signal test under ASan and MSan (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: add IORING_OP_OPENAT support (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: add IORING_OP_CLOSE support (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: remove bug workaround for obsolete kernels (Ben Noordhuis)
|
||||||
|
|
||||||
|
* doc: update active maintainers list (Ben Noordhuis)
|
||||||
|
|
||||||
|
* test: add ASSERT_OK (Trevor Norris)
|
||||||
|
|
||||||
|
* src: fix events/events_waiting metrics counter (Trevor Norris)
|
||||||
|
|
||||||
|
* unix,win: add uv_clock_gettime() (Ben Noordhuis)
|
||||||
|
|
||||||
|
* build: remove freebsd and openbsd buildbots (Ben Noordhuis)
|
||||||
|
|
||||||
|
* win: fix race condition in uv__init_console() (sivadeilra)
|
||||||
|
|
||||||
|
* linux: fix logic bug in sqe ring space check (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: use io_uring to batch epoll_ctl calls (Ben Noordhuis)
|
||||||
|
|
||||||
|
* macos: update minimum supported version (Santiago Gimeno)
|
||||||
|
|
||||||
|
* docs: fix some typos (cui fliter)
|
||||||
|
|
||||||
|
* unix: use memcpy() instead of type punning (Ben Noordhuis)
|
||||||
|
|
||||||
|
* test: add additional assert (Mohammed Keyvanzadeh)
|
||||||
|
|
||||||
|
* build: export compile_commands.json (Lewis Russell)
|
||||||
|
|
||||||
|
* win,process: write minidumps when sending SIGQUIT (Elliot Saba)
|
||||||
|
|
||||||
|
* unix: constrained_memory should return UINT64_MAX (Tim Besard)
|
||||||
|
|
||||||
|
* unix: handle CQ overflow in iou ring (Santiago Gimeno)
|
||||||
|
|
||||||
|
* unix: remove clang compiler warning pragmas (Ben Noordhuis)
|
||||||
|
|
||||||
|
* win: fix mingw build (gengjiawen)
|
||||||
|
|
||||||
|
* test: fix -Wbool-compare compiler warning (Ben Noordhuis)
|
||||||
|
|
||||||
|
* win: define MiniDumpWithAvxXStateContext always (Santiago Gimeno)
|
||||||
|
|
||||||
|
* freebsd: hard-code UV_ENODATA definition (Santiago Gimeno)
|
||||||
|
|
||||||
|
* linux: work around EOWNERDEAD io_uring kernel bug (Ben Noordhuis)
|
||||||
|
|
||||||
|
* linux: fix WRITEV with lots of bufs using io_uring (Santiago Gimeno)
|
||||||
|
|
||||||
|
|
||||||
|
2022.07.12, Version 1.44.2 (Stable), 0c1fa696aa502eb749c2c4735005f41ba00a27b8
|
||||||
|
|
||||||
Changes since version 1.44.1:
|
Changes since version 1.44.1:
|
||||||
|
|
||||||
|
47
deps/libuv/LICENSE
vendored
47
deps/libuv/LICENSE
vendored
@ -1,6 +1,3 @@
|
|||||||
libuv is licensed for use as follows:
|
|
||||||
|
|
||||||
====
|
|
||||||
Copyright (c) 2015-present libuv project contributors.
|
Copyright (c) 2015-present libuv project contributors.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
@ -20,47 +17,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
IN THE SOFTWARE.
|
IN THE SOFTWARE.
|
||||||
====
|
|
||||||
|
|
||||||
This license applies to parts of libuv originating from the
|
|
||||||
https://github.com/joyent/libuv repository:
|
|
||||||
|
|
||||||
====
|
|
||||||
|
|
||||||
Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to
|
|
||||||
deal in the Software without restriction, including without limitation the
|
|
||||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
IN THE SOFTWARE.
|
|
||||||
|
|
||||||
====
|
|
||||||
|
|
||||||
This license applies to all parts of libuv that are not externally
|
|
||||||
maintained libraries.
|
|
||||||
|
|
||||||
The externally maintained libraries used by libuv are:
|
|
||||||
|
|
||||||
- tree.h (from FreeBSD), copyright Niels Provos. Two clause BSD license.
|
|
||||||
|
|
||||||
- inet_pton and inet_ntop implementations, contained in src/inet.c, are
|
|
||||||
copyright the Internet Systems Consortium, Inc., and licensed under the ISC
|
|
||||||
license.
|
|
||||||
|
|
||||||
- stdint-msvc2008.h (from msinttypes), copyright Alexander Chemeris. Three
|
|
||||||
clause BSD license.
|
|
||||||
|
|
||||||
- pthread-fixes.c, copyright Google Inc. and Sony Mobile Communications AB.
|
|
||||||
Three clause BSD license.
|
|
||||||
|
36
deps/libuv/LICENSE-extra
vendored
Normal file
36
deps/libuv/LICENSE-extra
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
This license applies to parts of libuv originating from the
|
||||||
|
https://github.com/joyent/libuv repository:
|
||||||
|
|
||||||
|
====
|
||||||
|
|
||||||
|
Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to
|
||||||
|
deal in the Software without restriction, including without limitation the
|
||||||
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
IN THE SOFTWARE.
|
||||||
|
|
||||||
|
====
|
||||||
|
|
||||||
|
This license applies to all parts of libuv that are not externally
|
||||||
|
maintained libraries.
|
||||||
|
|
||||||
|
The externally maintained libraries used by libuv are:
|
||||||
|
|
||||||
|
- tree.h (from FreeBSD), copyright Niels Provos. Two clause BSD license.
|
||||||
|
|
||||||
|
- inet_pton and inet_ntop implementations, contained in src/inet.c, are
|
||||||
|
copyright the Internet Systems Consortium, Inc., and licensed under the ISC
|
||||||
|
license.
|
10
deps/libuv/LINKS.md
vendored
10
deps/libuv/LINKS.md
vendored
@ -1,16 +1,20 @@
|
|||||||
### Apps / VM
|
### Apps / VM
|
||||||
|
* [AliceO2](https://github.com/AliceO2Group/AliceO2): The framework and detector specific code for the reconstruction, calibration and simulation for the ALICE experiment at CERN.
|
||||||
|
* [Beam](https://github.com/BeamMW/beam): A scalable, confidential cryptocurrency based on the Mimblewimble protocol.
|
||||||
* [BIND 9](https://bind.isc.org/): DNS software system including an authoritative server, a recursive resolver and related utilities.
|
* [BIND 9](https://bind.isc.org/): DNS software system including an authoritative server, a recursive resolver and related utilities.
|
||||||
* [cjdns](https://github.com/cjdelisle/cjdns): Encrypted self-configuring network/VPN routing engine
|
* [cjdns](https://github.com/cjdelisle/cjdns): Encrypted self-configuring network/VPN routing engine
|
||||||
* [clearskies_core](https://github.com/larroy/clearskies_core): Clearskies file synchronization program. (C++11)
|
* [clearskies_core](https://github.com/larroy/clearskies_core): Clearskies file synchronization program. (C++11)
|
||||||
* [CMake](https://cmake.org) open-source, cross-platform family of tools designed to build, test and package software
|
* [CMake](https://cmake.org) open-source, cross-platform family of tools designed to build, test and package software
|
||||||
|
* [Cocos-Engine](https://github.com/cocos/cocos-engine): The runtime framework for Cocos Creator editor.
|
||||||
* [Coherence](https://github.com/liesware/coherence/): Cryptographic server for modern web apps.
|
* [Coherence](https://github.com/liesware/coherence/): Cryptographic server for modern web apps.
|
||||||
|
* [DPS8M](https://dps8m.gitlab.io): GE ∕ Honeywell ∕ Bull DPS‑8/M and 6180/L68 mainframe simulator.
|
||||||
* [DPS-For-IoT](https://github.com/intel/dps-for-iot/wiki): Fully distributed publish/subscribe protocol.
|
* [DPS-For-IoT](https://github.com/intel/dps-for-iot/wiki): Fully distributed publish/subscribe protocol.
|
||||||
* [HashLink](https://github.com/HaxeFoundation/hashlink): Haxe run-time with libuv support included.
|
* [HashLink](https://github.com/HaxeFoundation/hashlink): Haxe run-time with libuv support included.
|
||||||
* [Haywire](https://github.com/kellabyte/Haywire): Asynchronous HTTP server.
|
* [Haywire](https://github.com/kellabyte/Haywire): Asynchronous HTTP server.
|
||||||
* [H2O](https://github.com/h2o/h2o): An optimized HTTP server with support for HTTP/1.x and HTTP/2.
|
* [H2O](https://github.com/h2o/h2o): An optimized HTTP server with support for HTTP/1.x and HTTP/2.
|
||||||
* [Igropyr](https://github.com/guenchi/Igropyr): a async Scheme http server base on libuv.
|
* [Igropyr](https://github.com/guenchi/Igropyr): a async Scheme http server base on libuv.
|
||||||
* [Julia](http://julialang.org/): Scientific computing programming language
|
* [Julia](http://julialang.org/): Scientific computing programming language
|
||||||
* [Kestrel](https://github.com/aspnet/AspNetCore/tree/master/src/Servers/Kestrel): web server (C# + libuv + [ASP.NET Core](http://github.com/aspnet))
|
* [Kestrel](https://github.com/dotnet/aspnetcore/tree/main/src/Servers/Kestrel): web server (C# + libuv + [ASP.NET Core](http://github.com/aspnet))
|
||||||
* [Knot DNS Resolver](https://www.knot-resolver.cz/): A minimalistic DNS caching resolver
|
* [Knot DNS Resolver](https://www.knot-resolver.cz/): A minimalistic DNS caching resolver
|
||||||
* [Lever](http://leverlanguage.com): runtime, libuv at the 0.9.0 release
|
* [Lever](http://leverlanguage.com): runtime, libuv at the 0.9.0 release
|
||||||
* [libnode](https://github.com/plenluno/libnode): C++ implementation of Node.js
|
* [libnode](https://github.com/plenluno/libnode): C++ implementation of Node.js
|
||||||
@ -30,8 +34,10 @@
|
|||||||
* [phastlight](https://github.com/phastlight/phastlight): Command line tool and web server written in PHP 5.3+ inspired by Node.js
|
* [phastlight](https://github.com/phastlight/phastlight): Command line tool and web server written in PHP 5.3+ inspired by Node.js
|
||||||
* [pilight](https://www.pilight.org/): home automation ("domotica")
|
* [pilight](https://www.pilight.org/): home automation ("domotica")
|
||||||
* [pixie](https://github.com/pixie-lang/pixie): clojure-inspired lisp with a tracing JIT
|
* [pixie](https://github.com/pixie-lang/pixie): clojure-inspired lisp with a tracing JIT
|
||||||
|
* [Pixie-io](https://github.com/pixie-io/pixie): Open-source observability tool for Kubernetes applications.
|
||||||
* [potion](https://github.com/perl11/potion)/[p2](https://github.com/perl11/p2): runtime
|
* [potion](https://github.com/perl11/potion)/[p2](https://github.com/perl11/p2): runtime
|
||||||
* [racer](https://libraries.io/rubygems/racer): Ruby web server written as an C extension
|
* [racer](https://libraries.io/rubygems/racer): Ruby web server written as an C extension
|
||||||
|
* [Socket Runtime](https://sockets.sh): A runtime for creating native cross-platform software on mobile and desktop using HTML, CSS, and JavaScript
|
||||||
* [spider-gazelle](https://github.com/cotag/spider-gazelle): Ruby web server using libuv bindings
|
* [spider-gazelle](https://github.com/cotag/spider-gazelle): Ruby web server using libuv bindings
|
||||||
* [Suave](http://suave.io/): A simple web development F# library providing a lightweight web server and a set of combinators to manipulate route flow and task composition
|
* [Suave](http://suave.io/): A simple web development F# library providing a lightweight web server and a set of combinators to manipulate route flow and task composition
|
||||||
* [Swish](https://github.com/becls/swish/): Concurrency engine with Erlang-like concepts. Includes a web server.
|
* [Swish](https://github.com/becls/swish/): Concurrency engine with Erlang-like concepts. Includes a web server.
|
||||||
@ -39,6 +45,7 @@
|
|||||||
* [Urbit](http://urbit.org): runtime
|
* [Urbit](http://urbit.org): runtime
|
||||||
* [uv_callback](https://github.com/litesync/uv_callback) libuv thread communication
|
* [uv_callback](https://github.com/litesync/uv_callback) libuv thread communication
|
||||||
* [uvloop](https://github.com/MagicStack/uvloop): Ultra fast implementation of python's asyncio event loop on top of libuv
|
* [uvloop](https://github.com/MagicStack/uvloop): Ultra fast implementation of python's asyncio event loop on top of libuv
|
||||||
|
* [WPILib](https://github.com/wpilibsuite/allwpilib): Libraries for creating robot programs for the roboRIO.
|
||||||
* [Wren CLI](https://github.com/wren-lang/wren-cli): For io, process, scheduler and timer modules
|
* [Wren CLI](https://github.com/wren-lang/wren-cli): For io, process, scheduler and timer modules
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
@ -59,6 +66,7 @@
|
|||||||
* [lluv](https://github.com/moteus/lua-lluv)
|
* [lluv](https://github.com/moteus/lua-lluv)
|
||||||
* C++11
|
* C++11
|
||||||
* [uvpp](https://github.com/larroy/uvpp) - Not complete, exposes very few aspects of `libuv`
|
* [uvpp](https://github.com/larroy/uvpp) - Not complete, exposes very few aspects of `libuv`
|
||||||
|
* [nsuv](https://github.com/nodesource/nsuv) - Template wrapper focused on enforcing compile-time type safety when propagating data
|
||||||
* C++17
|
* C++17
|
||||||
* [uvw](https://github.com/skypjack/uvw) - Header-only, event based, tiny and easy to use *libuv* wrapper in modern C++.
|
* [uvw](https://github.com/skypjack/uvw) - Header-only, event based, tiny and easy to use *libuv* wrapper in modern C++.
|
||||||
* Python
|
* Python
|
||||||
|
8
deps/libuv/MAINTAINERS.md
vendored
8
deps/libuv/MAINTAINERS.md
vendored
@ -4,12 +4,9 @@ libuv is currently managed by the following individuals:
|
|||||||
|
|
||||||
* **Ben Noordhuis** ([@bnoordhuis](https://github.com/bnoordhuis))
|
* **Ben Noordhuis** ([@bnoordhuis](https://github.com/bnoordhuis))
|
||||||
- GPG key: D77B 1E34 243F BAF0 5F8E 9CC3 4F55 C8C8 46AB 89B9 (pubkey-bnoordhuis)
|
- GPG key: D77B 1E34 243F BAF0 5F8E 9CC3 4F55 C8C8 46AB 89B9 (pubkey-bnoordhuis)
|
||||||
* **Bert Belder** ([@piscisaureus](https://github.com/piscisaureus))
|
|
||||||
* **Colin Ihrig** ([@cjihrig](https://github.com/cjihrig))
|
* **Colin Ihrig** ([@cjihrig](https://github.com/cjihrig))
|
||||||
- GPG key: 94AE 3667 5C46 4D64 BAFA 68DD 7434 390B DBE9 B9C5 (pubkey-cjihrig)
|
- GPG key: 94AE 3667 5C46 4D64 BAFA 68DD 7434 390B DBE9 B9C5 (pubkey-cjihrig)
|
||||||
- GPG key: 5735 3E0D BDAA A7E8 39B6 6A1A FF47 D5E4 AD8B 4FDC (pubkey-cjihrig-kb)
|
- GPG key: 5735 3E0D BDAA A7E8 39B6 6A1A FF47 D5E4 AD8B 4FDC (pubkey-cjihrig-kb)
|
||||||
* **Fedor Indutny** ([@indutny](https://github.com/indutny))
|
|
||||||
- GPG key: AF2E EA41 EC34 47BF DD86 FED9 D706 3CCE 19B7 E890 (pubkey-indutny)
|
|
||||||
* **Jameson Nash** ([@vtjnash](https://github.com/vtjnash))
|
* **Jameson Nash** ([@vtjnash](https://github.com/vtjnash))
|
||||||
- GPG key: AEAD 0A4B 6867 6775 1A0E 4AEF 34A2 5FB1 2824 6514 (pubkey-vtjnash)
|
- GPG key: AEAD 0A4B 6867 6775 1A0E 4AEF 34A2 5FB1 2824 6514 (pubkey-vtjnash)
|
||||||
- GPG key: CFBB 9CA9 A5BE AFD7 0E2B 3C5A 79A6 7C55 A367 9C8B (pubkey2022-vtjnash)
|
- GPG key: CFBB 9CA9 A5BE AFD7 0E2B 3C5A 79A6 7C55 A367 9C8B (pubkey2022-vtjnash)
|
||||||
@ -22,11 +19,16 @@ libuv is currently managed by the following individuals:
|
|||||||
- GPG key: 612F 0EAD 9401 6223 79DF 4402 F28C 3C8D A33C 03BE (pubkey-santigimeno)
|
- GPG key: 612F 0EAD 9401 6223 79DF 4402 F28C 3C8D A33C 03BE (pubkey-santigimeno)
|
||||||
* **Saúl Ibarra Corretgé** ([@saghul](https://github.com/saghul))
|
* **Saúl Ibarra Corretgé** ([@saghul](https://github.com/saghul))
|
||||||
- GPG key: FDF5 1936 4458 319F A823 3DC9 410E 5553 AE9B C059 (pubkey-saghul)
|
- GPG key: FDF5 1936 4458 319F A823 3DC9 410E 5553 AE9B C059 (pubkey-saghul)
|
||||||
|
* **Trevor Norris** ([@trevnorris](https://github.com/trevnorris))
|
||||||
|
- GPG key: AEFC 279A 0C93 0676 7E58 29A1 251C A676 820D C7F3 (pubkey-trevnorris)
|
||||||
|
|
||||||
## Project Maintainers emeriti
|
## Project Maintainers emeriti
|
||||||
|
|
||||||
* **Anna Henningsen** ([@addaleax](https://github.com/addaleax))
|
* **Anna Henningsen** ([@addaleax](https://github.com/addaleax))
|
||||||
* **Bartosz Sosnowski** ([@bzoz](https://github.com/bzoz))
|
* **Bartosz Sosnowski** ([@bzoz](https://github.com/bzoz))
|
||||||
|
* **Bert Belder** ([@piscisaureus](https://github.com/piscisaureus))
|
||||||
|
* **Fedor Indutny** ([@indutny](https://github.com/indutny))
|
||||||
|
- GPG key: AF2E EA41 EC34 47BF DD86 FED9 D706 3CCE 19B7 E890 (pubkey-indutny)
|
||||||
* **Imran Iqbal** ([@imran-iq](https://github.com/imran-iq))
|
* **Imran Iqbal** ([@imran-iq](https://github.com/imran-iq))
|
||||||
* **John Barboza** ([@jbarz](https://github.com/jbarz))
|
* **John Barboza** ([@jbarz](https://github.com/jbarz))
|
||||||
|
|
||||||
|
24
deps/libuv/Makefile.am
vendored
24
deps/libuv/Makefile.am
vendored
@ -38,6 +38,7 @@ libuv_la_SOURCES = src/fs-poll.c \
|
|||||||
src/random.c \
|
src/random.c \
|
||||||
src/strscpy.c \
|
src/strscpy.c \
|
||||||
src/strscpy.h \
|
src/strscpy.h \
|
||||||
|
src/thread-common.c \
|
||||||
src/threadpool.c \
|
src/threadpool.c \
|
||||||
src/timer.c \
|
src/timer.c \
|
||||||
src/uv-data-getter-setters.c \
|
src/uv-data-getter-setters.c \
|
||||||
@ -96,7 +97,6 @@ else # WINNT
|
|||||||
uvinclude_HEADERS += include/uv/unix.h
|
uvinclude_HEADERS += include/uv/unix.h
|
||||||
AM_CPPFLAGS += -I$(top_srcdir)/src/unix
|
AM_CPPFLAGS += -I$(top_srcdir)/src/unix
|
||||||
libuv_la_SOURCES += src/unix/async.c \
|
libuv_la_SOURCES += src/unix/async.c \
|
||||||
src/unix/atomic-ops.h \
|
|
||||||
src/unix/core.c \
|
src/unix/core.c \
|
||||||
src/unix/dl.c \
|
src/unix/dl.c \
|
||||||
src/unix/fs.c \
|
src/unix/fs.c \
|
||||||
@ -110,7 +110,6 @@ libuv_la_SOURCES += src/unix/async.c \
|
|||||||
src/unix/process.c \
|
src/unix/process.c \
|
||||||
src/unix/random-devurandom.c \
|
src/unix/random-devurandom.c \
|
||||||
src/unix/signal.c \
|
src/unix/signal.c \
|
||||||
src/unix/spinlock.h \
|
|
||||||
src/unix/stream.c \
|
src/unix/stream.c \
|
||||||
src/unix/tcp.c \
|
src/unix/tcp.c \
|
||||||
src/unix/thread.c \
|
src/unix/thread.c \
|
||||||
@ -122,11 +121,13 @@ endif # WINNT
|
|||||||
EXTRA_DIST = test/fixtures/empty_file \
|
EXTRA_DIST = test/fixtures/empty_file \
|
||||||
test/fixtures/load_error.node \
|
test/fixtures/load_error.node \
|
||||||
test/fixtures/lorem_ipsum.txt \
|
test/fixtures/lorem_ipsum.txt \
|
||||||
|
test/fixtures/one_file/one_file \
|
||||||
include \
|
include \
|
||||||
docs \
|
docs \
|
||||||
img \
|
img \
|
||||||
CONTRIBUTING.md \
|
CONTRIBUTING.md \
|
||||||
LICENSE \
|
LICENSE \
|
||||||
|
LICENSE-extra \
|
||||||
README.md
|
README.md
|
||||||
|
|
||||||
|
|
||||||
@ -278,11 +279,13 @@ test_run_tests_SOURCES = test/blackhole-server.c \
|
|||||||
test/test-tcp-writealot.c \
|
test/test-tcp-writealot.c \
|
||||||
test/test-tcp-write-fail.c \
|
test/test-tcp-write-fail.c \
|
||||||
test/test-tcp-try-write.c \
|
test/test-tcp-try-write.c \
|
||||||
|
test/test-tcp-write-in-a-row.c \
|
||||||
test/test-tcp-try-write-error.c \
|
test/test-tcp-try-write-error.c \
|
||||||
test/test-tcp-write-queue-order.c \
|
test/test-tcp-write-queue-order.c \
|
||||||
test/test-test-macros.c \
|
test/test-test-macros.c \
|
||||||
test/test-thread-equal.c \
|
test/test-thread-equal.c \
|
||||||
test/test-thread.c \
|
test/test-thread.c \
|
||||||
|
test/test-thread-affinity.c \
|
||||||
test/test-threadpool-cancel.c \
|
test/test-threadpool-cancel.c \
|
||||||
test/test-threadpool.c \
|
test/test-threadpool.c \
|
||||||
test/test-timer-again.c \
|
test/test-timer-again.c \
|
||||||
@ -313,6 +316,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \
|
|||||||
test/test-udp-sendmmsg-error.c \
|
test/test-udp-sendmmsg-error.c \
|
||||||
test/test-udp-send-unreachable.c \
|
test/test-udp-send-unreachable.c \
|
||||||
test/test-udp-try-send.c \
|
test/test-udp-try-send.c \
|
||||||
|
test/test-udp-recv-in-a-row.c \
|
||||||
test/test-uname.c \
|
test/test-uname.c \
|
||||||
test/test-walk-handles.c \
|
test/test-walk-handles.c \
|
||||||
test/test-watcher-cross-stop.c
|
test/test-watcher-cross-stop.c
|
||||||
@ -393,7 +397,6 @@ endif
|
|||||||
|
|
||||||
if ANDROID
|
if ANDROID
|
||||||
libuv_la_CFLAGS += -D_GNU_SOURCE
|
libuv_la_CFLAGS += -D_GNU_SOURCE
|
||||||
libuv_la_SOURCES += src/unix/pthread-fixes.c
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if CYGWIN
|
if CYGWIN
|
||||||
@ -467,22 +470,14 @@ libuv_la_SOURCES += src/unix/bsd-ifaddrs.c \
|
|||||||
src/unix/hurd.c
|
src/unix/hurd.c
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if KFREEBSD
|
|
||||||
libuv_la_CFLAGS += -D_GNU_SOURCE
|
|
||||||
endif
|
|
||||||
|
|
||||||
if LINUX
|
if LINUX
|
||||||
uvinclude_HEADERS += include/uv/linux.h
|
uvinclude_HEADERS += include/uv/linux.h
|
||||||
libuv_la_CFLAGS += -D_GNU_SOURCE
|
libuv_la_CFLAGS += -D_GNU_SOURCE
|
||||||
libuv_la_SOURCES += src/unix/linux-core.c \
|
libuv_la_SOURCES += src/unix/linux.c \
|
||||||
src/unix/linux-inotify.c \
|
|
||||||
src/unix/linux-syscalls.c \
|
|
||||||
src/unix/linux-syscalls.h \
|
|
||||||
src/unix/procfs-exepath.c \
|
src/unix/procfs-exepath.c \
|
||||||
src/unix/proctitle.c \
|
src/unix/proctitle.c \
|
||||||
src/unix/random-getrandom.c \
|
src/unix/random-getrandom.c \
|
||||||
src/unix/random-sysctl-linux.c \
|
src/unix/random-sysctl-linux.c
|
||||||
src/unix/epoll.c
|
|
||||||
test_run_tests_LDFLAGS += -lutil
|
test_run_tests_LDFLAGS += -lutil
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@ -546,8 +541,7 @@ libuv_la_CFLAGS += -D_UNIX03_THREADS \
|
|||||||
-qXPLINK \
|
-qXPLINK \
|
||||||
-qFLOAT=IEEE
|
-qFLOAT=IEEE
|
||||||
libuv_la_LDFLAGS += -qXPLINK
|
libuv_la_LDFLAGS += -qXPLINK
|
||||||
libuv_la_SOURCES += src/unix/pthread-fixes.c \
|
libuv_la_SOURCES += src/unix/os390.c \
|
||||||
src/unix/os390.c \
|
|
||||||
src/unix/os390-syscalls.c \
|
src/unix/os390-syscalls.c \
|
||||||
src/unix/proctitle.c
|
src/unix/proctitle.c
|
||||||
endif
|
endif
|
||||||
|
16
deps/libuv/README.md
vendored
16
deps/libuv/README.md
vendored
@ -43,8 +43,11 @@ The ABI/API changes can be tracked [here](http://abi-laboratory.pro/tracker/time
|
|||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|
||||||
libuv is licensed under the MIT license. Check the [LICENSE file](LICENSE).
|
libuv is licensed under the MIT license. Check the [LICENSE](LICENSE) and
|
||||||
The documentation is licensed under the CC BY 4.0 license. Check the [LICENSE-docs file](LICENSE-docs).
|
[LICENSE-extra](LICENSE-extra) files.
|
||||||
|
|
||||||
|
The documentation is licensed under the CC BY 4.0 license. Check the
|
||||||
|
[LICENSE-docs file](LICENSE-docs).
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
@ -220,6 +223,15 @@ Make sure that you specify the architecture you wish to build for in the
|
|||||||
"ARCHS" flag. You can specify more than one by delimiting with a space
|
"ARCHS" flag. You can specify more than one by delimiting with a space
|
||||||
(e.g. "x86_64 i386").
|
(e.g. "x86_64 i386").
|
||||||
|
|
||||||
|
### Install with vcpkg
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git clone https://github.com/microsoft/vcpkg.git
|
||||||
|
$ ./bootstrap-vcpkg.bat # for powershell
|
||||||
|
$ ./bootstrap-vcpkg.sh # for bash
|
||||||
|
$ ./vcpkg install libuv
|
||||||
|
```
|
||||||
|
|
||||||
### Running tests
|
### Running tests
|
||||||
|
|
||||||
Some tests are timing sensitive. Relaxing test timeouts may be necessary
|
Some tests are timing sensitive. Relaxing test timeouts may be necessary
|
||||||
|
6
deps/libuv/SUPPORTED_PLATFORMS.md
vendored
6
deps/libuv/SUPPORTED_PLATFORMS.md
vendored
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
| System | Support type | Supported versions | Notes |
|
| System | Support type | Supported versions | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| GNU/Linux | Tier 1 | Linux >= 2.6.32 with glibc >= 2.12 | |
|
| GNU/Linux | Tier 1 | Linux >= 3.10 with glibc >= 2.17 | |
|
||||||
| macOS | Tier 1 | macOS >= 10.15 | Current and previous macOS release |
|
| macOS | Tier 1 | macOS >= 11 | Currently supported macOS releases |
|
||||||
| Windows | Tier 1 | >= Windows 8 | VS 2015 and later are supported |
|
| Windows | Tier 1 | >= Windows 8 | VS 2015 and later are supported |
|
||||||
| FreeBSD | Tier 1 | >= 10 | |
|
| FreeBSD | Tier 2 | >= 12 | |
|
||||||
| AIX | Tier 2 | >= 6 | Maintainers: @libuv/aix |
|
| AIX | Tier 2 | >= 6 | Maintainers: @libuv/aix |
|
||||||
| IBM i | Tier 2 | >= IBM i 7.2 | Maintainers: @libuv/ibmi |
|
| IBM i | Tier 2 | >= IBM i 7.2 | Maintainers: @libuv/ibmi |
|
||||||
| z/OS | Tier 2 | >= V2R2 | Maintainers: @libuv/zos |
|
| z/OS | Tier 2 | >= V2R2 | Maintainers: @libuv/zos |
|
||||||
|
2
deps/libuv/autogen.sh
vendored
2
deps/libuv/autogen.sh
vendored
@ -17,7 +17,7 @@
|
|||||||
set -eu
|
set -eu
|
||||||
cd `dirname "$0"`
|
cd `dirname "$0"`
|
||||||
|
|
||||||
if [ "${1:-dev}" == "release" ]; then
|
if [ "${1:-dev}" = "release" ]; then
|
||||||
export LIBUV_RELEASE=true
|
export LIBUV_RELEASE=true
|
||||||
else
|
else
|
||||||
export LIBUV_RELEASE=false
|
export LIBUV_RELEASE=false
|
||||||
|
17
deps/libuv/cmake-toolchains/cross-mingw32.cmake
vendored
Normal file
17
deps/libuv/cmake-toolchains/cross-mingw32.cmake
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
if(NOT HOST_ARCH)
|
||||||
|
message(SEND_ERROR "-DHOST_ARCH required to be specified")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
|
||||||
|
HOST_ARCH
|
||||||
|
)
|
||||||
|
|
||||||
|
SET(CMAKE_SYSTEM_NAME Windows)
|
||||||
|
set(COMPILER_PREFIX "${HOST_ARCH}-w64-mingw32")
|
||||||
|
find_program(CMAKE_RC_COMPILER NAMES ${COMPILER_PREFIX}-windres)
|
||||||
|
find_program(CMAKE_C_COMPILER NAMES ${COMPILER_PREFIX}-gcc)
|
||||||
|
find_program(CMAKE_CXX_COMPILER NAMES ${COMPILER_PREFIX}-g++)
|
||||||
|
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
15
deps/libuv/configure.ac
vendored
15
deps/libuv/configure.ac
vendored
@ -13,12 +13,13 @@
|
|||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
AC_PREREQ(2.57)
|
AC_PREREQ(2.57)
|
||||||
AC_INIT([libuv], [1.44.2], [https://github.com/libuv/libuv/issues])
|
AC_INIT([libuv], [1.46.0], [https://github.com/libuv/libuv/issues])
|
||||||
AC_CONFIG_MACRO_DIR([m4])
|
AC_CONFIG_MACRO_DIR([m4])
|
||||||
m4_include([m4/libuv-extra-automake-flags.m4])
|
m4_include([m4/libuv-extra-automake-flags.m4])
|
||||||
m4_include([m4/as_case.m4])
|
m4_include([m4/as_case.m4])
|
||||||
m4_include([m4/libuv-check-flags.m4])
|
m4_include([m4/libuv-check-flags.m4])
|
||||||
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects] UV_EXTRA_AUTOMAKE_FLAGS)
|
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects] UV_EXTRA_AUTOMAKE_FLAGS)
|
||||||
|
AM_MAINTAINER_MODE([enable]) # pass --disable-maintainer-mode if autotools may be unavailable
|
||||||
AC_CANONICAL_HOST
|
AC_CANONICAL_HOST
|
||||||
AC_ENABLE_SHARED
|
AC_ENABLE_SHARED
|
||||||
AC_ENABLE_STATIC
|
AC_ENABLE_STATIC
|
||||||
@ -61,8 +62,7 @@ AM_CONDITIONAL([ANDROID], [AS_CASE([$host_os],[linux-android*],[true], [false])
|
|||||||
AM_CONDITIONAL([CYGWIN], [AS_CASE([$host_os],[cygwin*], [true], [false])])
|
AM_CONDITIONAL([CYGWIN], [AS_CASE([$host_os],[cygwin*], [true], [false])])
|
||||||
AM_CONDITIONAL([DARWIN], [AS_CASE([$host_os],[darwin*], [true], [false])])
|
AM_CONDITIONAL([DARWIN], [AS_CASE([$host_os],[darwin*], [true], [false])])
|
||||||
AM_CONDITIONAL([DRAGONFLY],[AS_CASE([$host_os],[dragonfly*], [true], [false])])
|
AM_CONDITIONAL([DRAGONFLY],[AS_CASE([$host_os],[dragonfly*], [true], [false])])
|
||||||
AM_CONDITIONAL([FREEBSD], [AS_CASE([$host_os],[*freebsd*], [true], [false])])
|
AM_CONDITIONAL([FREEBSD], [AS_CASE([$host_os],[freebsd*], [true], [false])])
|
||||||
AM_CONDITIONAL([KFREEBSD], [AS_CASE([$host_os],[kfreebsd*], [true], [false])])
|
|
||||||
AM_CONDITIONAL([HAIKU], [AS_CASE([$host_os],[haiku], [true], [false])])
|
AM_CONDITIONAL([HAIKU], [AS_CASE([$host_os],[haiku], [true], [false])])
|
||||||
AM_CONDITIONAL([HURD], [AS_CASE([$host_os],[gnu*], [true], [false])])
|
AM_CONDITIONAL([HURD], [AS_CASE([$host_os],[gnu*], [true], [false])])
|
||||||
AM_CONDITIONAL([LINUX], [AS_CASE([$host_os],[linux*], [true], [false])])
|
AM_CONDITIONAL([LINUX], [AS_CASE([$host_os],[linux*], [true], [false])])
|
||||||
@ -74,12 +74,12 @@ AM_CONDITIONAL([OS400], [AS_CASE([$host_os],[os400], [true], [false])
|
|||||||
AM_CONDITIONAL([SUNOS], [AS_CASE([$host_os],[solaris*], [true], [false])])
|
AM_CONDITIONAL([SUNOS], [AS_CASE([$host_os],[solaris*], [true], [false])])
|
||||||
AM_CONDITIONAL([WINNT], [AS_CASE([$host_os],[mingw*], [true], [false])])
|
AM_CONDITIONAL([WINNT], [AS_CASE([$host_os],[mingw*], [true], [false])])
|
||||||
AS_CASE([$host_os],[mingw*], [
|
AS_CASE([$host_os],[mingw*], [
|
||||||
LIBS="$LIBS -lws2_32 -lpsapi -liphlpapi -lshell32 -luserenv -luser32"
|
LIBS="$LIBS -lws2_32 -lpsapi -liphlpapi -lshell32 -luserenv -luser32 -ldbghelp -lole32 -luuid"
|
||||||
|
])
|
||||||
|
AS_CASE([$host_os], [solaris2.10], [
|
||||||
|
CFLAGS="$CFLAGS -DSUNOS_NO_IFADDRS"
|
||||||
])
|
])
|
||||||
AS_CASE([$host_os], [netbsd*], [AC_CHECK_LIB([kvm], [kvm_open])])
|
AS_CASE([$host_os], [netbsd*], [AC_CHECK_LIB([kvm], [kvm_open])])
|
||||||
AS_CASE([$host_os], [kfreebsd*], [
|
|
||||||
LIBS="$LIBS -lfreebsd-glue"
|
|
||||||
])
|
|
||||||
AS_CASE([$host_os], [haiku], [
|
AS_CASE([$host_os], [haiku], [
|
||||||
LIBS="$LIBS -lnetwork"
|
LIBS="$LIBS -lnetwork"
|
||||||
])
|
])
|
||||||
@ -88,4 +88,5 @@ AC_CONFIG_FILES([Makefile libuv.pc])
|
|||||||
AC_CONFIG_LINKS([test/fixtures/empty_file:test/fixtures/empty_file])
|
AC_CONFIG_LINKS([test/fixtures/empty_file:test/fixtures/empty_file])
|
||||||
AC_CONFIG_LINKS([test/fixtures/load_error.node:test/fixtures/load_error.node])
|
AC_CONFIG_LINKS([test/fixtures/load_error.node:test/fixtures/load_error.node])
|
||||||
AC_CONFIG_LINKS([test/fixtures/lorem_ipsum.txt:test/fixtures/lorem_ipsum.txt])
|
AC_CONFIG_LINKS([test/fixtures/lorem_ipsum.txt:test/fixtures/lorem_ipsum.txt])
|
||||||
|
AC_CONFIG_LINKS([test/fixtures/one_file/one_file:test/fixtures/one_file/one_file])
|
||||||
AC_OUTPUT
|
AC_OUTPUT
|
||||||
|
82
deps/libuv/docs/code/Makefile
vendored
82
deps/libuv/docs/code/Makefile
vendored
@ -1,82 +0,0 @@
|
|||||||
examples=\
|
|
||||||
helloworld\
|
|
||||||
default-loop\
|
|
||||||
idle-basic\
|
|
||||||
uvcat\
|
|
||||||
uvtee\
|
|
||||||
onchange\
|
|
||||||
thread-create\
|
|
||||||
queue-work\
|
|
||||||
progress\
|
|
||||||
tcp-echo-server\
|
|
||||||
dns\
|
|
||||||
udp-dhcp\
|
|
||||||
idle-compute\
|
|
||||||
ref-timer\
|
|
||||||
spawn\
|
|
||||||
detach\
|
|
||||||
proc-streams\
|
|
||||||
cgi\
|
|
||||||
pipe-echo-server\
|
|
||||||
multi-echo-server\
|
|
||||||
tty\
|
|
||||||
tty-gravity\
|
|
||||||
interfaces\
|
|
||||||
locks \
|
|
||||||
signal \
|
|
||||||
uvstop \
|
|
||||||
queue-cancel
|
|
||||||
|
|
||||||
UV_PATH=$(shell pwd)/../..
|
|
||||||
UV_LIB=$(UV_PATH)/.libs/libuv.a
|
|
||||||
CFLAGS=-g -Wall -I$(UV_PATH)/include
|
|
||||||
LIBS=
|
|
||||||
|
|
||||||
uname_S=$(shell uname -s)
|
|
||||||
|
|
||||||
ifeq (Darwin, $(uname_S))
|
|
||||||
CFLAGS+=-framework CoreServices
|
|
||||||
SHARED_LIB_FLAGS=-bundle -undefined dynamic_lookup -o plugin/libhello.dylib
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifeq (Linux, $(uname_S))
|
|
||||||
LIBS=-lrt -ldl -lm -pthread -lcurl
|
|
||||||
SHARED_LIB_FLAGS=-shared -Wl,-soname,libhello.so -o plugin/libhello.so
|
|
||||||
PLUGIN_EXE_FLAGS=-Wl,-export-dynamic
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
||||||
all: $(examples) plugin/plugin proc-streams/test cgi/tick multi-echo-server/worker uvwget/uvwget
|
|
||||||
|
|
||||||
$(examples): % : %/main.c
|
|
||||||
gcc $(CFLAGS) -o $@/$@ $< $(UV_LIB) $(LIBS)
|
|
||||||
|
|
||||||
plugin: plugin/plugin
|
|
||||||
plugin/plugin: plugin/*.c
|
|
||||||
gcc $(CFLAGS) $(PLUGIN_EXE_FLAGS) -o plugin/plugin plugin/main.c $(UV_LIB) $(LIBS)
|
|
||||||
gcc -g -Wall -c -fPIC -o plugin/hello.o plugin/hello.c
|
|
||||||
gcc $(SHARED_LIB_FLAGS) plugin/hello.o
|
|
||||||
|
|
||||||
proc-streams/test: proc-streams/test.c
|
|
||||||
gcc -g -Wall -o proc-streams/test proc-streams/test.c
|
|
||||||
|
|
||||||
cgi/tick: cgi/tick.c
|
|
||||||
gcc -g -Wall -o cgi/tick cgi/tick.c
|
|
||||||
|
|
||||||
multi-echo-server/worker: multi-echo-server/worker.c
|
|
||||||
gcc $(CFLAGS) -o multi-echo-server/worker multi-echo-server/worker.c $(UV_LIB) $(LIBS)
|
|
||||||
|
|
||||||
uvwget: uvwget/uvwget
|
|
||||||
uvwget/uvwget: uvwget/main.c
|
|
||||||
gcc $(CFLAGS) `curl-config --cflags --libs` -o uvwget/uvwget uvwget/main.c $(UV_LIB) $(LIBS)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
for dir in $(examples); do cd $$dir; rm -f $$dir; rm -rf $$dir.dSYM; cd ..; done
|
|
||||||
rm -rf plugin/*.o plugin/libhello.*
|
|
||||||
rm -rf plugin/plugin plugin/plugin.dSYM
|
|
||||||
rm -rf proc-streams/test proc-streams/test.dSYM
|
|
||||||
rm -rf cgi/tick cgi/tick.dSYM
|
|
||||||
rm -rf multi-echo-server/worker multi-echo-server/worker.dSYM
|
|
||||||
rm -rf uvwget/uvwget uvwget/uvwget.dSYM
|
|
||||||
|
|
||||||
.PHONY: clean all $(examples) plugin uvwget
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user