forked from cory/tildefriends
		
	Compare commits
	
		
			21 Commits
		
	
	
		
			fae2771645
			...
			submodules
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e00f73e1d5 | |||
| 4c11667ebd | |||
| 658e7089be | |||
| 0965e90d7b | |||
| d1f87a8fb4 | |||
| 2b4265f9ee | |||
| 3bd827a9f7 | |||
| 474e39c9c3 | |||
| 0272382e0e | |||
| b1c8b51377 | |||
| 1a5acca5cf | |||
| 2d5417f7dc | |||
| 2a10d26215 | |||
| b8e5caba0d | |||
| a4b324127a | |||
| acae3e9562 | |||
| 4aa7424977 | |||
| 758f177617 | |||
| 9291de41d8 | |||
| 3603ce5ba6 | |||
| bff231751e | 
@@ -1,5 +1,4 @@
 | 
			
		||||
.svn
 | 
			
		||||
db.*
 | 
			
		||||
db.sqlite
 | 
			
		||||
out/**/*.o
 | 
			
		||||
out/**/*.d
 | 
			
		||||
NOTES.md
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -8,4 +8,7 @@ out
 | 
			
		||||
*.swo
 | 
			
		||||
*.swp
 | 
			
		||||
.zsign_cache/
 | 
			
		||||
NOTES.md
 | 
			
		||||
 | 
			
		||||
deps/codemirror/cm6.js
 | 
			
		||||
deps/prettier/standalone.mjs
 | 
			
		||||
deps/lit
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,7 @@
 | 
			
		||||
[submodule "deps/zlib"]
 | 
			
		||||
	path = deps/zlib
 | 
			
		||||
	url = https://github.com/madler/zlib.git
 | 
			
		||||
	branch = master
 | 
			
		||||
[submodule "deps/libsodium"]
 | 
			
		||||
	path = deps/libsodium
 | 
			
		||||
	url = https://github.com/jedisct1/libsodium.git
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +0,0 @@
 | 
			
		||||
default: true
 | 
			
		||||
MD010: false # Ignore tabs in code blocks
 | 
			
		||||
MD013: false # Don't wrap lines by default
 | 
			
		||||
MD046: 
 | 
			
		||||
  style: "fenced" # Force fenced code blocks
 | 
			
		||||
@@ -12,8 +12,3 @@ deps
 | 
			
		||||
apps/ssb/tribute.esm.js
 | 
			
		||||
apps/api/app.js
 | 
			
		||||
**/emojis.json
 | 
			
		||||
 | 
			
		||||
# only markdownlint should deal with the documentation
 | 
			
		||||
docs/**/*.md
 | 
			
		||||
 | 
			
		||||
NOTES.md
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										78
									
								
								GNUmakefile
									
									
									
									
									
								
							
							
						
						
									
										78
									
								
								GNUmakefile
									
									
									
									
									
								
							@@ -3,21 +3,31 @@
 | 
			
		||||
MAKEFLAGS += --warn-undefined-variables
 | 
			
		||||
MAKEFLAGS += --no-builtin-rules
 | 
			
		||||
 | 
			
		||||
VERSION_CODE := 19
 | 
			
		||||
VERSION_NUMBER := 0.0.19-wip
 | 
			
		||||
VERSION_NAME := Don't let your loyalty become a burden.
 | 
			
		||||
VERSION_CODE := 17
 | 
			
		||||
VERSION_NUMBER := 0.0.17-wip
 | 
			
		||||
VERSION_NAME := Please enjoy responsibly.
 | 
			
		||||
 | 
			
		||||
SQLITE_URL := https://www.sqlite.org/2024/sqlite-amalgamation-3450300.zip
 | 
			
		||||
LIBUV_URL := https://dist.libuv.org/dist/v1.48.0/libuv-v1.48.0.tar.gz
 | 
			
		||||
SQLITE_URL := https://www.sqlite.org/2024/sqlite-amalgamation-3450200.zip
 | 
			
		||||
 | 
			
		||||
PROJECT = tildefriends
 | 
			
		||||
BUILD_DIR ?= out
 | 
			
		||||
UNAME_S := $(shell uname -s)
 | 
			
		||||
UNAME_M := $(shell uname -m)
 | 
			
		||||
 | 
			
		||||
ANDROID_SDK ?= ~/Android/Sdk
 | 
			
		||||
#ANDROID_SDK ?= ~/Android/Sdk
 | 
			
		||||
ANDROID_SDK ?= /nix/store/54n9xsbb8gxa719g0bs7ghp336pax6mq-androidsdk/libexec/android-sdk
 | 
			
		||||
 | 
			
		||||
HAVE_WIN := 0
 | 
			
		||||
ifeq ($(UNAME_M),x86_64)
 | 
			
		||||
ifneq ($(UNAME_S),Haiku)
 | 
			
		||||
debug: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
 | 
			
		||||
debug: LDFLAGS += -fsanitize=address -fsanitize=undefined
 | 
			
		||||
endif
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
ifeq ($(UNAME_M),aarch64)
 | 
			
		||||
debug: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
 | 
			
		||||
debug: LDFLAGS += -fsanitize=address -fsanitize=undefined
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
ifeq ($(UNAME_S),Darwin)
 | 
			
		||||
BUILD_TYPES := macosdebug macosrelease iosdebug iosrelease iossimdebug iossimrelease
 | 
			
		||||
@@ -41,6 +51,7 @@ LDFLAGS += \
 | 
			
		||||
	-lc++abi
 | 
			
		||||
HAVE_ANDROID := 0
 | 
			
		||||
HAVE_LINUX_IOS := 0
 | 
			
		||||
HAVE_WIN := 0
 | 
			
		||||
else
 | 
			
		||||
$(error Unexpected host platform $(UNAME_S).)
 | 
			
		||||
endif
 | 
			
		||||
@@ -57,11 +68,11 @@ CFLAGS += \
 | 
			
		||||
	-fno-exceptions \
 | 
			
		||||
	-g
 | 
			
		||||
 | 
			
		||||
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0
 | 
			
		||||
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-34
 | 
			
		||||
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.2.11394342
 | 
			
		||||
ANDROID_MIN_SDK_VERSION := 24
 | 
			
		||||
ANDROID_TARGET_SDK_VERSION := 34
 | 
			
		||||
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0
 | 
			
		||||
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-$(ANDROID_TARGET_SDK_VERSION)
 | 
			
		||||
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.2.11394342
 | 
			
		||||
 | 
			
		||||
ANDROID_ARMV7A_TARGETS := \
 | 
			
		||||
	out/androiddebug-armv7a/tildefriends \
 | 
			
		||||
@@ -211,18 +222,6 @@ $(IOS_TARGETS): LDFLAGS += -Ldeps/openssl/ios/ios64-xcrun/usr/local/lib
 | 
			
		||||
$(IOSSIM_TARGETS): CFLAGS += -Ideps/openssl/ios/iossimulator-xcrun/usr/local/include
 | 
			
		||||
$(IOSSIM_TARGETS): LDFLAGS += -Ldeps/openssl/ios/iossimulator-xcrun/usr/local/lib
 | 
			
		||||
 | 
			
		||||
ifeq ($(UNAME_M),x86_64)
 | 
			
		||||
ifneq ($(UNAME_S),Haiku)
 | 
			
		||||
out/debug/tildefriends: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
 | 
			
		||||
out/debug/tildefriends: LDFLAGS += -fsanitize=address -fsanitize=undefined
 | 
			
		||||
endif
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
ifeq ($(UNAME_M),aarch64)
 | 
			
		||||
out/debug/tildefriends: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
 | 
			
		||||
out/debug/tildefriends: LDFLAGS += -fsanitize=address -fsanitize=undefined
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
get_objs = \
 | 
			
		||||
	$(foreach build_type,$(BUILD_TYPES),$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)))))) \
 | 
			
		||||
	$(foreach build_type,debug release,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_unix))))) \
 | 
			
		||||
@@ -579,7 +578,7 @@ $(MINIUNZIP_OBJS): CFLAGS += \
 | 
			
		||||
LDFLAGS += \
 | 
			
		||||
	-pthread \
 | 
			
		||||
	-lm
 | 
			
		||||
$(LINUX_TARGETS) $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
 | 
			
		||||
debug release $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
 | 
			
		||||
	-lssl \
 | 
			
		||||
	-lcrypto
 | 
			
		||||
ifneq ($(UNAME_S),Haiku)
 | 
			
		||||
@@ -686,6 +685,7 @@ out/res/drawable_icon.xml.flat: 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 $@)
 | 
			
		||||
	mkdir -p out/apk
 | 
			
		||||
	@$(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)
 | 
			
		||||
@@ -693,7 +693,7 @@ CLASS_FILES := $(foreach src,$(JAVA_FILES),out/classes/com/unprompted/tildefrien
 | 
			
		||||
 | 
			
		||||
$(CLASS_FILES) &: $(JAVA_FILES)
 | 
			
		||||
	@echo "[javac] $(CLASS_FILES)"
 | 
			
		||||
	@javac --release 8 -encoding UTF-8 -Xlint:deprecation -XDuseUnsharedTable=true -classpath $(ANDROID_PLATFORM)/android.jar:$(ANDROID_BUILD_TOOLS)/core-lambda-stubs.jar -d out/classes $(JAVA_FILES)
 | 
			
		||||
	@javac --release 8 -Xlint:deprecation -classpath $(ANDROID_PLATFORM)/android.jar -d out/classes $(JAVA_FILES)
 | 
			
		||||
 | 
			
		||||
out/apk/classes.dex: $(CLASS_FILES)
 | 
			
		||||
	@mkdir -p $(dir $@)
 | 
			
		||||
@@ -729,7 +729,7 @@ out/apk/TildeFriends-arm-%.unsigned.apk:
 | 
			
		||||
	@cp out/apk/res.apk $@.zip
 | 
			
		||||
	@cp out/apk/classes.dex out/apk-arm-$(BUILD_TYPE)/
 | 
			
		||||
	@cd out/apk-arm-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../
 | 
			
		||||
	@zip -u $@.zip -q -9 $(RAW_FILES)
 | 
			
		||||
	@zip -u $@.zip -q $(RAW_FILES)
 | 
			
		||||
	@$(ANDROID_BUILD_TOOLS)/zipalign -f 4 $@.zip $@
 | 
			
		||||
 | 
			
		||||
out/apk/TildeFriends-x86-%.unsigned.apk:
 | 
			
		||||
@@ -742,7 +742,7 @@ out/apk/TildeFriends-x86-%.unsigned.apk:
 | 
			
		||||
	@cp out/apk/res.apk $@.zip
 | 
			
		||||
	@cp out/apk/classes.dex out/apk-x86-$(BUILD_TYPE)/
 | 
			
		||||
	@cd out/apk-x86-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../
 | 
			
		||||
	@zip -u $@.zip -q -9 $(RAW_FILES)
 | 
			
		||||
	@zip -u $@.zip -q $(RAW_FILES)
 | 
			
		||||
	@$(ANDROID_BUILD_TOOLS)/zipalign -f 4 $@.zip $@
 | 
			
		||||
 | 
			
		||||
out/%.apk: out/apk/%.unsigned.apk
 | 
			
		||||
@@ -759,7 +759,7 @@ release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-releas
 | 
			
		||||
 | 
			
		||||
releaseapkgo: out/TildeFriends-arm-release.apk
 | 
			
		||||
	@adb install -r $<
 | 
			
		||||
	@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
 | 
			
		||||
	@adb shell am start com.unprompted.tildefriends/.MainActivity
 | 
			
		||||
.PHONY: releaseapkgo
 | 
			
		||||
 | 
			
		||||
# iOS Support
 | 
			
		||||
@@ -770,10 +770,10 @@ out/%.app/tildefriends.png: src/ios/tildefriends.png
 | 
			
		||||
	@mkdir -p $(dir $@)
 | 
			
		||||
	@cp -v $< $@
 | 
			
		||||
 | 
			
		||||
out/data.zip: $(RAW_FILES)
 | 
			
		||||
out/%/data.zip: $(RAW_FILES)
 | 
			
		||||
	@zip -u $@ -q -9 $(RAW_FILES)
 | 
			
		||||
 | 
			
		||||
out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/data.zip
 | 
			
		||||
out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/tildefriends-%.app/data.zip
 | 
			
		||||
	@mkdir -p $(dir $@)
 | 
			
		||||
	@cp -v $< $@
 | 
			
		||||
ifeq ($(HAVE_LINUX_IOS),1)
 | 
			
		||||
@@ -788,16 +788,6 @@ out/tildefriends-%.ipa: out/tildefriends-ios%.app/tildefriends
 | 
			
		||||
	@cd $@.tmp/ && zip -u ../../$@ -q -9 -r ./
 | 
			
		||||
	@rm -rf $@.tmp/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
out/%/tildefriends.standalone: out/%/tildefriends out/data.zip
 | 
			
		||||
	@echo "[standalone] $@"
 | 
			
		||||
	@cat $< out/data.zip > $@
 | 
			
		||||
	@chmod +x $@
 | 
			
		||||
out/%/tildefriends.standalone.exe: out/%/tildefriends.exe out/data.zip
 | 
			
		||||
	@echo "[standalone] $@"
 | 
			
		||||
	@cat $< out/data.zip > $@
 | 
			
		||||
	@chmod +x $@
 | 
			
		||||
 | 
			
		||||
iossimdebug-app: out/tildefriends-iossimdebug.app/tildefriends
 | 
			
		||||
iossimrelease-app: out/tildefriends-iossimrelease.app/tildefriends
 | 
			
		||||
iosdebug-app: out/tildefriends-iosdebug.app/tildefriends
 | 
			
		||||
@@ -820,10 +810,6 @@ apklog:
 | 
			
		||||
.PHONY: apklog
 | 
			
		||||
 | 
			
		||||
fetchdeps:
 | 
			
		||||
	@echo "[fetch] libuv"
 | 
			
		||||
	@test -f out/deps/libuv.tar.gz && test "$$(cat out/deps/libuv.txt 2>/dev/null)" = $(LIBUV_URL) || (mkdir -p out/deps/ && curl -q $(LIBUV_URL) -o out/deps/libuv.tar.gz)
 | 
			
		||||
	@test -d deps/libuv/ && test "$$(cat out/deps/libuv.txt 2>/dev/null)" = $(LIBUV_URL) || (rm -rf deps/libuv/ && mkdir -p deps/libuv/ && tar -C deps/libuv/ -m --strip=1 -xf out/deps/libuv.tar.gz)
 | 
			
		||||
	@echo -n $(LIBUV_URL) > out/deps/libuv.txt
 | 
			
		||||
	@echo "[fetch] sqlite"
 | 
			
		||||
	@test -f out/deps/sqlite.zip && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p out/deps/ && curl -q $(SQLITE_URL) -o out/deps/sqlite.zip)
 | 
			
		||||
	@test -d deps/sqlite/ && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p deps/sqlite/ && unzip -qDjo -d deps/sqlite/ out/deps/sqlite.zip)
 | 
			
		||||
@@ -858,11 +844,11 @@ clean:
 | 
			
		||||
	rm -rf $(BUILD_DIR)
 | 
			
		||||
.PHONY: clean
 | 
			
		||||
 | 
			
		||||
dist: release-apk iosrelease-ipa $(if $(HAVE_WIN), out/winrelease/tildefriends.standalone.exe)
 | 
			
		||||
dist: release-apk iosrelease-ipa
 | 
			
		||||
	@echo [archive] dist/tildefriends-$(VERSION_NUMBER).tar.xz
 | 
			
		||||
	@rm -rf out/tildefriends-$(VERSION_NUMBER)
 | 
			
		||||
	@mkdir -p dist/ out/tildefriends-$(VERSION_NUMBER)
 | 
			
		||||
	@git ls-files --recurse-submodules | tar -c -T- | tar -x -C out/tildefriends-$(VERSION_NUMBER)
 | 
			
		||||
	@git archive main | tar -x -C out/tildefriends-$(VERSION_NUMBER)
 | 
			
		||||
	@tar \
 | 
			
		||||
		--exclude=apps/welcome* \
 | 
			
		||||
		--exclude=deps/libbacktrace/Isaac.Newton-Opticks.txt \
 | 
			
		||||
@@ -887,8 +873,6 @@ dist: release-apk iosrelease-ipa $(if $(HAVE_WIN), out/winrelease/tildefriends.s
 | 
			
		||||
	@cp out/TildeFriends-arm-release.zopfli.apk dist/TildeFriends-arm-$(VERSION_NUMBER).apk
 | 
			
		||||
	@echo "[cp] TildeFriends-$(VERSION_NUMBER).ipa"
 | 
			
		||||
	@cp out/tildefriends-release.ipa dist/TildeFriends-$(VERSION_NUMBER).ipa
 | 
			
		||||
	@test $(HAVE_WIN) && echo "[cp] tildefriends-$(VERSION_NUMBER).exe"
 | 
			
		||||
	@test $(HAVE_WIN) && cp out/winrelease/tildefriends.standalone.exe dist/tildefriends-$(VERSION_NUMBER).exe
 | 
			
		||||
.PHONY: dist
 | 
			
		||||
 | 
			
		||||
dist-test: dist
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							@@ -1,4 +1,4 @@
 | 
			
		||||
Copyright 2014-2024 Cory McWilliams
 | 
			
		||||
Copyright 2014 Cory McWilliams
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										37
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								README.md
									
									
									
									
									
								
							@@ -4,19 +4,46 @@ Tilde Friends is a tool for making and sharing.
 | 
			
		||||
 | 
			
		||||
A public instance lives at https://www.tildefriends.net/.
 | 
			
		||||
 | 
			
		||||
It is both a peer-to-peer social network client, participating in Secure Scuttlebutt, as well as a platform for writing and running web applications.
 | 
			
		||||
It is both a peer-to-peer social network client, participating in Secure
 | 
			
		||||
Scuttlebutt, as well as a platform for writing and running web applications.
 | 
			
		||||
 | 
			
		||||
## Goals
 | 
			
		||||
 | 
			
		||||
1. Make it easy and fun to run all sorts of web applications.
 | 
			
		||||
2. Provide security that is easy to understand and protects your data.
 | 
			
		||||
3. Make creating and sharing web applications accessible to anyone with a browser.
 | 
			
		||||
3. Make creating and sharing web applications accessible to anyone with a
 | 
			
		||||
   browser.
 | 
			
		||||
 | 
			
		||||
## Building
 | 
			
		||||
 | 
			
		||||
Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. Builds for
 | 
			
		||||
all of those host platforms plus mingw64, iOS, and android.
 | 
			
		||||
 | 
			
		||||
1. Requires openssl (`libssl-dev`, in debian-speak). All other dependencies
 | 
			
		||||
   are kept up to date in the tree.
 | 
			
		||||
2. To build, run `make debug` or `make release`. An executable will be
 | 
			
		||||
   generated in a subdirectory of `out/`.
 | 
			
		||||
3. It's possible to build for Android, iOS, and Windows on Linux, if you have
 | 
			
		||||
   the right dependencies in the right places. `make windebug winrelease
 | 
			
		||||
iosdebug-ipa iosrelease-ipa release-apk`.
 | 
			
		||||
4. To build in docker, `docker build .`.
 | 
			
		||||
5. `make format` will normalize formatting to the coding standard.
 | 
			
		||||
 | 
			
		||||
## Running
 | 
			
		||||
 | 
			
		||||
By default, running the built `tildefriends` executable will start a web server
 | 
			
		||||
at <http://localhost:12345/>. `tildefriends -h` lists further options.
 | 
			
		||||
 | 
			
		||||
The first user to create an account and log in will be granted administrative
 | 
			
		||||
privileges. Further administration can be done at
 | 
			
		||||
<http://localhost:12345/~core/admin/>.
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
Docs are a work in progress in the `docs` folder, or alternatively in Tilde Friends: <https://www.tildefriends.net/~cory/wiki/#test-wiki/tf-app-quick-reference>.
 | 
			
		||||
Docs are a work in progress:
 | 
			
		||||
<https://www.tildefriends.net/~cory/wiki/#test-wiki/tf-app-quick-reference>.
 | 
			
		||||
 | 
			
		||||
## License
 | 
			
		||||
 | 
			
		||||
All code, documentation and assets unless otherwise noted in is provided under the
 | 
			
		||||
[MIT](https://opensource.org/licenses/MIT/) license.
 | 
			
		||||
All code unless otherwise noted in is provided under the
 | 
			
		||||
[MIT](https://opensource.org/licenses/MIT) license.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								android-sdk.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								android-sdk.nix
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
with import <nixpkgs> {};
 | 
			
		||||
let
 | 
			
		||||
  androidComposition = androidenv.composeAndroidPackages {
 | 
			
		||||
    cmdLineToolsVersion = "9.0";
 | 
			
		||||
    toolsVersion = "26.1.1";
 | 
			
		||||
    platformToolsVersion = "34.0.5";
 | 
			
		||||
    buildToolsVersions = [ "34.0.0" ];
 | 
			
		||||
    includeEmulator = false;
 | 
			
		||||
    #emulatorVersion = "30.3.4";
 | 
			
		||||
    platformVersions = [ "34" ];
 | 
			
		||||
    includeSources = false;
 | 
			
		||||
    includeSystemImages = false;
 | 
			
		||||
    #systemImageTypes = [ "google_apis_playstore" ];
 | 
			
		||||
    #abiVersions = [ "armeabi-v7a" "arm64-v8a" ];
 | 
			
		||||
    #cmakeVersions = [ "3.10.2" ];
 | 
			
		||||
    includeNDK = true;
 | 
			
		||||
    ndkVersions = ["26.0.10792818"];
 | 
			
		||||
    useGoogleAPIs = false;
 | 
			
		||||
    useGoogleTVAddOns = false;
 | 
			
		||||
    #includeExtras = [
 | 
			
		||||
    #  "extras;google;gcm"
 | 
			
		||||
    #];
 | 
			
		||||
  };
 | 
			
		||||
in
 | 
			
		||||
androidComposition.androidsdk
 | 
			
		||||
 | 
			
		||||
# $ NIXPKGS_ACCEPT_ANDROID_SDK_LICENSE=1 NIXPKGS_ALLOW_UNFREE=1 nix-build android-sdk.nix --impure
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
{
 | 
			
		||||
	"type": "tildefriends-app",
 | 
			
		||||
	"emoji": "🎛",
 | 
			
		||||
	"previous": "&vrpS/vE7n588iYv1p8HafDxHB+YDHTrtUbJiu9nGA9I=.sha256"
 | 
			
		||||
	"emoji": "🎛"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,37 +4,9 @@
 | 
			
		||||
		<script>
 | 
			
		||||
			const g_data = $data;
 | 
			
		||||
		</script>
 | 
			
		||||
		<link rel="stylesheet" href="w3.css"></link>
 | 
			
		||||
		<style>
 | 
			
		||||
			/* 2018 Valiant Poppy */
 | 
			
		||||
			.w3-theme-l5 {color:#000 !important; background-color:#fbf3f3 !important}
 | 
			
		||||
			.w3-theme-l4 {color:#000 !important; background-color:#f3d7d6 !important}
 | 
			
		||||
			.w3-theme-l3 {color:#000 !important; background-color:#e6afae !important}
 | 
			
		||||
			.w3-theme-l2 {color:#fff !important; background-color:#da8785 !important}
 | 
			
		||||
			.w3-theme-l1 {color:#fff !important; background-color:#cd5f5d !important}
 | 
			
		||||
			.w3-theme-d1 {color:#fff !important; background-color:#a93634 !important}
 | 
			
		||||
			.w3-theme-d2 {color:#fff !important; background-color:#96302e !important}
 | 
			
		||||
			.w3-theme-d3 {color:#fff !important; background-color:#832a28 !important}
 | 
			
		||||
			.w3-theme-d4 {color:#fff !important; background-color:#702423 !important}
 | 
			
		||||
			.w3-theme-d5 {color:#fff !important; background-color:#5e1e1d !important}
 | 
			
		||||
 | 
			
		||||
			.w3-theme-light {color:#000 !important; background-color:#fbf3f3 !important}
 | 
			
		||||
			.w3-theme-dark {color:#fff !important; background-color:#5e1e1d !important}
 | 
			
		||||
			.w3-theme-action {color:#fff !important; background-color:#5e1e1d !important}
 | 
			
		||||
 | 
			
		||||
			.w3-theme {color:#fff !important; background-color:#bd3d3a !important}
 | 
			
		||||
			.w3-text-theme {color:#bd3d3a !important}
 | 
			
		||||
			.w3-border-theme {border-color:#bd3d3a !important}
 | 
			
		||||
 | 
			
		||||
			.w3-hover-theme:hover {color:#fff !important; background-color:#bd3d3a !important}
 | 
			
		||||
			.w3-hover-text-theme:hover {color:#bd3d3a !important}
 | 
			
		||||
			.w3-hover-border-theme:hover {border-color:#bd3d3a !important}
 | 
			
		||||
		</style>
 | 
			
		||||
	</head>
 | 
			
		||||
	<body class="w3-theme-l4">
 | 
			
		||||
		<header class="w3-row w3-padding w3-header w3-theme-l1">
 | 
			
		||||
			<h1>Tilde Friends Administration</h1>
 | 
			
		||||
		</header>
 | 
			
		||||
	<body style="color: #fff; width: 100%">
 | 
			
		||||
		<h1>Tilde Friends Administration</h1>
 | 
			
		||||
	</body>
 | 
			
		||||
	<script type="module" src="script.js"></script>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
@@ -32,54 +32,59 @@ window.addEventListener('load', function () {
 | 
			
		||||
	function input_template(key, description) {
 | 
			
		||||
		if (description.type === 'boolean') {
 | 
			
		||||
			return html`
 | 
			
		||||
				<li class="w3-row">
 | 
			
		||||
					<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${key}</label>
 | 
			
		||||
					<div class="w3-quarter w3-padding">${description.description}</div>
 | 
			
		||||
					<input class="w3-quarter w3-check" type="checkbox" ?checked=${description.value} id=${'gs_' + key}></input>
 | 
			
		||||
					<button class="w3-quarter w3-button w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.checked)}>Set</button>
 | 
			
		||||
				</li>
 | 
			
		||||
				<div style="margin-top: 1em">
 | 
			
		||||
					<label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
 | 
			
		||||
					<div>
 | 
			
		||||
						<input type="checkbox" ?checked=${description.value} id=${'gs_' + key}></input>
 | 
			
		||||
						<button @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.checked)}>Set</button>
 | 
			
		||||
						<div>${description.description}</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			`;
 | 
			
		||||
		} else if (description.type === 'textarea') {
 | 
			
		||||
			return html`
 | 
			
		||||
				<li class="w3-row">
 | 
			
		||||
					<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${key}</label>
 | 
			
		||||
					<div class="w3-rest w3-padding">${description.description}</div>
 | 
			
		||||
					<textarea class="w3-input" style="vertical-align: top; resize: vertical" id=${'gs_' + key}>${description.value}</textarea>
 | 
			
		||||
					<button class="w3-button w3-right w3-quarter w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.value)}>Set</button>
 | 
			
		||||
				</li>
 | 
			
		||||
				<div style="margin-top: 1em"">
 | 
			
		||||
					<label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
 | 
			
		||||
					<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.previousElementSibling.firstElementChild.value)}>Set</button>
 | 
			
		||||
						<div>${description.description}</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			`;
 | 
			
		||||
		} else {
 | 
			
		||||
			return html`
 | 
			
		||||
				<li class="w3-row">
 | 
			
		||||
					<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${key}</label>
 | 
			
		||||
					<div class="w3-quarter w3-padding">${description.description}</div>
 | 
			
		||||
					<input class="w3-input w3-quarter" type="text" value="${description.value}" id=${'gs_' + key}></input>
 | 
			
		||||
					<button class="w3-button w3-quarter w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.value)}>Set</button>
 | 
			
		||||
				</li>
 | 
			
		||||
				<div style="margin-top: 1em">
 | 
			
		||||
					<label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
 | 
			
		||||
					<div>
 | 
			
		||||
						<input type="text" value="${description.value}" id=${'gs_' + key}></input>
 | 
			
		||||
						<button @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.value)}>Set</button>
 | 
			
		||||
						<div>${description.description}</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			`;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	const user_template = (user, permissions) => html`
 | 
			
		||||
		<li class="w3-card w3-margin">
 | 
			
		||||
			<button class="w3-button w3-theme-action" @click=${(e) => delete_user(user)}>Delete</button>
 | 
			
		||||
		<li>
 | 
			
		||||
			<button @click=${(e) => delete_user(user)}>Delete</button>
 | 
			
		||||
			${user}: ${permissions.map((x) => permission_template(x))}
 | 
			
		||||
		</li>
 | 
			
		||||
	`;
 | 
			
		||||
	const users_template = (users) =>
 | 
			
		||||
		html`
 | 
			
		||||
			<header class="w3-container w3-theme-l2"><h2>Users</h2></header>
 | 
			
		||||
			<ul class="w3-ul">
 | 
			
		||||
		html`<h2>Users</h2>
 | 
			
		||||
			<ul>
 | 
			
		||||
				${Object.entries(users).map((u) => user_template(u[0], u[1]))}
 | 
			
		||||
			</ul>`;
 | 
			
		||||
	const page_template = (data) =>
 | 
			
		||||
		html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%">
 | 
			
		||||
			<header class="w3-container w3-theme-l2"><h2>Global Settings</h2></header>
 | 
			
		||||
			<div class="w3-container">
 | 
			
		||||
				<ul class="w3-ul">
 | 
			
		||||
					${Object.keys(data.settings)
 | 
			
		||||
						.sort()
 | 
			
		||||
						.map((x) => html`${input_template(x, data.settings[x])}`)}
 | 
			
		||||
				</ul>
 | 
			
		||||
			<h2>Global Settings</h2>
 | 
			
		||||
			<div>
 | 
			
		||||
				${Object.keys(data.settings)
 | 
			
		||||
					.sort()
 | 
			
		||||
					.map((x) => html`${input_template(x, data.settings[x])}`)}
 | 
			
		||||
			</div>
 | 
			
		||||
			${users_template(data.users)}
 | 
			
		||||
		</div> `;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,235 +0,0 @@
 | 
			
		||||
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
 | 
			
		||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
 | 
			
		||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
 | 
			
		||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
 | 
			
		||||
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
 | 
			
		||||
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
 | 
			
		||||
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
 | 
			
		||||
a{background-color:transparent}a:active,a:hover{outline-width:0}
 | 
			
		||||
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
 | 
			
		||||
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
 | 
			
		||||
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
 | 
			
		||||
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
 | 
			
		||||
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
 | 
			
		||||
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
 | 
			
		||||
button,input{overflow:visible}button,select{text-transform:none}
 | 
			
		||||
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
 | 
			
		||||
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
 | 
			
		||||
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
 | 
			
		||||
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
 | 
			
		||||
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
 | 
			
		||||
[type=checkbox],[type=radio]{padding:0}
 | 
			
		||||
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
 | 
			
		||||
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
 | 
			
		||||
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
 | 
			
		||||
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
 | 
			
		||||
/* End extract */
 | 
			
		||||
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
 | 
			
		||||
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
 | 
			
		||||
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
 | 
			
		||||
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
 | 
			
		||||
hr{border:0;border-top:1px solid #eee;margin:20px 0}
 | 
			
		||||
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
 | 
			
		||||
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
 | 
			
		||||
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
 | 
			
		||||
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
 | 
			
		||||
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
 | 
			
		||||
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
 | 
			
		||||
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
 | 
			
		||||
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
 | 
			
		||||
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
 | 
			
		||||
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}   
 | 
			
		||||
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
 | 
			
		||||
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
 | 
			
		||||
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
 | 
			
		||||
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
 | 
			
		||||
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
 | 
			
		||||
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
 | 
			
		||||
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
 | 
			
		||||
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
 | 
			
		||||
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
 | 
			
		||||
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
 | 
			
		||||
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
 | 
			
		||||
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
 | 
			
		||||
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
 | 
			
		||||
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
 | 
			
		||||
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
 | 
			
		||||
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
 | 
			
		||||
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
 | 
			
		||||
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
 | 
			
		||||
.w3-main,#main{transition:margin-left .4s}
 | 
			
		||||
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
 | 
			
		||||
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
 | 
			
		||||
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
 | 
			
		||||
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
 | 
			
		||||
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
 | 
			
		||||
.w3-bar .w3-button{white-space:normal}
 | 
			
		||||
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
 | 
			
		||||
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
 | 
			
		||||
.w3-responsive{display:block;overflow-x:auto}
 | 
			
		||||
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
 | 
			
		||||
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
 | 
			
		||||
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
 | 
			
		||||
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
 | 
			
		||||
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
 | 
			
		||||
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
 | 
			
		||||
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
 | 
			
		||||
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
 | 
			
		||||
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
 | 
			
		||||
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
 | 
			
		||||
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
 | 
			
		||||
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
 | 
			
		||||
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
 | 
			
		||||
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
 | 
			
		||||
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
 | 
			
		||||
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
 | 
			
		||||
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
 | 
			
		||||
@media (max-width:1205px){.w3-auto{max-width:95%}}
 | 
			
		||||
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
 | 
			
		||||
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}	
 | 
			
		||||
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
 | 
			
		||||
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
 | 
			
		||||
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
 | 
			
		||||
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
 | 
			
		||||
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
 | 
			
		||||
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
 | 
			
		||||
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
 | 
			
		||||
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
 | 
			
		||||
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
 | 
			
		||||
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
 | 
			
		||||
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
 | 
			
		||||
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
 | 
			
		||||
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
 | 
			
		||||
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
 | 
			
		||||
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
 | 
			
		||||
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
 | 
			
		||||
.w3-display-position{position:absolute}
 | 
			
		||||
.w3-circle{border-radius:50%}
 | 
			
		||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
 | 
			
		||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
 | 
			
		||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
 | 
			
		||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
 | 
			
		||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
 | 
			
		||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
 | 
			
		||||
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
 | 
			
		||||
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
 | 
			
		||||
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
 | 
			
		||||
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
 | 
			
		||||
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
 | 
			
		||||
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
 | 
			
		||||
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
 | 
			
		||||
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
 | 
			
		||||
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
 | 
			
		||||
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
 | 
			
		||||
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
 | 
			
		||||
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
 | 
			
		||||
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
 | 
			
		||||
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
 | 
			
		||||
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
 | 
			
		||||
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
 | 
			
		||||
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
 | 
			
		||||
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
 | 
			
		||||
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
 | 
			
		||||
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
 | 
			
		||||
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
 | 
			
		||||
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
 | 
			
		||||
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
 | 
			
		||||
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
 | 
			
		||||
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
 | 
			
		||||
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
 | 
			
		||||
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
 | 
			
		||||
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
 | 
			
		||||
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
 | 
			
		||||
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
 | 
			
		||||
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
 | 
			
		||||
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
 | 
			
		||||
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
 | 
			
		||||
.w3-left{float:left!important}.w3-right{float:right!important}
 | 
			
		||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
 | 
			
		||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
 | 
			
		||||
.w3-hover-none:hover{box-shadow:none!important}
 | 
			
		||||
/* Colors */
 | 
			
		||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
 | 
			
		||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
 | 
			
		||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
 | 
			
		||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
 | 
			
		||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
 | 
			
		||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
 | 
			
		||||
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
 | 
			
		||||
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
 | 
			
		||||
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
 | 
			
		||||
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
 | 
			
		||||
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
 | 
			
		||||
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
 | 
			
		||||
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
 | 
			
		||||
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
 | 
			
		||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
 | 
			
		||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
 | 
			
		||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
 | 
			
		||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
 | 
			
		||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
 | 
			
		||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
 | 
			
		||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
 | 
			
		||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
 | 
			
		||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
 | 
			
		||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
 | 
			
		||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
 | 
			
		||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
 | 
			
		||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
 | 
			
		||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
 | 
			
		||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
 | 
			
		||||
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
 | 
			
		||||
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
 | 
			
		||||
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
 | 
			
		||||
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
 | 
			
		||||
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
 | 
			
		||||
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
 | 
			
		||||
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
 | 
			
		||||
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
 | 
			
		||||
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
 | 
			
		||||
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
 | 
			
		||||
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
 | 
			
		||||
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
 | 
			
		||||
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
 | 
			
		||||
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
 | 
			
		||||
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
 | 
			
		||||
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
 | 
			
		||||
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
 | 
			
		||||
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
 | 
			
		||||
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
 | 
			
		||||
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
 | 
			
		||||
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
 | 
			
		||||
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
 | 
			
		||||
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
 | 
			
		||||
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
 | 
			
		||||
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
 | 
			
		||||
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
 | 
			
		||||
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
 | 
			
		||||
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
 | 
			
		||||
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
 | 
			
		||||
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
 | 
			
		||||
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
 | 
			
		||||
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
 | 
			
		||||
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
 | 
			
		||||
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
 | 
			
		||||
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
 | 
			
		||||
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
 | 
			
		||||
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
 | 
			
		||||
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
 | 
			
		||||
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
 | 
			
		||||
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
 | 
			
		||||
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
 | 
			
		||||
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
 | 
			
		||||
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
 | 
			
		||||
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
 | 
			
		||||
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
 | 
			
		||||
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
 | 
			
		||||
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
 | 
			
		||||
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
 | 
			
		||||
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
 | 
			
		||||
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
 | 
			
		||||
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
 | 
			
		||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
 | 
			
		||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
 | 
			
		||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
 | 
			
		||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
 | 
			
		||||
							
								
								
									
										4
									
								
								apps/blog/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								apps/blog/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
	"type": "tildefriends-app",
 | 
			
		||||
	"emoji": "🪪",
 | 
			
		||||
	"previous": "&de7q4A59auHP/34bXgeNH05JZoxsGr5TjwXPvehWH30=.sha256"
 | 
			
		||||
	"previous": "&kgukkyDk1RxgfzgMH6H/0QeDPIuwPZypLuAFax21ljk=.sha256"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,36 +19,7 @@ tfrpc.register(async function reload() {
 | 
			
		||||
async function main() {
 | 
			
		||||
	let ids = await ssb.getIdentities();
 | 
			
		||||
	await app.setDocument(
 | 
			
		||||
		`
 | 
			
		||||
		<head>
 | 
			
		||||
			<link rel="stylesheet" href="w3.css"></link>
 | 
			
		||||
			<style>
 | 
			
		||||
				/* "2018 Sargasso Sea" */
 | 
			
		||||
				.w3-theme-l5 {color:#000 !important; background-color:#f3f4f7 !important}
 | 
			
		||||
				.w3-theme-l4 {color:#000 !important; background-color:#d7dbe3 !important}
 | 
			
		||||
				.w3-theme-l3 {color:#000 !important; background-color:#b0b6c8 !important}
 | 
			
		||||
				.w3-theme-l2 {color:#fff !important; background-color:#8892ac !important}
 | 
			
		||||
				.w3-theme-l1 {color:#fff !important; background-color:#636f8e !important}
 | 
			
		||||
				.w3-theme-d1 {color:#fff !important; background-color:#40485c !important}
 | 
			
		||||
				.w3-theme-d2 {color:#fff !important; background-color:#394052 !important}
 | 
			
		||||
				.w3-theme-d3 {color:#fff !important; background-color:#323848 !important}
 | 
			
		||||
				.w3-theme-d4 {color:#fff !important; background-color:#2b303d !important}
 | 
			
		||||
				.w3-theme-d5 {color:#fff !important; background-color:#242833 !important}
 | 
			
		||||
 | 
			
		||||
				.w3-theme-light {color:#000 !important; background-color:#f3f4f7 !important}
 | 
			
		||||
				.w3-theme-dark {color:#fff !important; background-color:#242833 !important}
 | 
			
		||||
				.w3-theme-action {color:#fff !important; background-color:#242833 !important}
 | 
			
		||||
 | 
			
		||||
				.w3-theme {color:#fff !important; background-color:#485167 !important}
 | 
			
		||||
				.w3-text-theme {color:#485167 !important}
 | 
			
		||||
				.w3-border-theme {border-color:#485167 !important}
 | 
			
		||||
 | 
			
		||||
				.w3-hover-theme:hover {color:#fff !important; background-color:#485167 !important}
 | 
			
		||||
				.w3-hover-text-theme:hover {color:#485167 !important}
 | 
			
		||||
				.w3-hover-border-theme:hover {border-color:#485167 !important}
 | 
			
		||||
			</style>
 | 
			
		||||
		</head>
 | 
			
		||||
		<body class="w3-theme-l3">
 | 
			
		||||
		`<body style="color: #fff">
 | 
			
		||||
		<script>const handler = {};</script>
 | 
			
		||||
		<script type="module">
 | 
			
		||||
			import * as tfrpc from '/static/tfrpc.js';
 | 
			
		||||
@@ -56,8 +27,7 @@ async function main() {
 | 
			
		||||
				let id = event.srcElement.dataset.id;
 | 
			
		||||
				let element = document.createElement('textarea');
 | 
			
		||||
				element.value = await tfrpc.rpc.get_private_key(id);
 | 
			
		||||
				element.style = 'width: 100%; height: auto; read-only: true; resize: none';
 | 
			
		||||
				element.classList.add('w3-input');
 | 
			
		||||
				element.style = 'width: 100%; read-only: true';
 | 
			
		||||
				element.readOnly = true;
 | 
			
		||||
				event.srcElement.parentElement.appendChild(element);
 | 
			
		||||
				event.srcElement.onclick = event => handler.hide_id(event, element);
 | 
			
		||||
@@ -99,34 +69,23 @@ async function main() {
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		</script>
 | 
			
		||||
		<header class="w3-theme w3-padding"><h1>SSB Identity Management</h1></header>
 | 
			
		||||
		<div class="w3-card-4 w3-margin">
 | 
			
		||||
			<header class="w3-container w3-theme-l2"><h2>Create a new identity</h2></header>
 | 
			
		||||
			<footer class="w3-padding">
 | 
			
		||||
				<button id="create_id" onclick="handler.create_id()" class="w3-button w3-theme">Create Identity</button>
 | 
			
		||||
			</footer>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="w3-card-4 w3-margin">
 | 
			
		||||
			<header class="w3-container w3-theme-l2"><h2>Import an SSB Identity from 12 BIP39 English Words</h2></header>
 | 
			
		||||
			<textarea id="add_id" style="width: 100%" rows="4" class="w3-input"></textarea>
 | 
			
		||||
			<footer class="w3-padding">
 | 
			
		||||
				<button id="add" onclick="handler.add_id(event)" class="w3-button w3-theme">Import Identity</button>
 | 
			
		||||
			</footer>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="w3-card-4 w3-margin">
 | 
			
		||||
			<header class="w3-container w3-theme-l2"><h2>Identities</h2></header>
 | 
			
		||||
			<ul class="w3-ul">` +
 | 
			
		||||
				ids
 | 
			
		||||
					.map(
 | 
			
		||||
						(id) => `<li style="overflow: hidden; text-wrap: nowrap; text-overflow: ellipsis">
 | 
			
		||||
				<button onclick="handler.export_id(event)" data-id="${id}" class="w3-button w3-theme">Export Identity</button>
 | 
			
		||||
				<button onclick="handler.delete_id(event)" data-id="${id}" class="w3-button w3-theme">Delete Identity</button>
 | 
			
		||||
				${id}
 | 
			
		||||
			</li>`
 | 
			
		||||
					)
 | 
			
		||||
					.join('\n') +
 | 
			
		||||
				`	</ul>
 | 
			
		||||
		</div>
 | 
			
		||||
		<h1>SSB Identity Management</h1>
 | 
			
		||||
		<h2>Create a new identity</h2>
 | 
			
		||||
		<button id="create_id" onclick="handler.create_id()">Create Identity</button>
 | 
			
		||||
		<h2>Import an SSB Identity from 12 BIP39 English Words</h2>
 | 
			
		||||
		<textarea id="add_id" style="width: 100%" rows="4"></textarea><button id="add" onclick="handler.add_id(event)">Import Identity</button>
 | 
			
		||||
		<h2>Identities</h2>
 | 
			
		||||
		<ul>` +
 | 
			
		||||
			ids
 | 
			
		||||
				.map(
 | 
			
		||||
					(id) => `<li>
 | 
			
		||||
			<button onclick="handler.export_id(event)" data-id="${id}">Export Identity</button>
 | 
			
		||||
			<button onclick="handler.delete_id(event)" data-id="${id}">Delete Identity</button>
 | 
			
		||||
			${id}
 | 
			
		||||
		</li>`
 | 
			
		||||
				)
 | 
			
		||||
				.join('\n') +
 | 
			
		||||
			`	</ul>
 | 
			
		||||
	</body>`
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,235 +0,0 @@
 | 
			
		||||
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
 | 
			
		||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
 | 
			
		||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
 | 
			
		||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
 | 
			
		||||
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
 | 
			
		||||
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
 | 
			
		||||
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
 | 
			
		||||
a{background-color:transparent}a:active,a:hover{outline-width:0}
 | 
			
		||||
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
 | 
			
		||||
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
 | 
			
		||||
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
 | 
			
		||||
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
 | 
			
		||||
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
 | 
			
		||||
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
 | 
			
		||||
button,input{overflow:visible}button,select{text-transform:none}
 | 
			
		||||
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
 | 
			
		||||
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
 | 
			
		||||
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
 | 
			
		||||
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
 | 
			
		||||
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
 | 
			
		||||
[type=checkbox],[type=radio]{padding:0}
 | 
			
		||||
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
 | 
			
		||||
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
 | 
			
		||||
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
 | 
			
		||||
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
 | 
			
		||||
/* End extract */
 | 
			
		||||
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
 | 
			
		||||
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
 | 
			
		||||
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
 | 
			
		||||
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
 | 
			
		||||
hr{border:0;border-top:1px solid #eee;margin:20px 0}
 | 
			
		||||
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
 | 
			
		||||
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
 | 
			
		||||
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
 | 
			
		||||
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
 | 
			
		||||
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
 | 
			
		||||
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
 | 
			
		||||
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
 | 
			
		||||
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
 | 
			
		||||
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
 | 
			
		||||
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}   
 | 
			
		||||
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
 | 
			
		||||
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
 | 
			
		||||
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
 | 
			
		||||
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
 | 
			
		||||
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
 | 
			
		||||
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
 | 
			
		||||
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
 | 
			
		||||
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
 | 
			
		||||
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
 | 
			
		||||
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
 | 
			
		||||
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
 | 
			
		||||
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
 | 
			
		||||
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
 | 
			
		||||
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
 | 
			
		||||
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
 | 
			
		||||
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
 | 
			
		||||
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
 | 
			
		||||
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
 | 
			
		||||
.w3-main,#main{transition:margin-left .4s}
 | 
			
		||||
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
 | 
			
		||||
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
 | 
			
		||||
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
 | 
			
		||||
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
 | 
			
		||||
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
 | 
			
		||||
.w3-bar .w3-button{white-space:normal}
 | 
			
		||||
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
 | 
			
		||||
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
 | 
			
		||||
.w3-responsive{display:block;overflow-x:auto}
 | 
			
		||||
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
 | 
			
		||||
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
 | 
			
		||||
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
 | 
			
		||||
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
 | 
			
		||||
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
 | 
			
		||||
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
 | 
			
		||||
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
 | 
			
		||||
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
 | 
			
		||||
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
 | 
			
		||||
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
 | 
			
		||||
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
 | 
			
		||||
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
 | 
			
		||||
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
 | 
			
		||||
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
 | 
			
		||||
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
 | 
			
		||||
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
 | 
			
		||||
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
 | 
			
		||||
@media (max-width:1205px){.w3-auto{max-width:95%}}
 | 
			
		||||
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
 | 
			
		||||
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}	
 | 
			
		||||
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
 | 
			
		||||
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
 | 
			
		||||
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
 | 
			
		||||
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
 | 
			
		||||
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
 | 
			
		||||
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
 | 
			
		||||
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
 | 
			
		||||
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
 | 
			
		||||
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
 | 
			
		||||
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
 | 
			
		||||
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
 | 
			
		||||
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
 | 
			
		||||
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
 | 
			
		||||
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
 | 
			
		||||
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
 | 
			
		||||
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
 | 
			
		||||
.w3-display-position{position:absolute}
 | 
			
		||||
.w3-circle{border-radius:50%}
 | 
			
		||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
 | 
			
		||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
 | 
			
		||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
 | 
			
		||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
 | 
			
		||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
 | 
			
		||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
 | 
			
		||||
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
 | 
			
		||||
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
 | 
			
		||||
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
 | 
			
		||||
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
 | 
			
		||||
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
 | 
			
		||||
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
 | 
			
		||||
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
 | 
			
		||||
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
 | 
			
		||||
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
 | 
			
		||||
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
 | 
			
		||||
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
 | 
			
		||||
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
 | 
			
		||||
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
 | 
			
		||||
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
 | 
			
		||||
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
 | 
			
		||||
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
 | 
			
		||||
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
 | 
			
		||||
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
 | 
			
		||||
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
 | 
			
		||||
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
 | 
			
		||||
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
 | 
			
		||||
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
 | 
			
		||||
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
 | 
			
		||||
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
 | 
			
		||||
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
 | 
			
		||||
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
 | 
			
		||||
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
 | 
			
		||||
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
 | 
			
		||||
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
 | 
			
		||||
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
 | 
			
		||||
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
 | 
			
		||||
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
 | 
			
		||||
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
 | 
			
		||||
.w3-left{float:left!important}.w3-right{float:right!important}
 | 
			
		||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
 | 
			
		||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
 | 
			
		||||
.w3-hover-none:hover{box-shadow:none!important}
 | 
			
		||||
/* Colors */
 | 
			
		||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
 | 
			
		||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
 | 
			
		||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
 | 
			
		||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
 | 
			
		||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
 | 
			
		||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
 | 
			
		||||
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
 | 
			
		||||
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
 | 
			
		||||
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
 | 
			
		||||
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
 | 
			
		||||
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
 | 
			
		||||
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
 | 
			
		||||
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
 | 
			
		||||
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
 | 
			
		||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
 | 
			
		||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
 | 
			
		||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
 | 
			
		||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
 | 
			
		||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
 | 
			
		||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
 | 
			
		||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
 | 
			
		||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
 | 
			
		||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
 | 
			
		||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
 | 
			
		||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
 | 
			
		||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
 | 
			
		||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
 | 
			
		||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
 | 
			
		||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
 | 
			
		||||
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
 | 
			
		||||
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
 | 
			
		||||
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
 | 
			
		||||
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
 | 
			
		||||
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
 | 
			
		||||
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
 | 
			
		||||
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
 | 
			
		||||
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
 | 
			
		||||
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
 | 
			
		||||
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
 | 
			
		||||
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
 | 
			
		||||
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
 | 
			
		||||
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
 | 
			
		||||
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
 | 
			
		||||
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
 | 
			
		||||
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
 | 
			
		||||
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
 | 
			
		||||
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
 | 
			
		||||
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
 | 
			
		||||
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
 | 
			
		||||
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
 | 
			
		||||
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
 | 
			
		||||
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
 | 
			
		||||
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
 | 
			
		||||
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
 | 
			
		||||
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
 | 
			
		||||
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
 | 
			
		||||
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
 | 
			
		||||
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
 | 
			
		||||
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
 | 
			
		||||
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
 | 
			
		||||
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
 | 
			
		||||
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
 | 
			
		||||
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
 | 
			
		||||
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
 | 
			
		||||
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
 | 
			
		||||
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
 | 
			
		||||
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
 | 
			
		||||
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
 | 
			
		||||
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
 | 
			
		||||
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
 | 
			
		||||
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
 | 
			
		||||
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
 | 
			
		||||
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
 | 
			
		||||
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
 | 
			
		||||
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
 | 
			
		||||
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
 | 
			
		||||
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
 | 
			
		||||
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
 | 
			
		||||
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
 | 
			
		||||
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
 | 
			
		||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
 | 
			
		||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
 | 
			
		||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
 | 
			
		||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
	"type": "tildefriends-app",
 | 
			
		||||
	"emoji": "🦟",
 | 
			
		||||
	"previous": "&cUqvSDUls3jn0haD85LPFAGdkc8wFuy347TtATNcJgg=.sha256"
 | 
			
		||||
	"previous": "&TegdzvFE+im94shygaHkgDYSaSrwY2h0OKUXSRPBQDM=.sha256"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -85,9 +85,6 @@ tfrpc.register(async function store_message(message) {
 | 
			
		||||
tfrpc.register(function apps() {
 | 
			
		||||
	return core.apps();
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(function getActiveIdentity() {
 | 
			
		||||
	return ssb.getActiveIdentity();
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function try_decrypt(id, content) {
 | 
			
		||||
	return await ssb.privateMessageDecrypt(id, content);
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								apps/issues/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								apps/issues/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -4,6 +4,48 @@ import * as tfutils from './tf-utils.js';
 | 
			
		||||
 | 
			
		||||
const k_project = '%Hr+4xEVtjplidSKBlRWi4Aw/0Tfw7B+1OR9BzlDKmOI=.sha256';
 | 
			
		||||
 | 
			
		||||
class TfIdPickerElement extends LitElement {
 | 
			
		||||
	static get properties() {
 | 
			
		||||
		return {
 | 
			
		||||
			ids: {type: Array},
 | 
			
		||||
			selected: {type: String},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super();
 | 
			
		||||
		this.load();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async load() {
 | 
			
		||||
		this.selected = await tfrpc.rpc.localStorageGet('whoami');
 | 
			
		||||
		this.ids = (await tfrpc.rpc.getIdentities()) || [];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	changed(event) {
 | 
			
		||||
		this.selected = event.srcElement.value;
 | 
			
		||||
		tfrpc.rpc.localStorageSet('whoami', this.selected);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		if (this.ids) {
 | 
			
		||||
			return html`
 | 
			
		||||
				<select @change=${this.changed} style="max-width: 100%">
 | 
			
		||||
					${this.ids.map(
 | 
			
		||||
						(id) =>
 | 
			
		||||
							html`<option ?selected=${id == this.selected} value=${id}>
 | 
			
		||||
								${id}
 | 
			
		||||
							</option>`
 | 
			
		||||
					)}
 | 
			
		||||
				</select>
 | 
			
		||||
			`;
 | 
			
		||||
		} else {
 | 
			
		||||
			return html`<div>Loading...</div>`;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
customElements.define('tf-id-picker', TfIdPickerElement);
 | 
			
		||||
 | 
			
		||||
class TfComposeElement extends LitElement {
 | 
			
		||||
	static get properties() {
 | 
			
		||||
		return {
 | 
			
		||||
@@ -63,10 +105,10 @@ class TfIssuesAppElement extends LitElement {
 | 
			
		||||
		let issues = {};
 | 
			
		||||
		let messages = await tfrpc.rpc.query(
 | 
			
		||||
			`
 | 
			
		||||
			WITH issues AS (SELECT messages.id, json(messages.content) AS content, messages.author, messages.timestamp FROM messages_refs JOIN messages ON
 | 
			
		||||
			WITH issues AS (SELECT messages.* FROM messages_refs JOIN messages ON
 | 
			
		||||
				messages.id = messages_refs.message
 | 
			
		||||
				WHERE messages_refs.ref = ? AND json_extract(messages.content, '$.type') = 'issue'),
 | 
			
		||||
			edits AS (SELECT messages.id, json(messages.content) AS content, messages.author, messages.timestamp FROM issues JOIN messages_refs ON
 | 
			
		||||
			edits AS (SELECT messages.* FROM issues JOIN messages_refs ON
 | 
			
		||||
				issues.id = messages_refs.ref JOIN messages ON
 | 
			
		||||
				messages.id = messages_refs.message
 | 
			
		||||
				WHERE json_extract(messages.content, '$.type') IN ('issue-edit', 'post'))
 | 
			
		||||
@@ -164,7 +206,7 @@ class TfIssuesAppElement extends LitElement {
 | 
			
		||||
		if (
 | 
			
		||||
			confirm(`Are you sure you want to ${open ? 'open' : 'close'} this issue?`)
 | 
			
		||||
		) {
 | 
			
		||||
			let whoami = await tfrpc.rpc.getActiveIdentity();
 | 
			
		||||
			let whoami = this.shadowRoot.getElementById('picker').selected;
 | 
			
		||||
			await tfrpc.rpc.appendMessage(whoami, {
 | 
			
		||||
				type: 'issue-edit',
 | 
			
		||||
				issues: [
 | 
			
		||||
@@ -179,7 +221,7 @@ class TfIssuesAppElement extends LitElement {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async create_issue(event) {
 | 
			
		||||
		let whoami = await tfrpc.rpc.getActiveIdentity();
 | 
			
		||||
		let whoami = this.shadowRoot.getElementById('picker').selected;
 | 
			
		||||
		await tfrpc.rpc.appendMessage(whoami, {
 | 
			
		||||
			type: 'issue',
 | 
			
		||||
			project: k_project,
 | 
			
		||||
@@ -189,7 +231,7 @@ class TfIssuesAppElement extends LitElement {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async reply_to_issue(event) {
 | 
			
		||||
		let whoami = await tfrpc.rpc.getActiveIdentity();
 | 
			
		||||
		let whoami = this.shadowRoot.getElementById('picker').selected;
 | 
			
		||||
		await tfrpc.rpc.appendMessage(whoami, {
 | 
			
		||||
			type: 'post',
 | 
			
		||||
			text: event.detail.value,
 | 
			
		||||
@@ -207,7 +249,10 @@ class TfIssuesAppElement extends LitElement {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		let header = html` <h1>Tilde Friends Issues</h1> `;
 | 
			
		||||
		let header = html`
 | 
			
		||||
			<h1>Tilde Friends Issues</h1>
 | 
			
		||||
			<tf-id-picker id="picker"></tf-id-picker>
 | 
			
		||||
		`;
 | 
			
		||||
		if (this.selected) {
 | 
			
		||||
			return html`
 | 
			
		||||
				${header}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								apps/journal/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								apps/journal/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
	"type": "tildefriends-app",
 | 
			
		||||
	"emoji": "🚪",
 | 
			
		||||
	"previous": "&HXCdDG8gGYXElTyEFbg85jqa6lDXNL2ENPIA9UoJNbI=.sha256"
 | 
			
		||||
	"emoji": "📦",
 | 
			
		||||
	"previous": "&IU+TwyM7TznD8NBfnw7tgW2zxVlMqTVxSqWFjuosLwo=.sha256"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								apps/sneaker/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								apps/sneaker/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
	"type": "tildefriends-app",
 | 
			
		||||
	"emoji": "🐌",
 | 
			
		||||
	"previous": "&vEaOZjrNb0u9rhNqrQ8eU9TlOFlo4HsgW6hbI7VdIT0=.sha256"
 | 
			
		||||
	"previous": "&Xs1X5TzLCk6KVr+5IDc80JAHYxJyoD10cXKBUYpFqWQ=.sha256"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -100,9 +100,6 @@ tfrpc.register(async function try_decrypt(id, content) {
 | 
			
		||||
tfrpc.register(async function encrypt(id, recipients, content) {
 | 
			
		||||
	return await ssb.privateMessageEncrypt(id, recipients, content);
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function getActiveIdentity() {
 | 
			
		||||
	return await ssb.getActiveIdentity();
 | 
			
		||||
});
 | 
			
		||||
ssb.addEventListener('broadcasts', async function () {
 | 
			
		||||
	await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
 | 
			
		||||
});
 | 
			
		||||
@@ -110,9 +107,6 @@ ssb.addEventListener('broadcasts', async function () {
 | 
			
		||||
core.register('onConnectionsChanged', async function () {
 | 
			
		||||
	await tfrpc.rpc.set('connections', await ssb.connections());
 | 
			
		||||
});
 | 
			
		||||
core.register('setActiveIdentity', async function (id) {
 | 
			
		||||
	await tfrpc.rpc.set('identity', id);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
async function main() {
 | 
			
		||||
	if (typeof database !== 'undefined') {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,94 +1,90 @@
 | 
			
		||||
function textNode(text) {
 | 
			
		||||
	const node = new commonmark.Node('text', undefined);
 | 
			
		||||
	node.literal = text;
 | 
			
		||||
	return node;
 | 
			
		||||
  const node = new commonmark.Node("text", undefined);
 | 
			
		||||
  node.literal = text;
 | 
			
		||||
  return node;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function linkNode(text, link) {
 | 
			
		||||
	const linkNode = new commonmark.Node('link', undefined);
 | 
			
		||||
	if (link.startsWith('#')) {
 | 
			
		||||
		linkNode.destination = `#q=${encodeURIComponent(link)}`;
 | 
			
		||||
	} else {
 | 
			
		||||
		linkNode.destination = link;
 | 
			
		||||
	}
 | 
			
		||||
	linkNode.appendChild(textNode(text));
 | 
			
		||||
	return linkNode;
 | 
			
		||||
  const linkNode = new commonmark.Node("link", undefined);
 | 
			
		||||
  linkNode.destination = `#q=${encodeURIComponent(link)}`;
 | 
			
		||||
  linkNode.appendChild(textNode(text));
 | 
			
		||||
  return linkNode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function splitMatches(text, regexp) {
 | 
			
		||||
	// Regexp must be sticky.
 | 
			
		||||
	regexp = new RegExp(regexp, 'gm');
 | 
			
		||||
  // Regexp must be sticky.
 | 
			
		||||
  regexp = new RegExp(regexp, "gm");
 | 
			
		||||
 | 
			
		||||
	let i = 0;
 | 
			
		||||
	const result = [];
 | 
			
		||||
  let i = 0;
 | 
			
		||||
  const result = [];
 | 
			
		||||
 | 
			
		||||
	let match = regexp.exec(text);
 | 
			
		||||
	while (match) {
 | 
			
		||||
		const matchText = match[0];
 | 
			
		||||
  let match = regexp.exec(text);
 | 
			
		||||
  while (match) {
 | 
			
		||||
    const matchText = match[0];
 | 
			
		||||
 | 
			
		||||
		if (match.index > i) {
 | 
			
		||||
			result.push([text.substring(i, match.index), false]);
 | 
			
		||||
		}
 | 
			
		||||
    if (match.index > i) {
 | 
			
		||||
      result.push([text.substring(i, match.index), false]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
		result.push([matchText, true]);
 | 
			
		||||
		i = match.index + matchText.length;
 | 
			
		||||
    result.push([matchText, true]);
 | 
			
		||||
    i = match.index + matchText.length;
 | 
			
		||||
 | 
			
		||||
		match = regexp.exec(text);
 | 
			
		||||
	}
 | 
			
		||||
    match = regexp.exec(text);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
	if (i < text.length) {
 | 
			
		||||
		result.push([text.substring(i, text.length), false]);
 | 
			
		||||
	}
 | 
			
		||||
  if (i < text.length) {
 | 
			
		||||
    result.push([text.substring(i, text.length), false]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
	return result;
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const regex = new RegExp('(?:https?://[^ ]+[^ .,])|(?:(?<!\\w)#[\\w-]+)|(?:@[A-Za-z0-9+/]+=.ed25519)|(?:[%&][A-Za-z0-9+/]+=.sha256)');
 | 
			
		||||
const regex = new RegExp("(?<!\\w)#[\\w-]+");
 | 
			
		||||
 | 
			
		||||
function split(textNodes) {
 | 
			
		||||
	const text = textNodes.map((n) => n.literal).join('');
 | 
			
		||||
	const parts = splitMatches(text, regex);
 | 
			
		||||
  const text = textNodes.map(n => n.literal).join("");
 | 
			
		||||
  const parts = splitMatches(text, regex);
 | 
			
		||||
 | 
			
		||||
	return parts.map((part) => {
 | 
			
		||||
		if (part[1]) {
 | 
			
		||||
			return linkNode(part[0], part[0]);
 | 
			
		||||
		} else {
 | 
			
		||||
			return textNode(part[0]);
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
  return parts.map(part => {
 | 
			
		||||
    if (part[1]) {
 | 
			
		||||
      return linkNode(part[0], part[0]);
 | 
			
		||||
    } else {
 | 
			
		||||
      return textNode(part[0]);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function transform(parsed) {
 | 
			
		||||
	const walker = parsed.walker();
 | 
			
		||||
	let event;
 | 
			
		||||
  const walker = parsed.walker();
 | 
			
		||||
  let event;
 | 
			
		||||
 | 
			
		||||
	let nodes = [];
 | 
			
		||||
	while ((event = walker.next())) {
 | 
			
		||||
		const node = event.node;
 | 
			
		||||
		if (event.entering && node.type === 'text') {
 | 
			
		||||
			nodes.push(node);
 | 
			
		||||
		} else {
 | 
			
		||||
			if (nodes.length > 0) {
 | 
			
		||||
				split(nodes)
 | 
			
		||||
					.reverse()
 | 
			
		||||
					.forEach((newNode) => {
 | 
			
		||||
						nodes[0].insertAfter(newNode);
 | 
			
		||||
					});
 | 
			
		||||
  let nodes = [];
 | 
			
		||||
  while ((event = walker.next())) {
 | 
			
		||||
    const node = event.node;
 | 
			
		||||
    if (event.entering && node.type === "text") {
 | 
			
		||||
      nodes.push(node);
 | 
			
		||||
    } else {
 | 
			
		||||
      if (nodes.length > 0) {
 | 
			
		||||
        split(nodes)
 | 
			
		||||
          .reverse()
 | 
			
		||||
          .forEach(newNode => {
 | 
			
		||||
            nodes[0].insertAfter(newNode);
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
				nodes.forEach((n) => n.unlink());
 | 
			
		||||
				nodes = [];
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
        nodes.forEach(n => n.unlink());
 | 
			
		||||
        nodes = [];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
	if (nodes.length > 0) {
 | 
			
		||||
		split(nodes)
 | 
			
		||||
			.reverse()
 | 
			
		||||
			.forEach((newNode) => {
 | 
			
		||||
				nodes[0].insertAfter(newNode);
 | 
			
		||||
			});
 | 
			
		||||
		nodes.forEach((n) => n.unlink());
 | 
			
		||||
	}
 | 
			
		||||
  if (nodes.length > 0) {
 | 
			
		||||
    split(nodes)
 | 
			
		||||
      .reverse()
 | 
			
		||||
      .forEach(newNode => {
 | 
			
		||||
        nodes[0].insertAfter(newNode);
 | 
			
		||||
      });
 | 
			
		||||
    nodes.forEach(n => n.unlink());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
	return parsed;
 | 
			
		||||
  return parsed;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										91
									
								
								apps/ssb/commonmark-linkify.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								apps/ssb/commonmark-linkify.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
			
		||||
function textNode(text) {
 | 
			
		||||
  const node = new commonmark.Node("text", undefined);
 | 
			
		||||
  node.literal = text;
 | 
			
		||||
  return node;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function linkNode(text, url) {
 | 
			
		||||
  const urlNode = new commonmark.Node("link", undefined);
 | 
			
		||||
  urlNode.destination = url;
 | 
			
		||||
  urlNode.appendChild(textNode(text));
 | 
			
		||||
 | 
			
		||||
  return urlNode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function splitMatches(text, regexp) {
 | 
			
		||||
  // Regexp must be sticky.
 | 
			
		||||
  regexp = new RegExp(regexp, "gm");
 | 
			
		||||
 | 
			
		||||
  let i = 0;
 | 
			
		||||
  const result = [];
 | 
			
		||||
 | 
			
		||||
  let match = regexp.exec(text);
 | 
			
		||||
  while (match) {
 | 
			
		||||
    const matchText = match[0];
 | 
			
		||||
 | 
			
		||||
    if (match.index > i) {
 | 
			
		||||
      result.push([text.substring(i, match.index), false]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    result.push([matchText, true]);
 | 
			
		||||
    i = match.index + matchText.length;
 | 
			
		||||
 | 
			
		||||
    match = regexp.exec(text);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (i < text.length) {
 | 
			
		||||
    result.push([text.substring(i, text.length), false]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const urlRegexp = new RegExp("https?://[^ ]+[^ .,]");
 | 
			
		||||
 | 
			
		||||
function splitURLs(textNodes) {
 | 
			
		||||
  const text = textNodes.map(n => n.literal).join("");
 | 
			
		||||
  const parts = splitMatches(text, urlRegexp);
 | 
			
		||||
 | 
			
		||||
  return parts.map(part => {
 | 
			
		||||
    if (part[1]) {
 | 
			
		||||
      return linkNode(part[0], part[0]);
 | 
			
		||||
    } else {
 | 
			
		||||
      return textNode(part[0]);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function transform(parsed) {
 | 
			
		||||
  const walker = parsed.walker();
 | 
			
		||||
  let event;
 | 
			
		||||
 | 
			
		||||
  let nodes = [];
 | 
			
		||||
  while ((event = walker.next())) {
 | 
			
		||||
    const node = event.node;
 | 
			
		||||
    if (event.entering && node.type === "text") {
 | 
			
		||||
      nodes.push(node);
 | 
			
		||||
    } else {
 | 
			
		||||
      if (nodes.length > 0) {
 | 
			
		||||
        splitURLs(nodes)
 | 
			
		||||
          .reverse()
 | 
			
		||||
          .forEach(newNode => {
 | 
			
		||||
            nodes[0].insertAfter(newNode);
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
        nodes.forEach(n => n.unlink());
 | 
			
		||||
        nodes = [];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (nodes.length > 0) {
 | 
			
		||||
    splitURLs(nodes)
 | 
			
		||||
      .reverse()
 | 
			
		||||
      .forEach(newNode => {
 | 
			
		||||
        nodes[0].insertAfter(newNode);
 | 
			
		||||
      });
 | 
			
		||||
    nodes.forEach(n => n.unlink());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return parsed;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,3 @@
 | 
			
		||||
import * as tfrpc from '/static/tfrpc.js';
 | 
			
		||||
 | 
			
		||||
let g_emojis;
 | 
			
		||||
 | 
			
		||||
function get_emojis() {
 | 
			
		||||
@@ -12,154 +10,105 @@ function get_emojis() {
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function get_recent(author) {
 | 
			
		||||
	let recent = await tfrpc.rpc.query(
 | 
			
		||||
		`
 | 
			
		||||
		SELECT DISTINCT content ->> '$.vote.expression' AS value
 | 
			
		||||
		FROM messages
 | 
			
		||||
		WHERE author = ? AND
 | 
			
		||||
		content ->> '$.type' = 'vote'
 | 
			
		||||
		ORDER BY timestamp DESC LIMIT 10
 | 
			
		||||
	`,
 | 
			
		||||
		[author]
 | 
			
		||||
	);
 | 
			
		||||
	return recent.map((x) => x.value);
 | 
			
		||||
}
 | 
			
		||||
export function picker(callback, anchor) {
 | 
			
		||||
	get_emojis().then(function (json) {
 | 
			
		||||
		let div = document.createElement('div');
 | 
			
		||||
		div.id = 'emoji_picker';
 | 
			
		||||
		div.style.color = '#000';
 | 
			
		||||
		div.style.background = '#fff';
 | 
			
		||||
		div.style.border = '1px solid #000';
 | 
			
		||||
		div.style.display = 'block';
 | 
			
		||||
		div.style.position = 'absolute';
 | 
			
		||||
		div.style.minWidth = 'min(16em, 90vw)';
 | 
			
		||||
		div.style.width = 'min(16em, 90vw)';
 | 
			
		||||
		div.style.maxWidth = 'min(16em, 90vw)';
 | 
			
		||||
		div.style.maxHeight = '16em';
 | 
			
		||||
		div.style.overflow = 'scroll';
 | 
			
		||||
		div.style.fontWeight = 'bold';
 | 
			
		||||
		div.style.fontSize = 'xx-large';
 | 
			
		||||
		let input = document.createElement('input');
 | 
			
		||||
		input.type = 'text';
 | 
			
		||||
		input.style.display = 'block';
 | 
			
		||||
		input.style.boxSizing = 'border-box';
 | 
			
		||||
		input.style.width = '100%';
 | 
			
		||||
		input.style.margin = '0';
 | 
			
		||||
		input.style.position = 'relative';
 | 
			
		||||
		div.appendChild(input);
 | 
			
		||||
		let list = document.createElement('div');
 | 
			
		||||
		div.appendChild(list);
 | 
			
		||||
		div.addEventListener('mousedown', function (event) {
 | 
			
		||||
			event.stopPropagation();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
export async function picker(callback, anchor, author) {
 | 
			
		||||
	let json = await get_emojis();
 | 
			
		||||
	let recent = await get_recent(author);
 | 
			
		||||
		function cleanup() {
 | 
			
		||||
			console.log('emoji cleanup');
 | 
			
		||||
			div.parentElement.removeChild(div);
 | 
			
		||||
			window.removeEventListener('keydown', key_down);
 | 
			
		||||
			console.log('removing click');
 | 
			
		||||
			document.body.removeEventListener('mousedown', cleanup);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	let div = document.createElement('div');
 | 
			
		||||
	div.id = 'emoji_picker';
 | 
			
		||||
	div.style.color = '#000';
 | 
			
		||||
	div.style.background = '#fff';
 | 
			
		||||
	div.style.border = '1px solid #000';
 | 
			
		||||
	div.style.display = 'block';
 | 
			
		||||
	div.style.position = 'absolute';
 | 
			
		||||
	div.style.minWidth = 'min(16em, 90vw)';
 | 
			
		||||
	div.style.width = 'min(16em, 90vw)';
 | 
			
		||||
	div.style.maxWidth = 'min(16em, 90vw)';
 | 
			
		||||
	div.style.maxHeight = '16em';
 | 
			
		||||
	div.style.overflow = 'scroll';
 | 
			
		||||
	div.style.fontWeight = 'bold';
 | 
			
		||||
	div.style.fontSize = 'xx-large';
 | 
			
		||||
	let input = document.createElement('input');
 | 
			
		||||
	input.type = 'text';
 | 
			
		||||
	input.style.display = 'block';
 | 
			
		||||
	input.style.boxSizing = 'border-box';
 | 
			
		||||
	input.style.width = '100%';
 | 
			
		||||
	input.style.margin = '0';
 | 
			
		||||
	input.style.position = 'relative';
 | 
			
		||||
	div.appendChild(input);
 | 
			
		||||
	let list = document.createElement('div');
 | 
			
		||||
	div.appendChild(list);
 | 
			
		||||
	div.addEventListener('mousedown', function (event) {
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
	});
 | 
			
		||||
		function key_down(event) {
 | 
			
		||||
			if (event.key == 'Escape') {
 | 
			
		||||
				cleanup();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	function cleanup() {
 | 
			
		||||
		console.log('emoji cleanup');
 | 
			
		||||
		div.parentElement.removeChild(div);
 | 
			
		||||
		window.removeEventListener('keydown', key_down);
 | 
			
		||||
		console.log('removing click');
 | 
			
		||||
		document.body.removeEventListener('mousedown', cleanup);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function key_down(event) {
 | 
			
		||||
		if (event.key == 'Escape') {
 | 
			
		||||
		function chosen(event) {
 | 
			
		||||
			console.log(event.srcElement.innerText);
 | 
			
		||||
			callback(event.srcElement.innerText);
 | 
			
		||||
			cleanup();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function chosen(event) {
 | 
			
		||||
		console.log(event.srcElement.innerText);
 | 
			
		||||
		callback(event.srcElement.innerText);
 | 
			
		||||
		cleanup();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function refresh() {
 | 
			
		||||
		while (list.firstChild) {
 | 
			
		||||
			list.removeChild(list.firstChild);
 | 
			
		||||
		}
 | 
			
		||||
		let search = input.value.toLowerCase();
 | 
			
		||||
		let any_at_all = false;
 | 
			
		||||
		if (recent) {
 | 
			
		||||
			let emoji_to_name = {};
 | 
			
		||||
			for (let row of Object.values(json)) {
 | 
			
		||||
				for (let entry of Object.entries(row)) {
 | 
			
		||||
					emoji_to_name[entry[1]] = entry[0];
 | 
			
		||||
		function refresh() {
 | 
			
		||||
			while (list.firstChild) {
 | 
			
		||||
				list.removeChild(list.firstChild);
 | 
			
		||||
			}
 | 
			
		||||
			let search = input.value.toLowerCase();
 | 
			
		||||
			let any_at_all = false;
 | 
			
		||||
			for (let row of Object.entries(json)) {
 | 
			
		||||
				let header = document.createElement('div');
 | 
			
		||||
				header.appendChild(document.createTextNode(row[0]));
 | 
			
		||||
				list.appendChild(header);
 | 
			
		||||
				let any = false;
 | 
			
		||||
				for (let entry of Object.entries(row[1])) {
 | 
			
		||||
					if (
 | 
			
		||||
						search &&
 | 
			
		||||
						search.length &&
 | 
			
		||||
						entry[0].toLowerCase().indexOf(search) == -1
 | 
			
		||||
					) {
 | 
			
		||||
						continue;
 | 
			
		||||
					}
 | 
			
		||||
					let emoji = document.createElement('span');
 | 
			
		||||
					const k_size = '1.25em';
 | 
			
		||||
					emoji.style.display = 'inline-block';
 | 
			
		||||
					emoji.style.overflow = 'hidden';
 | 
			
		||||
					emoji.style.cursor = 'pointer';
 | 
			
		||||
					emoji.onclick = chosen;
 | 
			
		||||
					emoji.title = entry[0];
 | 
			
		||||
					emoji.appendChild(document.createTextNode(entry[1]));
 | 
			
		||||
					list.appendChild(emoji);
 | 
			
		||||
					any = true;
 | 
			
		||||
					any_at_all = true;
 | 
			
		||||
				}
 | 
			
		||||
				if (!any) {
 | 
			
		||||
					list.removeChild(header);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			let header = document.createElement('div');
 | 
			
		||||
			header.appendChild(document.createTextNode('Recent'));
 | 
			
		||||
			list.appendChild(header);
 | 
			
		||||
			let any = false;
 | 
			
		||||
			for (let entry of recent) {
 | 
			
		||||
				if (
 | 
			
		||||
					search &&
 | 
			
		||||
					search.length &&
 | 
			
		||||
					(emoji_to_name[entry] || '').toLowerCase().indexOf(search) == -1
 | 
			
		||||
				) {
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
				let emoji = document.createElement('span');
 | 
			
		||||
				const k_size = '1.25em';
 | 
			
		||||
				emoji.style.display = 'inline-block';
 | 
			
		||||
				emoji.style.overflow = 'hidden';
 | 
			
		||||
				emoji.style.cursor = 'pointer';
 | 
			
		||||
				emoji.onclick = chosen;
 | 
			
		||||
				emoji.title = emoji_to_name[entry] || entry;
 | 
			
		||||
				emoji.appendChild(document.createTextNode(entry));
 | 
			
		||||
				list.appendChild(emoji);
 | 
			
		||||
				any = true;
 | 
			
		||||
			}
 | 
			
		||||
			if (!any) {
 | 
			
		||||
				list.removeChild(header);
 | 
			
		||||
			if (!any_at_all) {
 | 
			
		||||
				list.appendChild(document.createTextNode('No matches found.'));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for (let row of Object.entries(json)) {
 | 
			
		||||
			let header = document.createElement('div');
 | 
			
		||||
			header.appendChild(document.createTextNode(row[0]));
 | 
			
		||||
			list.appendChild(header);
 | 
			
		||||
			let any = false;
 | 
			
		||||
			for (let entry of Object.entries(row[1])) {
 | 
			
		||||
				if (
 | 
			
		||||
					search &&
 | 
			
		||||
					search.length &&
 | 
			
		||||
					entry[0].toLowerCase().indexOf(search) == -1
 | 
			
		||||
				) {
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
				let emoji = document.createElement('span');
 | 
			
		||||
				const k_size = '1.25em';
 | 
			
		||||
				emoji.style.display = 'inline-block';
 | 
			
		||||
				emoji.style.overflow = 'hidden';
 | 
			
		||||
				emoji.style.cursor = 'pointer';
 | 
			
		||||
				emoji.onclick = chosen;
 | 
			
		||||
				emoji.title = entry[0];
 | 
			
		||||
				emoji.appendChild(document.createTextNode(entry[1]));
 | 
			
		||||
				list.appendChild(emoji);
 | 
			
		||||
				any = true;
 | 
			
		||||
				any_at_all = true;
 | 
			
		||||
			}
 | 
			
		||||
			if (!any) {
 | 
			
		||||
				list.removeChild(header);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (!any_at_all) {
 | 
			
		||||
			list.appendChild(document.createTextNode('No matches found.'));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	refresh();
 | 
			
		||||
	input.oninput = refresh;
 | 
			
		||||
	document.body.appendChild(div);
 | 
			
		||||
	div.style.position = 'fixed';
 | 
			
		||||
	div.style.top = '50%';
 | 
			
		||||
	div.style.left = '50%';
 | 
			
		||||
	div.style.transform = 'translate(-50%, -50%)';
 | 
			
		||||
	input.focus();
 | 
			
		||||
	console.log('adding click');
 | 
			
		||||
	document.body.addEventListener('mousedown', cleanup);
 | 
			
		||||
	window.addEventListener('keydown', key_down);
 | 
			
		||||
		refresh();
 | 
			
		||||
		input.oninput = refresh;
 | 
			
		||||
		document.body.appendChild(div);
 | 
			
		||||
		div.style.position = 'fixed';
 | 
			
		||||
		div.style.top = '50%';
 | 
			
		||||
		div.style.left = '50%';
 | 
			
		||||
		div.style.transform = 'translate(-50%, -50%)';
 | 
			
		||||
		input.focus();
 | 
			
		||||
		console.log('adding click');
 | 
			
		||||
		document.body.addEventListener('mousedown', cleanup);
 | 
			
		||||
		window.addEventListener('keydown', key_down);
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html>
 | 
			
		||||
<html style="color: #fff">
 | 
			
		||||
	<head>
 | 
			
		||||
		<title>Tilde Friends</title>
 | 
			
		||||
		<base target="_top" />
 | 
			
		||||
@@ -10,14 +10,14 @@
 | 
			
		||||
			}
 | 
			
		||||
		</style>
 | 
			
		||||
	</head>
 | 
			
		||||
	<body style="margin: 0; padding: 0">
 | 
			
		||||
		<tf-app></tf-app>
 | 
			
		||||
		<tf-reactions-modal id="reactions_modal"></tf-reactions-modal>
 | 
			
		||||
	<body style="background-color: #223a5e">
 | 
			
		||||
		<tf-app class="w3-deep-purple" />
 | 
			
		||||
		<script>
 | 
			
		||||
			window.litDisableBundleWarning = true;
 | 
			
		||||
		</script>
 | 
			
		||||
		<script src="filesaver.min.js"></script>
 | 
			
		||||
		<script src="commonmark.min.js"></script>
 | 
			
		||||
		<script src="commonmark-linkify.js" type="module"></script>
 | 
			
		||||
		<script src="commonmark-hashtag.js" type="module"></script>
 | 
			
		||||
		<script src="script.js" type="module"></script>
 | 
			
		||||
	</body>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								apps/ssb/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								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
											
										
									
								
							@@ -1,13 +1,13 @@
 | 
			
		||||
import {LitElement, html} from './lit-all.min.js';
 | 
			
		||||
import * as tfrpc from '/static/tfrpc.js';
 | 
			
		||||
 | 
			
		||||
import * as tf_id_picker from './tf-id-picker.js';
 | 
			
		||||
import * as tf_app from './tf-app.js';
 | 
			
		||||
import * as tf_message from './tf-message.js';
 | 
			
		||||
import * as tf_user from './tf-user.js';
 | 
			
		||||
import * as tf_compose from './tf-compose.js';
 | 
			
		||||
import * as tf_news from './tf-news.js';
 | 
			
		||||
import * as tf_profile from './tf-profile.js';
 | 
			
		||||
import * as tf_reactions_modal from './tf-reactions-modal.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_feed from './tf-tab-news-feed.js';
 | 
			
		||||
 
 | 
			
		||||
@@ -52,15 +52,13 @@ class TfElement extends LitElement {
 | 
			
		||||
				self.broadcasts = value;
 | 
			
		||||
			} else if (name === 'connections') {
 | 
			
		||||
				self.connections = value;
 | 
			
		||||
			} else if (name === 'identity') {
 | 
			
		||||
				self.whoami = value;
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		this.initial_load();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async initial_load() {
 | 
			
		||||
		let whoami = await tfrpc.rpc.getActiveIdentity();
 | 
			
		||||
		let whoami = await tfrpc.rpc.localStorageGet('whoami');
 | 
			
		||||
		let ids = (await tfrpc.rpc.getIdentities()) || [];
 | 
			
		||||
		this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
 | 
			
		||||
		this.ids = ids;
 | 
			
		||||
@@ -195,6 +193,29 @@ class TfElement extends LitElement {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render_id_picker() {
 | 
			
		||||
		return html`
 | 
			
		||||
			<div style="display: flex; gap: 8px">
 | 
			
		||||
				<tf-id-picker
 | 
			
		||||
					id="picker"
 | 
			
		||||
					style="flex: 1 1 auto"
 | 
			
		||||
					selected=${this.whoami}
 | 
			
		||||
					.ids=${this.ids}
 | 
			
		||||
					.users=${this.users}
 | 
			
		||||
					@change=${this._handle_whoami_changed}
 | 
			
		||||
				></tf-id-picker>
 | 
			
		||||
				<button
 | 
			
		||||
					class="w3-button w3-dark-grey w3-border"
 | 
			
		||||
					style="flex: 0 0 auto"
 | 
			
		||||
					@click=${this.create_identity}
 | 
			
		||||
					id="create_identity"
 | 
			
		||||
				>
 | 
			
		||||
					Create Identity
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
		`;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async load_recent_tags() {
 | 
			
		||||
		let start = new Date();
 | 
			
		||||
		this.tags = await tfrpc.rpc.query(
 | 
			
		||||
@@ -234,15 +255,7 @@ class TfElement extends LitElement {
 | 
			
		||||
			by_count.push({count: v.of, id: id});
 | 
			
		||||
		}
 | 
			
		||||
		console.log(by_count.sort((x, y) => y.count - x.count).slice(0, 20));
 | 
			
		||||
		let start_time = new Date();
 | 
			
		||||
		users = await this.fetch_about(Object.keys(following).sort(), users);
 | 
			
		||||
		console.log(
 | 
			
		||||
			'about took',
 | 
			
		||||
			(new Date() - start_time) / 1000.0,
 | 
			
		||||
			'seconds for',
 | 
			
		||||
			Object.keys(users).length,
 | 
			
		||||
			'users'
 | 
			
		||||
		);
 | 
			
		||||
		this.following = Object.keys(following);
 | 
			
		||||
		this.users = users;
 | 
			
		||||
		await tags;
 | 
			
		||||
@@ -339,15 +352,15 @@ class TfElement extends LitElement {
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		let tabs = html`
 | 
			
		||||
			<div class="w3-bar w3-theme-l1">
 | 
			
		||||
			<div class="w3-bar w3-black">
 | 
			
		||||
				${Object.entries(k_tabs).map(
 | 
			
		||||
					([k, v]) => html`
 | 
			
		||||
						<button
 | 
			
		||||
							title=${v}
 | 
			
		||||
							class="w3-bar-item w3-padding-large w3-hover-theme tab ${self.tab ==
 | 
			
		||||
							class="w3-bar-item w3-padding-large w3-hover-gray tab ${self.tab ==
 | 
			
		||||
							v
 | 
			
		||||
								? 'w3-theme-l2'
 | 
			
		||||
								: 'w3-theme-l1'}"
 | 
			
		||||
								? 'w3-red'
 | 
			
		||||
								: 'w3-black'}"
 | 
			
		||||
							@click=${() => self.set_tab(v)}
 | 
			
		||||
						>
 | 
			
		||||
							${k}
 | 
			
		||||
@@ -358,25 +371,15 @@ class TfElement extends LitElement {
 | 
			
		||||
		`;
 | 
			
		||||
		let contents = !this.loaded
 | 
			
		||||
			? this.loading
 | 
			
		||||
				? html`<div class="w3-panel w3-theme-l5 w3-card-4 w3-padding-large w3-round-xlarge">
 | 
			
		||||
					Loading...
 | 
			
		||||
				</div>
 | 
			
		||||
				${this.render_tab()}`
 | 
			
		||||
				? html`<div>Loading...</div>`
 | 
			
		||||
				: html`<div>Select or create an identity.</div>`
 | 
			
		||||
			: this.render_tab();
 | 
			
		||||
		return html`
 | 
			
		||||
			<div
 | 
			
		||||
				style="width: 100vw; min-height: 100vh; height: 100%"
 | 
			
		||||
				class="w3-theme-dark"
 | 
			
		||||
			>
 | 
			
		||||
				${tabs}
 | 
			
		||||
				<div style="padding: 8px">
 | 
			
		||||
					${this.tags.map(
 | 
			
		||||
						(x) => html`<tf-tag tag=${x.tag} count=${x.count}></tf-tag>`
 | 
			
		||||
					)}
 | 
			
		||||
					${contents}
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			${this.render_id_picker()} ${tabs}
 | 
			
		||||
			${this.tags.map(
 | 
			
		||||
				(x) => html`<tf-tag tag=${x.tag} count=${x.count}></tf-tag>`
 | 
			
		||||
			)}
 | 
			
		||||
			${contents}
 | 
			
		||||
		`;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import {LitElement, html, unsafeHTML, live} from './lit-all.min.js';
 | 
			
		||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
 | 
			
		||||
import * as tfutils from './tf-utils.js';
 | 
			
		||||
import * as tfrpc from '/static/tfrpc.js';
 | 
			
		||||
import {styles} from './tf-styles.js';
 | 
			
		||||
@@ -13,7 +13,6 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
			branch: {type: String},
 | 
			
		||||
			apps: {type: Object},
 | 
			
		||||
			drafts: {type: Object},
 | 
			
		||||
			author: {type: String},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -26,7 +25,6 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
		this.branch = undefined;
 | 
			
		||||
		this.apps = undefined;
 | 
			
		||||
		this.drafts = {};
 | 
			
		||||
		this.author = undefined;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	process_text(text) {
 | 
			
		||||
@@ -66,7 +64,7 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
			updated = true;
 | 
			
		||||
		}
 | 
			
		||||
		if (updated) {
 | 
			
		||||
			setTimeout(() => this.notify(draft), 0);
 | 
			
		||||
			this.requestUpdate();
 | 
			
		||||
		}
 | 
			
		||||
		return tfutils.markdown(text);
 | 
			
		||||
	}
 | 
			
		||||
@@ -74,7 +72,7 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
	input(event) {
 | 
			
		||||
		let edit = this.renderRoot.getElementById('edit');
 | 
			
		||||
		let preview = this.renderRoot.getElementById('preview');
 | 
			
		||||
		preview.innerHTML = this.process_text(edit.innerText);
 | 
			
		||||
		preview.innerHTML = this.process_text(edit.value);
 | 
			
		||||
		let content_warning = this.renderRoot.getElementById('content_warning');
 | 
			
		||||
		let content_warning_preview = this.renderRoot.getElementById(
 | 
			
		||||
			'content_warning_preview'
 | 
			
		||||
@@ -82,10 +80,6 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
		if (content_warning && content_warning_preview) {
 | 
			
		||||
			content_warning_preview.innerText = content_warning.value;
 | 
			
		||||
		}
 | 
			
		||||
		let draft = this.get_draft();
 | 
			
		||||
		draft.text = edit.innerText;
 | 
			
		||||
		draft.content_warning = content_warning?.innerText;
 | 
			
		||||
		setTimeout(() => this.notify(draft), 0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	notify(draft) {
 | 
			
		||||
@@ -101,6 +95,14 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	change() {
 | 
			
		||||
		let draft = this.get_draft();
 | 
			
		||||
		draft.text = this.renderRoot.getElementById('edit')?.value;
 | 
			
		||||
		draft.content_warning =
 | 
			
		||||
			this.renderRoot.getElementById('content_warning')?.value;
 | 
			
		||||
		this.notify(draft);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	convert_to_format(buffer, type, mime_type) {
 | 
			
		||||
		return new Promise(function (resolve, reject) {
 | 
			
		||||
			let img = new Image();
 | 
			
		||||
@@ -167,7 +169,8 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
				size: buffer.length ?? buffer.byteLength,
 | 
			
		||||
			};
 | 
			
		||||
			let edit = self.renderRoot.getElementById('edit');
 | 
			
		||||
			edit.innerText += `\n`;
 | 
			
		||||
			edit.value += `\n`;
 | 
			
		||||
			self.change();
 | 
			
		||||
			self.input();
 | 
			
		||||
		} catch (e) {
 | 
			
		||||
			alert(e?.message);
 | 
			
		||||
@@ -194,7 +197,7 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
		let edit = this.renderRoot.getElementById('edit');
 | 
			
		||||
		let message = {
 | 
			
		||||
			type: 'post',
 | 
			
		||||
			text: edit.innerText,
 | 
			
		||||
			text: edit.value,
 | 
			
		||||
		};
 | 
			
		||||
		if (this.root || this.branch) {
 | 
			
		||||
			message.root = this.root;
 | 
			
		||||
@@ -222,8 +225,8 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
		}
 | 
			
		||||
		try {
 | 
			
		||||
			await tfrpc.rpc.appendMessage(this.whoami, message).then(function () {
 | 
			
		||||
				edit.innerText = '';
 | 
			
		||||
				self.input();
 | 
			
		||||
				edit.value = '';
 | 
			
		||||
				self.change();
 | 
			
		||||
				self.notify(undefined);
 | 
			
		||||
				self.requestUpdate();
 | 
			
		||||
			});
 | 
			
		||||
@@ -233,11 +236,17 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	discard() {
 | 
			
		||||
		let edit = this.renderRoot.getElementById('edit');
 | 
			
		||||
		edit.value = '';
 | 
			
		||||
		this.change();
 | 
			
		||||
		let preview = this.renderRoot.getElementById('preview');
 | 
			
		||||
		preview.innerHTML = '';
 | 
			
		||||
		this.notify(undefined);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	attach() {
 | 
			
		||||
		let self = this;
 | 
			
		||||
		let edit = this.renderRoot.getElementById('edit');
 | 
			
		||||
		let input = document.createElement('input');
 | 
			
		||||
		input.type = 'file';
 | 
			
		||||
		input.onchange = function (event) {
 | 
			
		||||
@@ -275,34 +284,22 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	firstUpdated() {
 | 
			
		||||
		let values = Object.entries(this.users).map((x) => ({
 | 
			
		||||
			key: x[1].name ?? x[0],
 | 
			
		||||
			value: x[0],
 | 
			
		||||
		}));
 | 
			
		||||
		if (this.author) {
 | 
			
		||||
			values = [].concat(
 | 
			
		||||
				[
 | 
			
		||||
					{
 | 
			
		||||
						key: this.users[this.author]?.name,
 | 
			
		||||
						value: this.author,
 | 
			
		||||
					},
 | 
			
		||||
				],
 | 
			
		||||
				values
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
		let tribute = new Tribute({
 | 
			
		||||
			collection: [
 | 
			
		||||
				{
 | 
			
		||||
					values: values,
 | 
			
		||||
					values: Object.entries(this.users).map((x) => ({
 | 
			
		||||
						key: x[1].name,
 | 
			
		||||
						value: x[0],
 | 
			
		||||
					})),
 | 
			
		||||
					selectTemplate: function (item) {
 | 
			
		||||
						return item ? `[@${item.original.key}](${item.original.value})` : undefined;
 | 
			
		||||
						return `[@${item.original.key}](${item.original.value})`;
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					trigger: '&',
 | 
			
		||||
					values: this.autocomplete,
 | 
			
		||||
					selectTemplate: function (item) {
 | 
			
		||||
						return item ? `` : undefined;
 | 
			
		||||
						return ``;
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			],
 | 
			
		||||
@@ -313,10 +310,10 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
	updated() {
 | 
			
		||||
		super.updated();
 | 
			
		||||
		let edit = this.renderRoot.getElementById('edit');
 | 
			
		||||
		if (this.last_updated_text !== edit.innerText) {
 | 
			
		||||
		if (this.last_updated_text !== edit.value) {
 | 
			
		||||
			let preview = this.renderRoot.getElementById('preview');
 | 
			
		||||
			preview.innerHTML = this.process_text(edit.innerText);
 | 
			
		||||
			this.last_updated_text = edit.innerText;
 | 
			
		||||
			preview.innerHTML = this.process_text(edit.value);
 | 
			
		||||
			this.last_updated_text = edit.value;
 | 
			
		||||
		}
 | 
			
		||||
		let encrypt = this.renderRoot.getElementById('encrypt_to');
 | 
			
		||||
		if (encrypt) {
 | 
			
		||||
@@ -336,7 +333,8 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
	remove_mention(id) {
 | 
			
		||||
		let draft = this.get_draft();
 | 
			
		||||
		delete draft.mentions[id];
 | 
			
		||||
		setTimeout(() => this.notify(), 0);
 | 
			
		||||
		this.notify(draft);
 | 
			
		||||
		this.requestUpdate();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render_mention(mention) {
 | 
			
		||||
@@ -344,7 +342,7 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
		return html` <div style="display: flex; flex-direction: row">
 | 
			
		||||
			<div style="align-self: center; margin: 0.5em">
 | 
			
		||||
				<button
 | 
			
		||||
					class="w3-button w3-theme-d1"
 | 
			
		||||
					class="w3-button w3-dark-grey"
 | 
			
		||||
					title="Remove ${mention.name} mention"
 | 
			
		||||
					@click=${() => self.remove_mention(mention.link)}
 | 
			
		||||
				>
 | 
			
		||||
@@ -398,16 +396,16 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
		if (this.apps) {
 | 
			
		||||
			return html`
 | 
			
		||||
				<div class="w3-card-4 w3-margin w3-padding">
 | 
			
		||||
					<select id="select" class="w3-select w3-theme-d1">
 | 
			
		||||
					<select id="select" class="w3-select w3-dark-grey">
 | 
			
		||||
						${Object.keys(self.apps).map(
 | 
			
		||||
							(app) => html`<option value=${app}>${app}</option>`
 | 
			
		||||
						)}
 | 
			
		||||
					</select>
 | 
			
		||||
					<button class="w3-button w3-theme-d1" @click=${attach_selected_app}>
 | 
			
		||||
					<button class="w3-button w3-dark-grey" @click=${attach_selected_app}>
 | 
			
		||||
						Attach
 | 
			
		||||
					</button>
 | 
			
		||||
					<button
 | 
			
		||||
						class="w3-button w3-theme-d1"
 | 
			
		||||
						class="w3-button w3-dark-grey"
 | 
			
		||||
						@click=${() => (this.apps = null)}
 | 
			
		||||
					>
 | 
			
		||||
						Cancel
 | 
			
		||||
@@ -423,12 +421,12 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
			self.apps = await tfrpc.rpc.apps();
 | 
			
		||||
		}
 | 
			
		||||
		if (!this.apps) {
 | 
			
		||||
			return html`<button class="w3-button w3-theme-d1" @click=${attach_app}>
 | 
			
		||||
			return html`<button class="w3-button w3-dark-grey" @click=${attach_app}>
 | 
			
		||||
				Attach App
 | 
			
		||||
			</button>`;
 | 
			
		||||
		} else {
 | 
			
		||||
			return html`<button
 | 
			
		||||
				class="w3-button w3-theme-d1"
 | 
			
		||||
				class="w3-button w3-dark-grey"
 | 
			
		||||
				@click=${() => (this.apps = null)}
 | 
			
		||||
			>
 | 
			
		||||
				Discard App
 | 
			
		||||
@@ -450,15 +448,15 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
			return html`
 | 
			
		||||
				<div class="w3-container w3-padding">
 | 
			
		||||
					<p>
 | 
			
		||||
						<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning(undefined)} checked="checked"></input>
 | 
			
		||||
						<input type="checkbox" class="w3-check w3-dark-grey" id="cw" @change=${() => self.set_content_warning(undefined)} checked="checked"></input>
 | 
			
		||||
						<label for="cw">CW</label>
 | 
			
		||||
					</p>
 | 
			
		||||
					<input type="text" class="w3-input w3-border w3-theme-d1" id="content_warning" placeholder="Enter a content warning here." @input=${this.input} @change=${this.change} value=${draft.content_warning}></input>
 | 
			
		||||
					<input type="text" class="w3-input w3-border w3-dark-grey" id="content_warning" placeholder="Enter a content warning here." @input=${this.input} @change=${this.change} value=${draft.content_warning}></input>
 | 
			
		||||
				</div>
 | 
			
		||||
			`;
 | 
			
		||||
		} else {
 | 
			
		||||
			return html`
 | 
			
		||||
				<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning('')}></input>
 | 
			
		||||
				<input type="checkbox" class="w3-check w3-dark-grey" id="cw" @change=${() => self.set_content_warning('')}></input>
 | 
			
		||||
				<label for="cw">CW</label>
 | 
			
		||||
			`;
 | 
			
		||||
		}
 | 
			
		||||
@@ -488,14 +486,14 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
			<div style="display: flex; flex-direction: row; width: 100%">
 | 
			
		||||
				<label for="encrypt_to">🔐 To:</label>
 | 
			
		||||
				<input type="text" id="encrypt_to" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input>
 | 
			
		||||
				<button class="w3-button w3-theme-d1" @click=${() => this.set_encrypt(undefined)}>🚮</button>
 | 
			
		||||
				<button class="w3-button w3-dark-grey" @click=${() => this.set_encrypt(undefined)}>🚮</button>
 | 
			
		||||
			</div>
 | 
			
		||||
			<ul>
 | 
			
		||||
				${draft.encrypt_to.map(
 | 
			
		||||
					(x) => html`
 | 
			
		||||
					<li>
 | 
			
		||||
						<tf-user id=${x} .users=${this.users}></tf-user>
 | 
			
		||||
						<input type="button" class="w3-button w3-theme-d1" value="🚮" @click=${() => this.set_encrypt(draft.encrypt_to.filter((id) => id != x))}></input>
 | 
			
		||||
						<input type="button" class="w3-button w3-dark-grey" value="🚮" @click=${() => this.set_encrypt(draft.encrypt_to.filter((id) => id != x))}></input>
 | 
			
		||||
					</li>`
 | 
			
		||||
				)}
 | 
			
		||||
			</ul>
 | 
			
		||||
@@ -514,7 +512,7 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
		let draft = self.get_draft();
 | 
			
		||||
		let content_warning =
 | 
			
		||||
			draft.content_warning !== undefined
 | 
			
		||||
				? html`<div class="w3-panel w3-round-xlarge w3-theme-d2">
 | 
			
		||||
				? html`<div class="w3-panel w3-round-xlarge w3-blue">
 | 
			
		||||
						<p id="content_warning_preview">${draft.content_warning}</p>
 | 
			
		||||
					</div>`
 | 
			
		||||
				: undefined;
 | 
			
		||||
@@ -522,31 +520,34 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
			draft.encrypt_to !== undefined
 | 
			
		||||
				? undefined
 | 
			
		||||
				: html`<button
 | 
			
		||||
						class="w3-button w3-theme-d1"
 | 
			
		||||
						class="w3-button w3-dark-grey"
 | 
			
		||||
						@click=${() => this.set_encrypt([])}
 | 
			
		||||
					>
 | 
			
		||||
						🔐
 | 
			
		||||
					</button>`;
 | 
			
		||||
		let result = html`
 | 
			
		||||
			<div
 | 
			
		||||
				class="w3-card-4 w3-theme-d4 w3-padding-small"
 | 
			
		||||
				class="w3-card-4 w3-blue-grey w3-padding"
 | 
			
		||||
				style="box-sizing: border-box"
 | 
			
		||||
			>
 | 
			
		||||
				${this.render_encrypt()}
 | 
			
		||||
				<div class="w3-container w3-padding-small">
 | 
			
		||||
					<div class="w3-half">
 | 
			
		||||
						<span
 | 
			
		||||
							class="w3-input w3-theme-d1 w3-border"
 | 
			
		||||
							style="resize: vertical; width: 100%; overflow: hidden; white-space: pre-wrap"
 | 
			
		||||
							placeholder="Write a post here."
 | 
			
		||||
							id="edit"
 | 
			
		||||
							@input=${this.input}
 | 
			
		||||
							@paste=${this.paste}
 | 
			
		||||
							contenteditable
 | 
			
		||||
							.innerText=${live(draft.text ?? '')}
 | 
			
		||||
							></span>
 | 
			
		||||
				<div style="display: flex; flex-direction: row; width: 100%; gap: 4px">
 | 
			
		||||
					<div style="flex: 1 0 50%">
 | 
			
		||||
						<p>
 | 
			
		||||
							<textarea
 | 
			
		||||
								class="w3-input w3-dark-grey w3-border"
 | 
			
		||||
								style="resize: vertical"
 | 
			
		||||
								placeholder="Write a post here."
 | 
			
		||||
								id="edit"
 | 
			
		||||
								@input=${this.input}
 | 
			
		||||
								@change=${this.change}
 | 
			
		||||
								@paste=${this.paste}
 | 
			
		||||
							>
 | 
			
		||||
${draft.text}</textarea
 | 
			
		||||
							>
 | 
			
		||||
						</p>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="w3-half w3-padding">
 | 
			
		||||
					<div style="flex: 1 0 50%">
 | 
			
		||||
						${content_warning}
 | 
			
		||||
						<div id="preview"></div>
 | 
			
		||||
					</div>
 | 
			
		||||
@@ -555,14 +556,18 @@ class TfComposeElement extends LitElement {
 | 
			
		||||
					self.render_mention(x)
 | 
			
		||||
				)}
 | 
			
		||||
				${this.render_attach_app()} ${this.render_content_warning()}
 | 
			
		||||
				<button class="w3-button w3-theme-d1" id="submit" @click=${this.submit}>
 | 
			
		||||
				<button
 | 
			
		||||
					class="w3-button w3-dark-grey"
 | 
			
		||||
					id="submit"
 | 
			
		||||
					@click=${this.submit}
 | 
			
		||||
				>
 | 
			
		||||
					Submit
 | 
			
		||||
				</button>
 | 
			
		||||
				<button class="w3-button w3-theme-d1" @click=${this.attach}>
 | 
			
		||||
				<button class="w3-button w3-dark-grey" @click=${this.attach}>
 | 
			
		||||
					Attach
 | 
			
		||||
				</button>
 | 
			
		||||
				${this.render_attach_app_button()} ${encrypt}
 | 
			
		||||
				<button class="w3-button w3-theme-d1" @click=${this.discard}>
 | 
			
		||||
				<button class="w3-button w3-dark-grey" @click=${this.discard}>
 | 
			
		||||
					Discard
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										54
									
								
								apps/ssb/tf-id-picker.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								apps/ssb/tf-id-picker.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
import {LitElement, html} from './lit-all.min.js';
 | 
			
		||||
import * as tfrpc from '/static/tfrpc.js';
 | 
			
		||||
import {styles} from './tf-styles.js';
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 ** Provide a list of IDs, and this lets the user pick one.
 | 
			
		||||
 */
 | 
			
		||||
class TfIdentityPickerElement extends LitElement {
 | 
			
		||||
	static get properties() {
 | 
			
		||||
		return {
 | 
			
		||||
			ids: {type: Array},
 | 
			
		||||
			selected: {type: String},
 | 
			
		||||
			users: {type: Object},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static styles = styles;
 | 
			
		||||
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super();
 | 
			
		||||
		this.ids = [];
 | 
			
		||||
		this.users = {};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	changed(event) {
 | 
			
		||||
		this.selected = event.srcElement.value;
 | 
			
		||||
		this.dispatchEvent(
 | 
			
		||||
			new Event('change', {
 | 
			
		||||
				srcElement: this,
 | 
			
		||||
			})
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		return html`
 | 
			
		||||
			<select
 | 
			
		||||
				class="w3-select w3-dark-grey w3-padding w3-border"
 | 
			
		||||
				@change=${this.changed}
 | 
			
		||||
				style="max-width: 100%; overflow: hidden"
 | 
			
		||||
			>
 | 
			
		||||
				${(this.ids ?? []).map(
 | 
			
		||||
					(id) =>
 | 
			
		||||
						html`<option ?selected=${id == this.selected} value=${id}>
 | 
			
		||||
							${this.users[id]?.name
 | 
			
		||||
								? this.users[id]?.name + ' - '
 | 
			
		||||
								: undefined}<small>${id}</small>
 | 
			
		||||
						</option>`
 | 
			
		||||
				)}
 | 
			
		||||
			</select>
 | 
			
		||||
		`;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define('tf-id-picker', TfIdentityPickerElement);
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import {LitElement, html, render, unsafeHTML} from './lit-all.min.js';
 | 
			
		||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
 | 
			
		||||
import * as tfrpc from '/static/tfrpc.js';
 | 
			
		||||
import * as tfutils from './tf-utils.js';
 | 
			
		||||
import * as emojis from './emojis.js';
 | 
			
		||||
@@ -54,12 +54,6 @@ class TfMessageElement extends LitElement {
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	show_reactions() {
 | 
			
		||||
		let modal = document.getElementById('reactions_modal');
 | 
			
		||||
		modal.users = this.users;
 | 
			
		||||
		modal.votes = this.message?.votes || [];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render_votes() {
 | 
			
		||||
		function normalize_expression(expression) {
 | 
			
		||||
			if (expression === 'Like' || !expression) {
 | 
			
		||||
@@ -72,21 +66,19 @@ class TfMessageElement extends LitElement {
 | 
			
		||||
				return expression;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (this.message?.votes?.length) {
 | 
			
		||||
			return html`<div class="w3-button" @click=${this.show_reactions}>
 | 
			
		||||
				${(this.message.votes || []).map(
 | 
			
		||||
					(vote) => html`
 | 
			
		||||
						<span
 | 
			
		||||
							title="${this.users[vote.author]?.name ?? vote.author} ${new Date(
 | 
			
		||||
								vote.timestamp
 | 
			
		||||
							)}"
 | 
			
		||||
						>
 | 
			
		||||
							${normalize_expression(vote.content.vote.expression)}
 | 
			
		||||
						</span>
 | 
			
		||||
					`
 | 
			
		||||
				)}
 | 
			
		||||
			</div>`;
 | 
			
		||||
		}
 | 
			
		||||
		return html`<div>
 | 
			
		||||
			${(this.message.votes || []).map(
 | 
			
		||||
				(vote) => html`
 | 
			
		||||
					<span
 | 
			
		||||
						title="${this.users[vote.author]?.name ?? vote.author} ${new Date(
 | 
			
		||||
							vote.timestamp
 | 
			
		||||
						)}"
 | 
			
		||||
					>
 | 
			
		||||
						${normalize_expression(vote.content.vote.expression)}
 | 
			
		||||
					</span>
 | 
			
		||||
				`
 | 
			
		||||
			)}
 | 
			
		||||
		</div>`;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render_raw() {
 | 
			
		||||
@@ -133,7 +125,7 @@ class TfMessageElement extends LitElement {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	react(event) {
 | 
			
		||||
		emojis.picker((x) => this.vote(x), null, this.whoami);
 | 
			
		||||
		emojis.picker((x) => this.vote(x));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	show_image(link) {
 | 
			
		||||
@@ -248,7 +240,7 @@ ${JSON.stringify(mention, null, 2)}</pre
 | 
			
		||||
			let self = this;
 | 
			
		||||
			return html`
 | 
			
		||||
				<fieldset
 | 
			
		||||
					style="padding: 0.5em; border: 1px solid black"
 | 
			
		||||
					style="background-color: rgba(0, 0, 0, 0.1); padding: 0.5em; border: 1px solid black"
 | 
			
		||||
				>
 | 
			
		||||
					<legend>Mentions</legend>
 | 
			
		||||
					${mentions.map((x) => self.render_mention(x))}
 | 
			
		||||
@@ -290,14 +282,14 @@ ${JSON.stringify(mention, null, 2)}</pre
 | 
			
		||||
		if (this.message.child_messages?.length) {
 | 
			
		||||
			if (!this.expanded[this.message.id]) {
 | 
			
		||||
				return html`<button
 | 
			
		||||
					class="w3-button w3-theme-d1"
 | 
			
		||||
					class="w3-button w3-dark-grey"
 | 
			
		||||
					@click=${() => self.set_expanded(true)}
 | 
			
		||||
				>
 | 
			
		||||
					+ ${this.total_child_messages(this.message) + ' More'}
 | 
			
		||||
				</button>`;
 | 
			
		||||
			} else {
 | 
			
		||||
				return html`<button
 | 
			
		||||
						class="w3-button w3-theme-d1"
 | 
			
		||||
						class="w3-button w3-dark-grey"
 | 
			
		||||
						@click=${() => self.set_expanded(false)}
 | 
			
		||||
					>
 | 
			
		||||
						Collapse</button
 | 
			
		||||
@@ -339,23 +331,20 @@ ${JSON.stringify(mention, null, 2)}</pre
 | 
			
		||||
		if (this.message?.decrypted?.type == 'post') {
 | 
			
		||||
			content = this.message.decrypted;
 | 
			
		||||
		}
 | 
			
		||||
		let class_background = this.message?.decrypted
 | 
			
		||||
			? 'w3-pale-red'
 | 
			
		||||
			: 'w3-theme-d4';
 | 
			
		||||
		let self = this;
 | 
			
		||||
		let raw_button;
 | 
			
		||||
		switch (this.format) {
 | 
			
		||||
			case 'raw':
 | 
			
		||||
				if (content?.type == 'post' || content?.type == 'blog') {
 | 
			
		||||
					raw_button = html`<button
 | 
			
		||||
						class="w3-button w3-theme-d1"
 | 
			
		||||
						class="w3-button w3-dark-grey"
 | 
			
		||||
						@click=${() => (self.format = 'md')}
 | 
			
		||||
					>
 | 
			
		||||
						Markdown
 | 
			
		||||
					</button>`;
 | 
			
		||||
				} else {
 | 
			
		||||
					raw_button = html`<button
 | 
			
		||||
						class="w3-button w3-theme-d1"
 | 
			
		||||
						class="w3-button w3-dark-grey"
 | 
			
		||||
						@click=${() => (self.format = 'message')}
 | 
			
		||||
					>
 | 
			
		||||
						Message
 | 
			
		||||
@@ -364,7 +353,7 @@ ${JSON.stringify(mention, null, 2)}</pre
 | 
			
		||||
				break;
 | 
			
		||||
			case 'md':
 | 
			
		||||
				raw_button = html`<button
 | 
			
		||||
					class="w3-button w3-theme-d1"
 | 
			
		||||
					class="w3-button w3-dark-grey"
 | 
			
		||||
					@click=${() => (self.format = 'message')}
 | 
			
		||||
				>
 | 
			
		||||
					Message
 | 
			
		||||
@@ -372,7 +361,7 @@ ${JSON.stringify(mention, null, 2)}</pre
 | 
			
		||||
				break;
 | 
			
		||||
			case 'decrypted':
 | 
			
		||||
				raw_button = html`<button
 | 
			
		||||
					class="w3-button w3-theme-d1"
 | 
			
		||||
					class="w3-button w3-dark-grey"
 | 
			
		||||
					@click=${() => (self.format = 'raw')}
 | 
			
		||||
				>
 | 
			
		||||
					Raw
 | 
			
		||||
@@ -381,14 +370,14 @@ ${JSON.stringify(mention, null, 2)}</pre
 | 
			
		||||
			default:
 | 
			
		||||
				if (this.message.decrypted) {
 | 
			
		||||
					raw_button = html`<button
 | 
			
		||||
						class="w3-button w3-theme-d1"
 | 
			
		||||
						class="w3-button w3-dark-grey"
 | 
			
		||||
						@click=${() => (self.format = 'decrypted')}
 | 
			
		||||
					>
 | 
			
		||||
						Decrypted
 | 
			
		||||
					</button>`;
 | 
			
		||||
				} else {
 | 
			
		||||
					raw_button = html`<button
 | 
			
		||||
						class="w3-button w3-theme-d1"
 | 
			
		||||
						class="w3-button w3-dark-grey"
 | 
			
		||||
						@click=${() => (self.format = 'raw')}
 | 
			
		||||
					>
 | 
			
		||||
						Raw
 | 
			
		||||
@@ -400,8 +389,8 @@ ${JSON.stringify(mention, null, 2)}</pre
 | 
			
		||||
			let body;
 | 
			
		||||
			return html`
 | 
			
		||||
				<div
 | 
			
		||||
					class="w3-card-4 w3-theme-d4 w3-border-theme"
 | 
			
		||||
					style="margin-top: 8px; padding: 16px; display: inline-block; overflow-wrap: anywhere"
 | 
			
		||||
					class="w3-card-4"
 | 
			
		||||
					style="background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; display: inline-block; overflow-wrap: anywhere"
 | 
			
		||||
				>
 | 
			
		||||
					<tf-user id=${self.message.author} .users=${self.users}></tf-user>
 | 
			
		||||
					<span style="padding-right: 8px"
 | 
			
		||||
@@ -411,24 +400,13 @@ ${JSON.stringify(mention, null, 2)}</pre
 | 
			
		||||
					>
 | 
			
		||||
					${raw_button} ${self.format == 'raw' ? self.render_raw() : inner}
 | 
			
		||||
					${self.render_votes()}
 | 
			
		||||
					${(self.message.child_messages || []).map(
 | 
			
		||||
						(x) => html`
 | 
			
		||||
							<tf-message
 | 
			
		||||
								.message=${x}
 | 
			
		||||
								whoami=${self.whoami}
 | 
			
		||||
								.users=${self.users}
 | 
			
		||||
								.drafts=${self.drafts}
 | 
			
		||||
								.expanded=${self.expanded}
 | 
			
		||||
							></tf-message>
 | 
			
		||||
						`
 | 
			
		||||
					)}
 | 
			
		||||
				</div>
 | 
			
		||||
			`;
 | 
			
		||||
		}
 | 
			
		||||
		if (this.message?.type === 'contact_group') {
 | 
			
		||||
			return html` <div
 | 
			
		||||
				class="w3-card-4 w3-theme-d4 w3-border-theme"
 | 
			
		||||
				style="margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
 | 
			
		||||
				class="w3-card-4"
 | 
			
		||||
				style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
 | 
			
		||||
			>
 | 
			
		||||
				${this.message.messages.map(
 | 
			
		||||
					(x) =>
 | 
			
		||||
@@ -443,8 +421,8 @@ ${JSON.stringify(mention, null, 2)}</pre
 | 
			
		||||
			</div>`;
 | 
			
		||||
		} else if (this.message.placeholder) {
 | 
			
		||||
			return html` <div
 | 
			
		||||
				class="w3-card-4 w3-theme-d4 w3-border-theme"
 | 
			
		||||
				style="margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
 | 
			
		||||
				class="w3-card-4"
 | 
			
		||||
				style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
 | 
			
		||||
			>
 | 
			
		||||
				<a target="_top" href=${'#' + this.message.id}>${this.message.id}</a>
 | 
			
		||||
				(placeholder)
 | 
			
		||||
@@ -520,11 +498,13 @@ ${JSON.stringify(mention, null, 2)}</pre
 | 
			
		||||
									branch=${this.message.id}
 | 
			
		||||
									.drafts=${this.drafts}
 | 
			
		||||
									@tf-discard=${this.discard_reply}
 | 
			
		||||
									author=${this.message.author}
 | 
			
		||||
								></tf-compose>
 | 
			
		||||
							`
 | 
			
		||||
						: html`
 | 
			
		||||
								<button class="w3-button w3-theme-d1" @click=${this.show_reply}>
 | 
			
		||||
								<button
 | 
			
		||||
									class="w3-button w3-dark-grey"
 | 
			
		||||
									@click=${this.show_reply}
 | 
			
		||||
								>
 | 
			
		||||
									Reply
 | 
			
		||||
								</button>
 | 
			
		||||
							`;
 | 
			
		||||
@@ -553,7 +533,7 @@ ${JSON.stringify(content, null, 2)}</pre
 | 
			
		||||
				}
 | 
			
		||||
				let content_warning = html`
 | 
			
		||||
					<div
 | 
			
		||||
						class="w3-panel w3-round-xlarge w3-theme-l4"
 | 
			
		||||
						class="w3-panel w3-round-xlarge w3-blue"
 | 
			
		||||
						style="cursor: pointer"
 | 
			
		||||
						@click=${(x) => this.toggle_expanded(':cw')}
 | 
			
		||||
					>
 | 
			
		||||
@@ -573,6 +553,9 @@ ${JSON.stringify(content, null, 2)}</pre
 | 
			
		||||
				let is_encrypted = this.message?.decrypted
 | 
			
		||||
					? html`<span style="align-self: center">🔓</span>`
 | 
			
		||||
					: undefined;
 | 
			
		||||
				let style_background = this.message?.decrypted
 | 
			
		||||
					? 'rgba(255, 0, 0, 0.2)'
 | 
			
		||||
					: 'rgba(255, 255, 255, 0.1)';
 | 
			
		||||
				return html`
 | 
			
		||||
					<style>
 | 
			
		||||
						code {
 | 
			
		||||
@@ -589,8 +572,8 @@ ${JSON.stringify(content, null, 2)}</pre
 | 
			
		||||
						}
 | 
			
		||||
					</style>
 | 
			
		||||
					<div
 | 
			
		||||
						class="w3-card-4 ${class_background} w3-border-theme"
 | 
			
		||||
						style="margin-top: 8px; padding: 16px"
 | 
			
		||||
						class="w3-card-4"
 | 
			
		||||
						style="border: 1px solid black; background-color: ${style_background}; margin-top: 8px; padding: 16px"
 | 
			
		||||
					>
 | 
			
		||||
						<div style="display: flex; flex-direction: row">
 | 
			
		||||
							<tf-user id=${this.message.author} .users=${this.users}></tf-user>
 | 
			
		||||
@@ -605,7 +588,7 @@ ${JSON.stringify(content, null, 2)}</pre
 | 
			
		||||
						${payload} ${this.render_votes()}
 | 
			
		||||
						<p>
 | 
			
		||||
							${reply}
 | 
			
		||||
							<button class="w3-button w3-theme-d1" @click=${this.react}>
 | 
			
		||||
							<button class="w3-button w3-dark-grey" @click=${this.react}>
 | 
			
		||||
								React
 | 
			
		||||
							</button>
 | 
			
		||||
						</p>
 | 
			
		||||
@@ -616,6 +599,9 @@ ${JSON.stringify(content, null, 2)}</pre
 | 
			
		||||
				let is_encrypted = this.message?.decrypted
 | 
			
		||||
					? html`<span style="align-self: center">🔓</span>`
 | 
			
		||||
					: undefined;
 | 
			
		||||
				let style_background = this.message?.decrypted
 | 
			
		||||
					? 'rgba(255, 0, 0, 0.2)'
 | 
			
		||||
					: 'rgba(255, 255, 255, 0.1)';
 | 
			
		||||
				return html`
 | 
			
		||||
					<style>
 | 
			
		||||
						code {
 | 
			
		||||
@@ -632,8 +618,8 @@ ${JSON.stringify(content, null, 2)}</pre
 | 
			
		||||
						}
 | 
			
		||||
					</style>
 | 
			
		||||
					<div
 | 
			
		||||
						class="w3-card-4 ${class_background} w3-border-theme"
 | 
			
		||||
						style="margin-top: 8px; padding: 16px"
 | 
			
		||||
						class="w3-card-4"
 | 
			
		||||
						style="border: 1px solid black; background-color: ${style_background}; margin-top: 8px; padding: 16px"
 | 
			
		||||
					>
 | 
			
		||||
						<div style="display: flex; flex-direction: row">
 | 
			
		||||
							<tf-user id=${this.message.author} .users=${this.users}></tf-user>
 | 
			
		||||
@@ -647,7 +633,7 @@ ${JSON.stringify(content, null, 2)}</pre
 | 
			
		||||
						</div>
 | 
			
		||||
						${content.text} ${this.render_votes()}
 | 
			
		||||
						<p>
 | 
			
		||||
							<button class="w3-button w3-theme-d1" @click=${this.react}>
 | 
			
		||||
							<button class="w3-button w3-dark-grey" @click=${this.react}>
 | 
			
		||||
								React
 | 
			
		||||
							</button>
 | 
			
		||||
						</p>
 | 
			
		||||
@@ -699,11 +685,13 @@ ${JSON.stringify(content, null, 2)}</pre
 | 
			
		||||
									branch=${this.message.id}
 | 
			
		||||
									.drafts=${this.drafts}
 | 
			
		||||
									@tf-discard=${this.discard_reply}
 | 
			
		||||
									author=${this.message.author}
 | 
			
		||||
								></tf-compose>
 | 
			
		||||
							`
 | 
			
		||||
						: html`
 | 
			
		||||
								<button class="w3-button w3-theme-d1" @click=${this.show_reply}>
 | 
			
		||||
								<button
 | 
			
		||||
									class="w3-button w3-dark-grey"
 | 
			
		||||
									@click=${this.show_reply}
 | 
			
		||||
								>
 | 
			
		||||
									Reply
 | 
			
		||||
								</button>
 | 
			
		||||
							`;
 | 
			
		||||
@@ -723,8 +711,8 @@ ${JSON.stringify(content, null, 2)}</pre
 | 
			
		||||
						}
 | 
			
		||||
					</style>
 | 
			
		||||
					<div
 | 
			
		||||
						class="w3-card-4 w3-theme-d4 w3-border-theme"
 | 
			
		||||
						style="margin-top: 8px; padding: 16px"
 | 
			
		||||
						class="w3-card-4"
 | 
			
		||||
						style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px"
 | 
			
		||||
					>
 | 
			
		||||
						<div style="display: flex; flex-direction: row">
 | 
			
		||||
							<tf-user id=${this.message.author} .users=${this.users}></tf-user>
 | 
			
		||||
@@ -740,7 +728,7 @@ ${JSON.stringify(content, null, 2)}</pre
 | 
			
		||||
						${this.render_mentions()}
 | 
			
		||||
						<div>
 | 
			
		||||
							${reply}
 | 
			
		||||
							<button class="w3-button w3-theme-d1" @click=${this.react}>
 | 
			
		||||
							<button class="w3-button w3-dark-grey" @click=${this.react}>
 | 
			
		||||
								React
 | 
			
		||||
							</button>
 | 
			
		||||
						</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -215,49 +215,49 @@ class TfProfileElement extends LitElement {
 | 
			
		||||
				let server_follow;
 | 
			
		||||
				if (this.server_follows_me === true) {
 | 
			
		||||
					server_follow = html`<button
 | 
			
		||||
						class="w3-button w3-theme-d1"
 | 
			
		||||
						class="w3-button w3-dark-grey"
 | 
			
		||||
						@click=${() => this.server_follow_me(false)}
 | 
			
		||||
					>
 | 
			
		||||
						Server, Stop Following Me
 | 
			
		||||
					</button>`;
 | 
			
		||||
				} else if (this.server_follows_me === false) {
 | 
			
		||||
					server_follow = html`<button
 | 
			
		||||
						class="w3-button w3-theme-d1"
 | 
			
		||||
						class="w3-button w3-dark-grey"
 | 
			
		||||
						@click=${() => this.server_follow_me(true)}
 | 
			
		||||
					>
 | 
			
		||||
						Server, Follow Me
 | 
			
		||||
					</button>`;
 | 
			
		||||
				}
 | 
			
		||||
				edit = html`
 | 
			
		||||
					<button class="w3-button w3-theme-d1" @click=${this.save_edits}>
 | 
			
		||||
					<button class="w3-button w3-dark-grey" @click=${this.save_edits}>
 | 
			
		||||
						Save Profile
 | 
			
		||||
					</button>
 | 
			
		||||
					<button class="w3-button w3-theme-d1" @click=${this.discard_edits}>
 | 
			
		||||
					<button class="w3-button w3-dark-grey" @click=${this.discard_edits}>
 | 
			
		||||
						Discard
 | 
			
		||||
					</button>
 | 
			
		||||
					${server_follow}
 | 
			
		||||
				`;
 | 
			
		||||
			} else {
 | 
			
		||||
				edit = html`<button class="w3-button w3-theme-d1" @click=${this.edit}>
 | 
			
		||||
				edit = html`<button class="w3-button w3-dark-grey" @click=${this.edit}>
 | 
			
		||||
					Edit Profile
 | 
			
		||||
				</button>`;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (this.id !== this.whoami && this.following !== undefined) {
 | 
			
		||||
			follow = this.following
 | 
			
		||||
				? html`<button class="w3-button w3-theme-d1" @click=${this.unfollow}>
 | 
			
		||||
				? html`<button class="w3-button w3-dark-grey" @click=${this.unfollow}>
 | 
			
		||||
						Unfollow
 | 
			
		||||
					</button>`
 | 
			
		||||
				: html`<button class="w3-button w3-theme-d1" @click=${this.follow}>
 | 
			
		||||
				: html`<button class="w3-button w3-dark-grey" @click=${this.follow}>
 | 
			
		||||
						Follow
 | 
			
		||||
					</button>`;
 | 
			
		||||
		}
 | 
			
		||||
		if (this.id !== this.whoami && this.blocking !== undefined) {
 | 
			
		||||
			block = this.blocking
 | 
			
		||||
				? html`<button class="w3-button w3-theme-d1" @click=${this.unblock}>
 | 
			
		||||
				? html`<button class="w3-button w3-dark-grey" @click=${this.unblock}>
 | 
			
		||||
						Unblock
 | 
			
		||||
					</button>`
 | 
			
		||||
				: html`<button class="w3-button w3-theme-d1" @click=${this.block}>
 | 
			
		||||
				: html`<button class="w3-button w3-dark-grey" @click=${this.block}>
 | 
			
		||||
						Block
 | 
			
		||||
					</button>`;
 | 
			
		||||
		}
 | 
			
		||||
@@ -267,16 +267,16 @@ class TfProfileElement extends LitElement {
 | 
			
		||||
				<div class="w3-container">
 | 
			
		||||
					<div>
 | 
			
		||||
						<label for="name">Name:</label>
 | 
			
		||||
						<input class="w3-input w3-theme-d1" type="text" id="name" value=${this.editing.name} @input=${(event) => (this.editing = Object.assign({}, this.editing, {name: event.srcElement.value}))}></input>
 | 
			
		||||
						<input class="w3-input w3-dark-grey" type="text" id="name" value=${this.editing.name} @input=${(event) => (this.editing = Object.assign({}, this.editing, {name: event.srcElement.value}))}></input>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div><label for="description">Description:</label></div>
 | 
			
		||||
					<textarea class="w3-input w3-theme-d1" style="resize: vertical" rows="8" id="description" @input=${(event) => (this.editing = Object.assign({}, this.editing, {description: event.srcElement.value}))}>${this.editing.description}</textarea>
 | 
			
		||||
					<textarea class="w3-input w3-dark-grey" style="resize: vertical" rows="8" id="description" @input=${(event) => (this.editing = Object.assign({}, this.editing, {description: event.srcElement.value}))}>${this.editing.description}</textarea>
 | 
			
		||||
					<div>
 | 
			
		||||
						<label for="public_web_hosting">Public Web Hosting:</label>
 | 
			
		||||
						<input class="w3-check w3-theme-d1" type="checkbox" id="public_web_hosting" ?checked=${this.editing.publicWebHosting} @input=${(event) => (self.editing = Object.assign({}, self.editing, {publicWebHosting: event.srcElement.checked}))}></input>
 | 
			
		||||
						<input class="w3-check w3-dark-grey" type="checkbox" id="public_web_hosting" ?checked=${this.editing.publicWebHosting} @input=${(event) => (self.editing = Object.assign({}, self.editing, {publicWebHosting: event.srcElement.checked}))}></input>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div>
 | 
			
		||||
						<button class="w3-button w3-theme-d1" @click=${this.attach_image}>Attach Image</button>
 | 
			
		||||
						<button class="w3-button w3-dark-grey" @click=${this.attach_image}>Attach Image</button>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>`
 | 
			
		||||
 
 | 
			
		||||
@@ -1,68 +0,0 @@
 | 
			
		||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
 | 
			
		||||
import {styles} from './tf-styles.js';
 | 
			
		||||
 | 
			
		||||
class TfReactionsModalElement extends LitElement {
 | 
			
		||||
	static get properties() {
 | 
			
		||||
		return {
 | 
			
		||||
			users: {type: Object},
 | 
			
		||||
			votes: {type: Array},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static styles = styles;
 | 
			
		||||
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super();
 | 
			
		||||
		this.votes = [];
 | 
			
		||||
		this.users = {};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clear() {
 | 
			
		||||
		this.votes = [];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		let self = this;
 | 
			
		||||
		return this.votes?.length
 | 
			
		||||
			? html` <div
 | 
			
		||||
					class="w3-modal w3-animate-opacity"
 | 
			
		||||
					style="display: block; box-sizing: border-box"
 | 
			
		||||
				>
 | 
			
		||||
					<div class="w3-modal-content w3-card-4 w3-theme-d1">
 | 
			
		||||
						<div class="w3-container w3-padding">
 | 
			
		||||
							<header class="w3-container">
 | 
			
		||||
								<h2>Reactions</h2>
 | 
			
		||||
								<span class="w3-button w3-display-topright" @click=${this.clear}
 | 
			
		||||
									>×</span
 | 
			
		||||
								>
 | 
			
		||||
							</header>
 | 
			
		||||
							<ul class="w3-theme-dark w3-container w3-ul">
 | 
			
		||||
								${this.votes.map(
 | 
			
		||||
									(x) => html`
 | 
			
		||||
										<li class="w3-bar">
 | 
			
		||||
											<span class="w3-bar-item"
 | 
			
		||||
												>${x?.content?.vote?.expression}</span
 | 
			
		||||
											>
 | 
			
		||||
											<tf-user
 | 
			
		||||
												class="w3-bar-item"
 | 
			
		||||
												id=${x.author}
 | 
			
		||||
												.users=${this.users}
 | 
			
		||||
											></tf-user>
 | 
			
		||||
											<span class="w3-bar-item w3-right"
 | 
			
		||||
												>${new Date(x?.timestamp).toLocaleString()}</span
 | 
			
		||||
											>
 | 
			
		||||
										</li>
 | 
			
		||||
									`
 | 
			
		||||
								)}
 | 
			
		||||
							</ul>
 | 
			
		||||
							<footer class="w3-container w3-padding">
 | 
			
		||||
								<button class="w3-button" @click=${this.clear}>Close</button>
 | 
			
		||||
							</footer>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>`
 | 
			
		||||
			: undefined;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define('tf-reactions-modal', TfReactionsModalElement);
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -33,11 +33,9 @@ class TfTabConnectionsElement extends LitElement {
 | 
			
		||||
 | 
			
		||||
	render_connection_summary(connection) {
 | 
			
		||||
		if (connection.address && connection.port) {
 | 
			
		||||
			return html`<div>
 | 
			
		||||
				<small>${connection.address}:${connection.port}</small>
 | 
			
		||||
			</div>`;
 | 
			
		||||
			return html`(<small>${connection.address}:${connection.port}</small>)`;
 | 
			
		||||
		} else if (connection.tunnel) {
 | 
			
		||||
			return html`<div>room peer</div>`;
 | 
			
		||||
			return html`(room peer)`;
 | 
			
		||||
		} else {
 | 
			
		||||
			return JSON.stringify(connection);
 | 
			
		||||
		}
 | 
			
		||||
@@ -63,7 +61,7 @@ class TfTabConnectionsElement extends LitElement {
 | 
			
		||||
		return html`
 | 
			
		||||
			<li>
 | 
			
		||||
				<button
 | 
			
		||||
					class="w3-button w3-theme-d1"
 | 
			
		||||
					class="w3-button w3-dark-grey"
 | 
			
		||||
					@click=${() => self._tunnel(connection.tunnel.id, connection.pubkey)}
 | 
			
		||||
				>
 | 
			
		||||
					Connect
 | 
			
		||||
@@ -75,17 +73,15 @@ class TfTabConnectionsElement extends LitElement {
 | 
			
		||||
 | 
			
		||||
	render_broadcast(connection) {
 | 
			
		||||
		return html`
 | 
			
		||||
			<li class="w3-bar" style="overflow: hidden; overflow-wrap: nowrap">
 | 
			
		||||
			<li>
 | 
			
		||||
				<button
 | 
			
		||||
					class="w3-bar-item w3-button w3-theme-d1"
 | 
			
		||||
					class="w3-button w3-dark-grey"
 | 
			
		||||
					@click=${() => tfrpc.rpc.connect(connection)}
 | 
			
		||||
				>
 | 
			
		||||
					Connect
 | 
			
		||||
				</button>
 | 
			
		||||
				<div class="w3-bar-item">
 | 
			
		||||
					<tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
 | 
			
		||||
					${this.render_connection_summary(connection)}
 | 
			
		||||
				</div>
 | 
			
		||||
				<tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
 | 
			
		||||
				${this.render_connection_summary(connection)}
 | 
			
		||||
			</li>
 | 
			
		||||
		`;
 | 
			
		||||
	}
 | 
			
		||||
@@ -98,7 +94,7 @@ class TfTabConnectionsElement extends LitElement {
 | 
			
		||||
	render_connection(connection) {
 | 
			
		||||
		return html`
 | 
			
		||||
			<button
 | 
			
		||||
				class="w3-button w3-theme-d1"
 | 
			
		||||
				class="w3-button w3-dark-grey"
 | 
			
		||||
				@click=${() => tfrpc.rpc.closeConnection(connection.id)}
 | 
			
		||||
			>
 | 
			
		||||
				Close
 | 
			
		||||
@@ -107,9 +103,6 @@ class TfTabConnectionsElement extends LitElement {
 | 
			
		||||
			${connection.tunnel !== undefined
 | 
			
		||||
				? '🚇'
 | 
			
		||||
				: html`(${connection.host}:${connection.port})`}
 | 
			
		||||
			<div>${connection.requests.map(x => html`
 | 
			
		||||
				<span class="w3-tag w3-small">${x.request_number > 0 ? '🟩' : '🟥'} ${x.name}</span>
 | 
			
		||||
			`)}</div>
 | 
			
		||||
			<ul>
 | 
			
		||||
				${this.connections
 | 
			
		||||
					.filter((x) => x.tunnel === this.connections.indexOf(connection))
 | 
			
		||||
@@ -122,64 +115,56 @@ class TfTabConnectionsElement extends LitElement {
 | 
			
		||||
	render() {
 | 
			
		||||
		let self = this;
 | 
			
		||||
		return html`
 | 
			
		||||
			<div class="w3-container" style="box-sizing: border-box">
 | 
			
		||||
			<div class="w3-container">
 | 
			
		||||
				<h2>New Connection</h2>
 | 
			
		||||
				<textarea class="w3-input w3-theme-d1" id="code"></textarea>
 | 
			
		||||
				<textarea class="w3-input w3-dark-grey" id="code"></textarea>
 | 
			
		||||
				<button
 | 
			
		||||
					class="w3-button w3-theme-d1"
 | 
			
		||||
					class="w3-button w3-dark-grey"
 | 
			
		||||
					@click=${() =>
 | 
			
		||||
						tfrpc.rpc.connect(self.renderRoot.getElementById('code').value)}
 | 
			
		||||
				>
 | 
			
		||||
					Connect
 | 
			
		||||
				</button>
 | 
			
		||||
				<h2>Broadcasts</h2>
 | 
			
		||||
				<ul class="w3-ul w3-border">
 | 
			
		||||
				<ul>
 | 
			
		||||
					${this.broadcasts
 | 
			
		||||
						.filter((x) => x.address)
 | 
			
		||||
						.map((x) => self.render_broadcast(x))}
 | 
			
		||||
				</ul>
 | 
			
		||||
				<h2>Connections</h2>
 | 
			
		||||
				<ul class="w3-ul w3-border">
 | 
			
		||||
				<ul>
 | 
			
		||||
					${this.connections
 | 
			
		||||
						.filter((x) => x.tunnel === undefined)
 | 
			
		||||
						.map(
 | 
			
		||||
							(x) => html`
 | 
			
		||||
								<li class="w3-bar">${this.render_connection(x)}</li>
 | 
			
		||||
							`
 | 
			
		||||
						)}
 | 
			
		||||
						.map((x) => html` <li>${this.render_connection(x)}</li> `)}
 | 
			
		||||
				</ul>
 | 
			
		||||
				<h2>Stored Connections</h2>
 | 
			
		||||
				<ul class="w3-ul w3-border">
 | 
			
		||||
				<h2>Stored Connections (WIP)</h2>
 | 
			
		||||
				<ul>
 | 
			
		||||
					${this.stored_connections.map(
 | 
			
		||||
						(x) => html`
 | 
			
		||||
							<li class="w3-bar">
 | 
			
		||||
							<li>
 | 
			
		||||
								<button
 | 
			
		||||
									class="w3-bar-item w3-button w3-theme-d1"
 | 
			
		||||
									class="w3-button w3-dark-grey"
 | 
			
		||||
									@click=${() => self.forget_stored_connection(x)}
 | 
			
		||||
								>
 | 
			
		||||
									Forget
 | 
			
		||||
								</button>
 | 
			
		||||
								<button
 | 
			
		||||
									class="w3-bar-item w3-button w3-theme-d1"
 | 
			
		||||
									class="w3-button w3-dark-grey"
 | 
			
		||||
									@click=${() => tfrpc.rpc.connect(x)}
 | 
			
		||||
								>
 | 
			
		||||
									Connect
 | 
			
		||||
								</button>
 | 
			
		||||
								<div class="w3-bar-item">
 | 
			
		||||
									<tf-user id=${x.pubkey} .users=${self.users}></tf-user>
 | 
			
		||||
									<div><small>${x.address}:${x.port}</small></div>
 | 
			
		||||
								</div>
 | 
			
		||||
								${x.address}:${x.port}
 | 
			
		||||
								<tf-user id=${x.pubkey} .users=${self.users}></tf-user>
 | 
			
		||||
							</li>
 | 
			
		||||
						`
 | 
			
		||||
					)}
 | 
			
		||||
				</ul>
 | 
			
		||||
				<h2>Local Accounts</h2>
 | 
			
		||||
				<ul class="w3-ul w3-border">
 | 
			
		||||
				<ul>
 | 
			
		||||
					${this.identities.map(
 | 
			
		||||
						(x) =>
 | 
			
		||||
							html`<li class="w3-bar">
 | 
			
		||||
								<tf-user id=${x} .users=${this.users}></tf-user>
 | 
			
		||||
							</li>`
 | 
			
		||||
							html`<li><tf-user id=${x} .users=${this.users}></tf-user></li>`
 | 
			
		||||
					)}
 | 
			
		||||
				</ul>
 | 
			
		||||
			</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -187,7 +187,7 @@ class TfTabNewsFeedElement extends LitElement {
 | 
			
		||||
		if (!this.hash.startsWith('#@') && !this.hash.startsWith('#%')) {
 | 
			
		||||
			more = html`
 | 
			
		||||
				<p>
 | 
			
		||||
					<button class="w3-button w3-theme-d1" @click=${this.load_more}>
 | 
			
		||||
					<button class="w3-button w3-dark-grey" @click=${this.load_more}>
 | 
			
		||||
						Load More
 | 
			
		||||
					</button>
 | 
			
		||||
				</p>
 | 
			
		||||
 
 | 
			
		||||
@@ -84,7 +84,10 @@ class TfTabNewsElement extends LitElement {
 | 
			
		||||
		} else {
 | 
			
		||||
			delete this.drafts[id];
 | 
			
		||||
		}
 | 
			
		||||
		this.drafts = Object.assign({}, this.drafts);
 | 
			
		||||
		/* Only trigger a re-render if we're creating a new draft or discarding an old one. */
 | 
			
		||||
		if ((previous !== undefined) != (event.detail.draft !== undefined)) {
 | 
			
		||||
			this.drafts = Object.assign({}, this.drafts);
 | 
			
		||||
		}
 | 
			
		||||
		tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -116,7 +119,7 @@ class TfTabNewsElement extends LitElement {
 | 
			
		||||
		return html`
 | 
			
		||||
			<p class="w3-bar">
 | 
			
		||||
				<button
 | 
			
		||||
					class="w3-bar-item w3-button w3-theme-d1"
 | 
			
		||||
					class="w3-bar-item w3-button w3-dark-grey"
 | 
			
		||||
					@click=${this.show_more}
 | 
			
		||||
				>
 | 
			
		||||
					${this.new_messages_text()}
 | 
			
		||||
 
 | 
			
		||||
@@ -110,14 +110,14 @@ class TfTabQueryElement extends LitElement {
 | 
			
		||||
				<textarea
 | 
			
		||||
					id="search"
 | 
			
		||||
					rows="8"
 | 
			
		||||
					class="w3-input w3-theme-d1"
 | 
			
		||||
					class="w3-input w3-dark-grey"
 | 
			
		||||
					style="flex: 1; resize: vertical"
 | 
			
		||||
					@keydown=${this.search_keydown}
 | 
			
		||||
				>
 | 
			
		||||
${this.query}</textarea
 | 
			
		||||
				>
 | 
			
		||||
				<button
 | 
			
		||||
					class="w3-button w3-theme-d1"
 | 
			
		||||
					class="w3-button w3-dark-grey"
 | 
			
		||||
					@click=${(event) =>
 | 
			
		||||
						self.search(self.renderRoot.getElementById('search').value)}
 | 
			
		||||
				>
 | 
			
		||||
 
 | 
			
		||||
@@ -78,8 +78,8 @@ class TfTabSearchElement extends LitElement {
 | 
			
		||||
		let self = this;
 | 
			
		||||
		return html`
 | 
			
		||||
			<div style="display: flex; flex-direction: row; gap: 4px">
 | 
			
		||||
				<input type="text" class="w3-input w3-theme-d1" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
 | 
			
		||||
				<button class="w3-button w3-theme-d1" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button>
 | 
			
		||||
				<input type="text" class="w3-input w3-dark-grey" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
 | 
			
		||||
				<button class="w3-button w3-dark-grey" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button>
 | 
			
		||||
			</div>
 | 
			
		||||
			<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} @tf-expand=${this.on_expand}></tf-news>
 | 
			
		||||
		`;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import * as linkify from './commonmark-linkify.js';
 | 
			
		||||
import * as hashtagify from './commonmark-hashtag.js';
 | 
			
		||||
 | 
			
		||||
const k_code_classes = 'w3-theme-l4 w3-theme-border w3-round';
 | 
			
		||||
 | 
			
		||||
function image(node, entering) {
 | 
			
		||||
	if (
 | 
			
		||||
		node.firstChild?.type === 'text' &&
 | 
			
		||||
@@ -62,32 +61,13 @@ function image(node, entering) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function code(node) {
 | 
			
		||||
	let attrs = this.attrs(node);
 | 
			
		||||
	attrs.push(['class', k_code_classes]);
 | 
			
		||||
	this.tag('code', attrs);
 | 
			
		||||
	this.out(node.literal);
 | 
			
		||||
	this.tag('/code');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function attrs(node) {
 | 
			
		||||
	let result = commonmark.HtmlRenderer.prototype.attrs.bind(this)(node);
 | 
			
		||||
	if (node.type == 'block_quote') {
 | 
			
		||||
		result.push(['class', 'w3-theme-d1']);
 | 
			
		||||
	} else if (node.type == 'code_block') {
 | 
			
		||||
		result.push(['class', k_code_classes]);
 | 
			
		||||
	}
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function markdown(md) {
 | 
			
		||||
	let reader = new commonmark.Parser({safe: true});
 | 
			
		||||
	let writer = new commonmark.HtmlRenderer();
 | 
			
		||||
	writer.image = image;
 | 
			
		||||
	writer.code = code;
 | 
			
		||||
	writer.attrs = attrs;
 | 
			
		||||
	let parsed = reader.parse(md || '');
 | 
			
		||||
	parsed = hashtagify.transform(parsed);
 | 
			
		||||
	parsed = linkify.transform(parsed);
 | 
			
		||||
	let walker = parsed.walker();
 | 
			
		||||
	let event, node;
 | 
			
		||||
	while ((event = walker.next())) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
	"type": "tildefriends-app",
 | 
			
		||||
	"emoji": "👋",
 | 
			
		||||
	"previous": "&W5aJp2DgOW5rQ0AOIC9Ut3DpsahPrO6PjkJ1PQbNRdM=.sha256"
 | 
			
		||||
	"previous": "&zFISmRDAv+SXFonfZ9/sHNhrmMe+poTU22gwZzuSkT4=.sha256"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@
 | 
			
		||||
					</p>
 | 
			
		||||
					<a
 | 
			
		||||
						class="w3-button w3-black w3-padding-large"
 | 
			
		||||
						href="https://dev.tildefriends.net/cory/tildefriends/releases"
 | 
			
		||||
						href="https://www.tildefriends.net/~cory/releases/"
 | 
			
		||||
						><i class="fa fa-download"></i> Download</a
 | 
			
		||||
					>
 | 
			
		||||
					<a
 | 
			
		||||
@@ -63,11 +63,6 @@
 | 
			
		||||
						href="https://www.tildefriends.net/~cory/apps/"
 | 
			
		||||
						><i class="fa fa-link"></i> Try It</a
 | 
			
		||||
					>
 | 
			
		||||
					<a
 | 
			
		||||
						class="w3-button w3-black w3-padding-large"
 | 
			
		||||
						href="https://dev.tildefriends.net/"
 | 
			
		||||
						><i class="fa fa-mug-hot"></i> Code</a
 | 
			
		||||
					>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="w3-col l4 m6">
 | 
			
		||||
					<img src="tildefriends.png" class="w3-image w3-right w3-hide-small" />
 | 
			
		||||
@@ -75,60 +70,6 @@
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<!-- Getting Starting Section -->
 | 
			
		||||
		<div class="w3-indigo w3-center">
 | 
			
		||||
			<div class="w3-row-padding w3-padding-64">
 | 
			
		||||
				<div class="w3-jumbo">
 | 
			
		||||
					<i class="fa fa-rocket"></i> <b>Getting Started</b>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<h2>First-time user checklist:</h2>
 | 
			
		||||
					<ol type="1" style="text-align: left">
 | 
			
		||||
						<li>
 | 
			
		||||
							<a href="https://dev.tildefriends.net/cory/tildefriends/releases"
 | 
			
		||||
								>Download</a
 | 
			
		||||
							>
 | 
			
		||||
							Tilde Friends and run your own instance, or use
 | 
			
		||||
							<a href="https://www.tildefriends.net/"
 | 
			
		||||
								>https://www.tildefriends.net/</a
 | 
			
		||||
							>.
 | 
			
		||||
						</li>
 | 
			
		||||
						<li>
 | 
			
		||||
							Create an account to identify yourself with that instance by
 | 
			
		||||
							username and password.
 | 
			
		||||
						</li>
 | 
			
		||||
						<li>
 | 
			
		||||
							Create an SSB identity in the <b>ssb</b> app. This will generate a
 | 
			
		||||
							keypair used to identify yourself to other users and sign your
 | 
			
		||||
							messages so that they can be verified as from you.
 | 
			
		||||
						</li>
 | 
			
		||||
						<li>
 | 
			
		||||
							Describe yourself in your profile in the <b>ssb</b> app. Give
 | 
			
		||||
							yourself a name and an avatar if you like.
 | 
			
		||||
						</li>
 | 
			
		||||
						<li>
 | 
			
		||||
							Connect to others. You will automatically discover peers on the
 | 
			
		||||
							same instance and same network if there are any. Or use
 | 
			
		||||
							<a href="https://github.com/staltz/ssb-room/blob/master/FAQ.md"
 | 
			
		||||
								>rooms</a
 | 
			
		||||
							>
 | 
			
		||||
							and pubs to reach more distant users.
 | 
			
		||||
							<a href="https://www.tildefriends.net/~cory/room/"
 | 
			
		||||
								>tildefriends.net itself</a
 | 
			
		||||
							>
 | 
			
		||||
							operates as a room, so you can connect and see who else is online
 | 
			
		||||
							and establish a connection.
 | 
			
		||||
						</li>
 | 
			
		||||
						<li>Follow people to grow your network.</li>
 | 
			
		||||
						<li>
 | 
			
		||||
							Use the <b>edit</b> link at the top of any page to start modifying
 | 
			
		||||
							and making apps.
 | 
			
		||||
						</li>
 | 
			
		||||
					</ol>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<!-- SSB Section -->
 | 
			
		||||
		<div class="w3-light-grey">
 | 
			
		||||
			<div class="w3-row-padding w3-padding-64">
 | 
			
		||||
@@ -258,7 +199,7 @@
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="w3-row" style="margin-top: 64px">
 | 
			
		||||
				<a href="https://codemirror.net/docs/changelog/" class="w3-col s3">
 | 
			
		||||
				<a href="https://codemirror.net/5/" class="w3-col s3">
 | 
			
		||||
					<i class="fa fa-keyboard w3-text-indigo w3-jumbo"></i>
 | 
			
		||||
					<p>CodeMirror</p>
 | 
			
		||||
				</a>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
	"type": "tildefriends-app",
 | 
			
		||||
	"emoji": "📝",
 | 
			
		||||
	"previous": "&DaYqKHRBKhjFGaOzbKZ1+/pLspJeEkDJYTF2B50tH6k=.sha256"
 | 
			
		||||
	"previous": "&DnfuAUGzzalSh9NgZXnzDc9Ru5aM0omfRJ4h27jYw4k=.sha256"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,6 @@ import * as utils from './utils.js';
 | 
			
		||||
let g_hash;
 | 
			
		||||
let g_collection_notifies = {};
 | 
			
		||||
 | 
			
		||||
tfrpc.register(async function getActiveIdentity() {
 | 
			
		||||
	return ssb.getActiveIdentity();
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function getOwnerIdentities() {
 | 
			
		||||
	return ssb.getOwnerIdentities();
 | 
			
		||||
});
 | 
			
		||||
@@ -57,9 +54,6 @@ core.register('message', async function message_handler(message) {
 | 
			
		||||
		await tfrpc.rpc.hash_changed(message.hash);
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
core.register('setActiveIdentity', async function setActiveIdentityHandler(id) {
 | 
			
		||||
	await tfrpc.rpc.setActiveIdentity(id);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
tfrpc.register(function set_hash(hash) {
 | 
			
		||||
	if (g_hash != hash) {
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@
 | 
			
		||||
			window.litDisableBundleWarning = true;
 | 
			
		||||
		</script>
 | 
			
		||||
		<script src="tf-collection.js" type="module"></script>
 | 
			
		||||
		<script src="tf-id-picker.js" type="module"></script>
 | 
			
		||||
		<script src="tf-wiki-doc.js" type="module"></script>
 | 
			
		||||
		<script src="tf-wiki-app.js" type="module"></script>
 | 
			
		||||
	</body>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								apps/wiki/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								apps/wiki/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										44
									
								
								apps/wiki/tf-id-picker.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								apps/wiki/tf-id-picker.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
import {LitElement, html} from './lit-all.min.js';
 | 
			
		||||
import * as tfrpc from '/static/tfrpc.js';
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 ** Provide a list of IDs, and this lets the user pick one.
 | 
			
		||||
 */
 | 
			
		||||
class TfIdentityPickerElement extends LitElement {
 | 
			
		||||
	static get properties() {
 | 
			
		||||
		return {
 | 
			
		||||
			ids: {type: Array},
 | 
			
		||||
			selected: {type: String},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super();
 | 
			
		||||
		this.ids = [];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	changed(event) {
 | 
			
		||||
		this.selected = event.srcElement.value;
 | 
			
		||||
		this.dispatchEvent(
 | 
			
		||||
			new Event('change', {
 | 
			
		||||
				srcElement: this,
 | 
			
		||||
			})
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		return html`
 | 
			
		||||
			<link rel="stylesheet" href="tildefriends.css" />
 | 
			
		||||
			<select @change=${this.changed} style="max-width: 100%">
 | 
			
		||||
				${(this.ids ?? []).map(
 | 
			
		||||
					(id) =>
 | 
			
		||||
						html`<option ?selected=${id == this.selected} value=${id}>
 | 
			
		||||
							${id}
 | 
			
		||||
						</option>`
 | 
			
		||||
				)}
 | 
			
		||||
			</select>
 | 
			
		||||
		`;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define('tf-id-picker', TfIdentityPickerElement);
 | 
			
		||||
@@ -31,16 +31,13 @@ class TfCollectionsAppElement extends LitElement {
 | 
			
		||||
		tfrpc.register(function hash_changed(hash) {
 | 
			
		||||
			self.notify_hash_changed(hash);
 | 
			
		||||
		});
 | 
			
		||||
		tfrpc.register(function setActiveIdentity(id) {
 | 
			
		||||
			self.whoami = id;
 | 
			
		||||
		});
 | 
			
		||||
		tfrpc.rpc.get_hash().then((hash) => self.notify_hash_changed(hash));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async load() {
 | 
			
		||||
		this.ids = await tfrpc.rpc.getIdentities();
 | 
			
		||||
		this.owner_ids = await tfrpc.rpc.getOwnerIdentities();
 | 
			
		||||
		this.whoami = await tfrpc.rpc.getActiveIdentity();
 | 
			
		||||
		this.whoami = await tfrpc.rpc.localStorageGet('collections_whoami');
 | 
			
		||||
		let ids = [...new Set([...this.owner_ids, this.whoami])].sort();
 | 
			
		||||
		this.following = Object.keys(await tfrpc.rpc.following(ids, 1)).sort();
 | 
			
		||||
 | 
			
		||||
@@ -276,6 +273,9 @@ class TfCollectionsAppElement extends LitElement {
 | 
			
		||||
					margin-right: 16px;
 | 
			
		||||
				}
 | 
			
		||||
			</style>
 | 
			
		||||
			<div>
 | 
			
		||||
				<tf-id-picker .ids=${this.ids} selected=${this.whoami} @change=${this.on_whoami_changed} ?hidden=${!this.ids?.length}></tf-id-picker>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div>
 | 
			
		||||
				${keyed(
 | 
			
		||||
					this.whoami,
 | 
			
		||||
 
 | 
			
		||||
@@ -96,7 +96,7 @@ export async function collection(
 | 
			
		||||
		let rows = [];
 | 
			
		||||
		await ssb.sqlAsync(
 | 
			
		||||
			`
 | 
			
		||||
				SELECT messages.id, author, json(content) AS content, timestamp
 | 
			
		||||
				SELECT messages.id, author, content, timestamp
 | 
			
		||||
				FROM messages
 | 
			
		||||
				JOIN json_each(?1) AS id ON messages.author = id.value
 | 
			
		||||
				WHERE
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bleh.tar.xz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bleh.tar.xz
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										38
									
								
								core/app.js
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								core/app.js
									
									
									
									
									
								
							@@ -1,3 +1,4 @@
 | 
			
		||||
import * as auth from './auth.js';
 | 
			
		||||
import * as core from './core.js';
 | 
			
		||||
 | 
			
		||||
let g_next_id = 1;
 | 
			
		||||
@@ -86,7 +87,8 @@ App.prototype.send = function (message) {
 | 
			
		||||
function socket(request, response, client) {
 | 
			
		||||
	let process;
 | 
			
		||||
	let options = {};
 | 
			
		||||
	let credentials = httpd.auth_query(request.headers);
 | 
			
		||||
	let credentials = auth.query(request.headers);
 | 
			
		||||
	let refresh = auth.makeRefresh(credentials);
 | 
			
		||||
 | 
			
		||||
	response.onClose = async function () {
 | 
			
		||||
		if (process && process.task) {
 | 
			
		||||
@@ -141,21 +143,12 @@ function socket(request, response, client) {
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				response.send(
 | 
			
		||||
					JSON.stringify(
 | 
			
		||||
						Object.assign(
 | 
			
		||||
							{
 | 
			
		||||
								action: 'session',
 | 
			
		||||
								credentials: credentials,
 | 
			
		||||
								parentApp: parentApp,
 | 
			
		||||
								id: blobId,
 | 
			
		||||
							},
 | 
			
		||||
							await ssb.getIdentityInfo(
 | 
			
		||||
								credentials?.session?.name,
 | 
			
		||||
								packageOwner,
 | 
			
		||||
								packageName
 | 
			
		||||
							)
 | 
			
		||||
						)
 | 
			
		||||
					),
 | 
			
		||||
					JSON.stringify({
 | 
			
		||||
						action: 'session',
 | 
			
		||||
						credentials: credentials,
 | 
			
		||||
						parentApp: parentApp,
 | 
			
		||||
						id: blobId,
 | 
			
		||||
					}),
 | 
			
		||||
					0x1
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
@@ -219,10 +212,6 @@ function socket(request, response, client) {
 | 
			
		||||
				if (process) {
 | 
			
		||||
					process.resetPermission(message.permission);
 | 
			
		||||
				}
 | 
			
		||||
			} else if (message.action == 'setActiveIdentity') {
 | 
			
		||||
				process.setActiveIdentity(message.identity);
 | 
			
		||||
			} else if (message.action == 'createIdentity') {
 | 
			
		||||
				process.createIdentity();
 | 
			
		||||
			} else if (message.message == 'tfrpc') {
 | 
			
		||||
				if (message.id && g_calls[message.id]) {
 | 
			
		||||
					if (message.error !== undefined) {
 | 
			
		||||
@@ -252,7 +241,14 @@ function socket(request, response, client) {
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	response.upgrade(100, {});
 | 
			
		||||
	response.upgrade(
 | 
			
		||||
		100,
 | 
			
		||||
		refresh
 | 
			
		||||
			? {
 | 
			
		||||
					'Set-Cookie': `session=${refresh.token}; path=/; Max-Age=${refresh.interval}; Secure; SameSite=Strict`,
 | 
			
		||||
				}
 | 
			
		||||
			: {}
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {socket, App};
 | 
			
		||||
 
 | 
			
		||||
@@ -19,11 +19,8 @@
 | 
			
		||||
			Object.assign(app, g_data);
 | 
			
		||||
 | 
			
		||||
			class TfAuthElement extends LitElement {
 | 
			
		||||
				static get properties() {
 | 
			
		||||
				static get_properties() {
 | 
			
		||||
					return {
 | 
			
		||||
						code_of_conduct: {type: String},
 | 
			
		||||
						error: {type: String},
 | 
			
		||||
						have_administrator: {type: Boolean},
 | 
			
		||||
						name: {type: String},
 | 
			
		||||
						tab: {type: String},
 | 
			
		||||
					};
 | 
			
		||||
@@ -34,6 +31,11 @@
 | 
			
		||||
					this.tab = 'login';
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				tab_changed(name) {
 | 
			
		||||
					this.tab = name;
 | 
			
		||||
					this.requestUpdate();
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				render() {
 | 
			
		||||
					let self = this;
 | 
			
		||||
					return html`
 | 
			
		||||
@@ -81,16 +83,16 @@
 | 
			
		||||
							<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 = 'login')}></input>
 | 
			
		||||
								<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 = 'register')}></input>
 | 
			
		||||
								<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 = 'guest')}></input>
 | 
			
		||||
								<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>
 | 
			
		||||
 | 
			
		||||
								<input type="radio" name="tab" id="change" value="Change Password" ?checked=${this.tab == 'change'} @change=${() => (self.tab = 'change')}></input>
 | 
			
		||||
								<input type="radio" name="tab" id="change" value="Change Password" ?checked=${this.tab == 'change'} @change=${() => self.tab_changed('change')}></input>
 | 
			
		||||
								<label for="change" id="change_label">Change Password</label>
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										420
									
								
								core/auth.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										420
									
								
								core/auth.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,420 @@
 | 
			
		||||
import * as core from './core.js';
 | 
			
		||||
import * as form from './form.js';
 | 
			
		||||
 | 
			
		||||
let gDatabase = new Database('auth');
 | 
			
		||||
 | 
			
		||||
const kRefreshInterval = 1 * 7 * 24 * 60 * 60 * 1000;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Makes a Base64 value URL safe
 | 
			
		||||
 * @param {string} value
 | 
			
		||||
 * @returns TODOC
 | 
			
		||||
 */
 | 
			
		||||
function b64url(value) {
 | 
			
		||||
	value = value.replaceAll('+', '-').replaceAll('/', '_');
 | 
			
		||||
	let equals = value.indexOf('=');
 | 
			
		||||
 | 
			
		||||
	if (equals !== -1) {
 | 
			
		||||
		return value.substring(0, equals);
 | 
			
		||||
	} else {
 | 
			
		||||
		return value;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * TODOC
 | 
			
		||||
 * @param {string} value
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
function unb64url(value) {
 | 
			
		||||
	value = value.replaceAll('-', '+').replaceAll('_', '/');
 | 
			
		||||
	let remainder = value.length % 4;
 | 
			
		||||
 | 
			
		||||
	if (remainder == 3) {
 | 
			
		||||
		return value + '=';
 | 
			
		||||
	} else if (remainder == 2) {
 | 
			
		||||
		return value + '==';
 | 
			
		||||
	} else {
 | 
			
		||||
		return value;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Creates a JSON Web Token
 | 
			
		||||
 * @param {object} payload Object: {"name": "username"}
 | 
			
		||||
 * @returns the JWT
 | 
			
		||||
 */
 | 
			
		||||
function makeJwt(payload) {
 | 
			
		||||
	const ids = ssb.getIdentities(':auth');
 | 
			
		||||
	let id;
 | 
			
		||||
 | 
			
		||||
	if (ids?.length) {
 | 
			
		||||
		id = ids[0];
 | 
			
		||||
	} else {
 | 
			
		||||
		id = ssb.createIdentity(':auth');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const final_payload = b64url(
 | 
			
		||||
		base64Encode(
 | 
			
		||||
			JSON.stringify(
 | 
			
		||||
				Object.assign({}, payload, {
 | 
			
		||||
					exp: new Date().valueOf() + kRefreshInterval,
 | 
			
		||||
				})
 | 
			
		||||
			)
 | 
			
		||||
		)
 | 
			
		||||
	);
 | 
			
		||||
	const jwt = [
 | 
			
		||||
		b64url(base64Encode(JSON.stringify({alg: 'HS256', typ: 'JWT'}))),
 | 
			
		||||
		final_payload,
 | 
			
		||||
		b64url(ssb.hmacsha256sign(final_payload, ':auth', id)),
 | 
			
		||||
	].join('.');
 | 
			
		||||
	return jwt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Validates a JWT ?
 | 
			
		||||
 * @param {*} session TODOC
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
function readSession(session) {
 | 
			
		||||
	let jwt_parts = session?.split('.');
 | 
			
		||||
 | 
			
		||||
	if (jwt_parts?.length === 3) {
 | 
			
		||||
		let [header, payload, signature] = jwt_parts;
 | 
			
		||||
		header = JSON.parse(utf8Decode(base64Decode(unb64url(header))));
 | 
			
		||||
 | 
			
		||||
		if (header.typ === 'JWT' && header.alg === 'HS256') {
 | 
			
		||||
			signature = unb64url(signature);
 | 
			
		||||
			let id = ssb.getIdentities(':auth');
 | 
			
		||||
 | 
			
		||||
			if (id?.length && ssb.hmacsha256verify(id[0], payload, signature)) {
 | 
			
		||||
				const result = JSON.parse(utf8Decode(base64Decode(unb64url(payload))));
 | 
			
		||||
				const now = new Date().valueOf();
 | 
			
		||||
 | 
			
		||||
				if (now < result.exp) {
 | 
			
		||||
					print(`JWT valid for another ${(result.exp - now) / 1000} seconds.`);
 | 
			
		||||
					return result;
 | 
			
		||||
				} else {
 | 
			
		||||
					print(`JWT expired by ${(now - result.exp) / 1000} seconds.`);
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				print('JWT verification failed.');
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			print('Invalid JWT header.');
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Check the provided password matches the hash
 | 
			
		||||
 * @param {string} password
 | 
			
		||||
 * @param {string} hash bcrypt hash
 | 
			
		||||
 * @returns true if the password matches the hash
 | 
			
		||||
 */
 | 
			
		||||
function verifyPassword(password, hash) {
 | 
			
		||||
	return bCrypt.hashpw(password, hash) === hash;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hashes a password
 | 
			
		||||
 * @param {string} password
 | 
			
		||||
 * @returns {string} TODOC
 | 
			
		||||
 */
 | 
			
		||||
function hashPassword(password) {
 | 
			
		||||
	let salt = bCrypt.gensalt(12);
 | 
			
		||||
	return bCrypt.hashpw(password, salt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Check if there is an administrator on the instance
 | 
			
		||||
 * @returns TODOC
 | 
			
		||||
 */
 | 
			
		||||
function noAdministrator() {
 | 
			
		||||
	return (
 | 
			
		||||
		!core.globalSettings ||
 | 
			
		||||
		!core.globalSettings.permissions ||
 | 
			
		||||
		!Object.keys(core.globalSettings.permissions).some(function (name) {
 | 
			
		||||
			return (
 | 
			
		||||
				core.globalSettings.permissions[name].indexOf('administration') != -1
 | 
			
		||||
			);
 | 
			
		||||
		})
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Makes a user an administrator
 | 
			
		||||
 * @param {string} name the user's name
 | 
			
		||||
 */
 | 
			
		||||
function makeAdministrator(name) {
 | 
			
		||||
	if (!core.globalSettings.permissions) {
 | 
			
		||||
		core.globalSettings.permissions = {};
 | 
			
		||||
	}
 | 
			
		||||
	if (!core.globalSettings.permissions[name]) {
 | 
			
		||||
		core.globalSettings.permissions[name] = [];
 | 
			
		||||
	}
 | 
			
		||||
	if (core.globalSettings.permissions[name].indexOf('administration') == -1) {
 | 
			
		||||
		core.globalSettings.permissions[name].push('administration');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	core.setGlobalSettings(core.globalSettings);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * TODOC
 | 
			
		||||
 * @param {*} headers most likely an object
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
function getCookies(headers) {
 | 
			
		||||
	let cookies = {};
 | 
			
		||||
 | 
			
		||||
	if (headers.cookie) {
 | 
			
		||||
		let parts = headers.cookie.split(/,|;/);
 | 
			
		||||
		for (let i in parts) {
 | 
			
		||||
			let equals = parts[i].indexOf('=');
 | 
			
		||||
			let name = parts[i].substring(0, equals).trim();
 | 
			
		||||
			let value = parts[i].substring(equals + 1).trim();
 | 
			
		||||
			cookies[name] = value;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cookies;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Validates a username
 | 
			
		||||
 * @param {string} name
 | 
			
		||||
 * @returns false | boolean[] ?
 | 
			
		||||
 */
 | 
			
		||||
function isNameValid(name) {
 | 
			
		||||
	// TODO(tasiaiso): convert this into a regex
 | 
			
		||||
	let c = name.charAt(0);
 | 
			
		||||
	return (
 | 
			
		||||
		((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) &&
 | 
			
		||||
		name
 | 
			
		||||
			.split()
 | 
			
		||||
			.map(
 | 
			
		||||
				(x) =>
 | 
			
		||||
					x >= ('a' && x <= 'z') ||
 | 
			
		||||
					x >= ('A' && x <= 'Z') ||
 | 
			
		||||
					x >= ('0' && x <= '9')
 | 
			
		||||
			)
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Request handler ?
 | 
			
		||||
 * @param {*} request TODOC
 | 
			
		||||
 * @param {*} response
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
function handler(request, response) {
 | 
			
		||||
	// TODO(tasiaiso): split this function
 | 
			
		||||
	let session = getCookies(request.headers).session;
 | 
			
		||||
	if (request.uri == '/login') {
 | 
			
		||||
		let formData = form.decodeForm(request.query);
 | 
			
		||||
		if (query(request.headers)?.permissions?.authenticated) {
 | 
			
		||||
			if (formData.return) {
 | 
			
		||||
				response.writeHead(303, {Location: formData.return});
 | 
			
		||||
			} else {
 | 
			
		||||
				response.writeHead(303, {
 | 
			
		||||
					Location:
 | 
			
		||||
						(request.client.tls ? 'https://' : 'http://') +
 | 
			
		||||
						request.headers.host +
 | 
			
		||||
						'/',
 | 
			
		||||
					'Content-Length': '0',
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
			response.end();
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		let sessionIsNew = false;
 | 
			
		||||
		let loginError;
 | 
			
		||||
 | 
			
		||||
		if (request.method == 'POST' || formData.submit) {
 | 
			
		||||
			sessionIsNew = true;
 | 
			
		||||
			formData = form.decodeForm(utf8Decode(request.body), formData);
 | 
			
		||||
			if (formData.submit == 'Login') {
 | 
			
		||||
				let account = gDatabase.get('user:' + formData.name);
 | 
			
		||||
				account = account ? JSON.parse(account) : account;
 | 
			
		||||
				if (formData.register == '1') {
 | 
			
		||||
					if (
 | 
			
		||||
						!account &&
 | 
			
		||||
						isNameValid(formData.name) &&
 | 
			
		||||
						formData.password == formData.confirm
 | 
			
		||||
					) {
 | 
			
		||||
						let users = new Set();
 | 
			
		||||
						let users_original = gDatabase.get('users');
 | 
			
		||||
						try {
 | 
			
		||||
							users = new Set(JSON.parse(users_original));
 | 
			
		||||
						} catch {}
 | 
			
		||||
						if (!users.has(formData.name)) {
 | 
			
		||||
							users.add(formData.name);
 | 
			
		||||
						}
 | 
			
		||||
						users = JSON.stringify([...users].sort());
 | 
			
		||||
						if (users !== users_original) {
 | 
			
		||||
							gDatabase.set('users', users);
 | 
			
		||||
						}
 | 
			
		||||
						session = makeJwt({name: formData.name});
 | 
			
		||||
						account = {password: hashPassword(formData.password)};
 | 
			
		||||
						gDatabase.set('user:' + formData.name, JSON.stringify(account));
 | 
			
		||||
						if (noAdministrator()) {
 | 
			
		||||
							makeAdministrator(formData.name);
 | 
			
		||||
						}
 | 
			
		||||
					} else {
 | 
			
		||||
						loginError = 'Error registering account.';
 | 
			
		||||
					}
 | 
			
		||||
				} else if (formData.change == '1') {
 | 
			
		||||
					if (
 | 
			
		||||
						account &&
 | 
			
		||||
						isNameValid(formData.name) &&
 | 
			
		||||
						formData.new_password == formData.confirm &&
 | 
			
		||||
						verifyPassword(formData.password, account.password)
 | 
			
		||||
					) {
 | 
			
		||||
						session = makeJwt({name: formData.name});
 | 
			
		||||
						account = {password: hashPassword(formData.new_password)};
 | 
			
		||||
						gDatabase.set('user:' + formData.name, JSON.stringify(account));
 | 
			
		||||
					} else {
 | 
			
		||||
						loginError = 'Error changing password.';
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					if (
 | 
			
		||||
						account &&
 | 
			
		||||
						account.password &&
 | 
			
		||||
						verifyPassword(formData.password, account.password)
 | 
			
		||||
					) {
 | 
			
		||||
						session = makeJwt({name: formData.name});
 | 
			
		||||
						if (noAdministrator()) {
 | 
			
		||||
							makeAdministrator(formData.name);
 | 
			
		||||
						}
 | 
			
		||||
					} else {
 | 
			
		||||
						loginError = 'Invalid username or password.';
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				// Proceed as Guest
 | 
			
		||||
				session = makeJwt({name: 'guest'});
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		let cookie = `session=${session}; path=/; Max-Age=${kRefreshInterval}; ${request.client.tls ? 'Secure; ' : ''}SameSite=Strict; HttpOnly`;
 | 
			
		||||
		let entry = readSession(session);
 | 
			
		||||
		if (entry && formData.return) {
 | 
			
		||||
			response.writeHead(303, {
 | 
			
		||||
				Location: formData.return,
 | 
			
		||||
				'Set-Cookie': cookie,
 | 
			
		||||
			});
 | 
			
		||||
			response.end();
 | 
			
		||||
		} else {
 | 
			
		||||
			File.readFile('core/auth.html')
 | 
			
		||||
				.then(function (data) {
 | 
			
		||||
					let html = utf8Decode(data);
 | 
			
		||||
					let auth_data = {
 | 
			
		||||
						session_is_new: sessionIsNew,
 | 
			
		||||
						name: entry?.name,
 | 
			
		||||
						error: loginError,
 | 
			
		||||
						code_of_conduct: core.globalSettings.code_of_conduct,
 | 
			
		||||
						have_administrator: !noAdministrator(),
 | 
			
		||||
					};
 | 
			
		||||
					html = utf8Encode(
 | 
			
		||||
						html.replace('$AUTH_DATA', JSON.stringify(auth_data))
 | 
			
		||||
					);
 | 
			
		||||
					response.writeHead(200, {
 | 
			
		||||
						'Content-Type': 'text/html; charset=utf-8',
 | 
			
		||||
						'Set-Cookie': cookie,
 | 
			
		||||
						'Content-Length': html.length,
 | 
			
		||||
					});
 | 
			
		||||
					response.end(html);
 | 
			
		||||
				})
 | 
			
		||||
				.catch(function (error) {
 | 
			
		||||
					response.writeHead(404, {
 | 
			
		||||
						'Content-Type': 'text/plain; charset=utf-8',
 | 
			
		||||
						Connection: 'close',
 | 
			
		||||
					});
 | 
			
		||||
					response.end('404 File not found');
 | 
			
		||||
				});
 | 
			
		||||
		}
 | 
			
		||||
	} else if (request.uri == '/login/logout') {
 | 
			
		||||
		response.writeHead(303, {
 | 
			
		||||
			'Set-Cookie': `session=; path=/; ${request.client.tls ? 'Secure; ' : ''}SameSite=Strict; expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly`,
 | 
			
		||||
			Location: '/login' + (request.query ? '?' + request.query : ''),
 | 
			
		||||
		});
 | 
			
		||||
		response.end();
 | 
			
		||||
	} else {
 | 
			
		||||
		response.writeHead(200, {
 | 
			
		||||
			'Content-Type': 'text/plain; charset=utf-8',
 | 
			
		||||
			Connection: 'close',
 | 
			
		||||
		});
 | 
			
		||||
		response.end('Hello, ' + request.client.peerName + '.');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets a user's permissions based on it's session ?
 | 
			
		||||
 * @param {*} session TODOC
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
function getPermissions(session) {
 | 
			
		||||
	let permissions;
 | 
			
		||||
	let entry = readSession(session);
 | 
			
		||||
	if (entry) {
 | 
			
		||||
		permissions = getPermissionsForUser(entry.name);
 | 
			
		||||
		permissions.authenticated = entry.name !== 'guest';
 | 
			
		||||
	}
 | 
			
		||||
	return permissions || {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get a user's permissions ?
 | 
			
		||||
 * @param {string} userName TODOC
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
function getPermissionsForUser(userName) {
 | 
			
		||||
	let permissions = {};
 | 
			
		||||
	if (
 | 
			
		||||
		core.globalSettings &&
 | 
			
		||||
		core.globalSettings.permissions &&
 | 
			
		||||
		core.globalSettings.permissions[userName]
 | 
			
		||||
	) {
 | 
			
		||||
		for (let i in core.globalSettings.permissions[userName]) {
 | 
			
		||||
			permissions[core.globalSettings.permissions[userName][i]] = true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return permissions;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * TODOC
 | 
			
		||||
 * @param {*} headers
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
function query(headers) {
 | 
			
		||||
	let session = getCookies(headers).session;
 | 
			
		||||
	let entry;
 | 
			
		||||
	let autologin = tildefriends.args.autologin;
 | 
			
		||||
	if ((entry = autologin ? {name: autologin} : readSession(session))) {
 | 
			
		||||
		return {
 | 
			
		||||
			session: entry,
 | 
			
		||||
			permissions: autologin
 | 
			
		||||
				? getPermissionsForUser(autologin)
 | 
			
		||||
				: getPermissions(session),
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Refreshes a JWT ?
 | 
			
		||||
 * @param {*} credentials TODOC
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
function makeRefresh(credentials) {
 | 
			
		||||
	if (credentials?.session?.name) {
 | 
			
		||||
		return {
 | 
			
		||||
			token: makeJwt({name: credentials.session.name}),
 | 
			
		||||
			interval: kRefreshInterval,
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {handler, query, makeRefresh};
 | 
			
		||||
							
								
								
									
										209
									
								
								core/client.js
									
									
									
									
									
								
							
							
						
						
									
										209
									
								
								core/client.js
									
									
									
									
									
								
							@@ -56,9 +56,6 @@ class TfNavigationElement extends LitElement {
 | 
			
		||||
			spark_lines: {type: Object},
 | 
			
		||||
			version: {type: Object},
 | 
			
		||||
			show_version: {type: Boolean},
 | 
			
		||||
			identity: {type: String},
 | 
			
		||||
			identities: {type: Array},
 | 
			
		||||
			names: {type: Object},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -68,8 +65,6 @@ class TfNavigationElement extends LitElement {
 | 
			
		||||
		this.show_permissions = false;
 | 
			
		||||
		this.status = {};
 | 
			
		||||
		this.spark_lines = {};
 | 
			
		||||
		this.identities = [];
 | 
			
		||||
		this.names = {};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
@@ -102,10 +97,10 @@ class TfNavigationElement extends LitElement {
 | 
			
		||||
	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 50 5em';
 | 
			
		||||
			spark_line.title = key;
 | 
			
		||||
			spark_line.classList.add('w3-bar-item');
 | 
			
		||||
			spark_line.classList.add('w3-hide-small');
 | 
			
		||||
			spark_line.style.paddingRight = '0';
 | 
			
		||||
			if (options) {
 | 
			
		||||
				if (options.max) {
 | 
			
		||||
					spark_line.max = options.max;
 | 
			
		||||
@@ -123,105 +118,16 @@ class TfNavigationElement extends LitElement {
 | 
			
		||||
	 */
 | 
			
		||||
	render_login() {
 | 
			
		||||
		if (this?.credentials?.session?.name) {
 | 
			
		||||
			return html`<a
 | 
			
		||||
				class="w3-bar-item w3-right"
 | 
			
		||||
				id="login"
 | 
			
		||||
				href="/login/logout?return=${url() + hash()}"
 | 
			
		||||
			return html`<a id="login" href="/login/logout?return=${url() + hash()}"
 | 
			
		||||
				>logout ${this.credentials.session.name}</a
 | 
			
		||||
			>`;
 | 
			
		||||
		} else {
 | 
			
		||||
			return html`<a
 | 
			
		||||
				class="w3-bar-item w3-right"
 | 
			
		||||
				id="login"
 | 
			
		||||
				href="/login?return=${url() + hash()}"
 | 
			
		||||
			return html`<a id="login" href="/login?return=${url() + hash()}"
 | 
			
		||||
				>login</a
 | 
			
		||||
			>`;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	set_active_identity(id) {
 | 
			
		||||
		send({action: 'setActiveIdentity', identity: id});
 | 
			
		||||
		this.renderRoot.getElementById('id_dropdown').classList.remove('w3-show');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	create_identity(event) {
 | 
			
		||||
		if (confirm('Are you sure you want to create a new identity?')) {
 | 
			
		||||
			send({action: 'createIdentity'});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	toggle_id_dropdown() {
 | 
			
		||||
		this.renderRoot.getElementById('id_dropdown').classList.toggle('w3-show');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	edit_profile() {
 | 
			
		||||
		window.location.href = '/~core/ssb/#' + this.identity;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render_identity() {
 | 
			
		||||
		let self = this;
 | 
			
		||||
		if (this.identities?.length) {
 | 
			
		||||
			return html`
 | 
			
		||||
				<link type="text/css" rel="stylesheet" href="/static/w3.css" />
 | 
			
		||||
				<div class="w3-dropdown-click w3-right" style="max-width: 100%">
 | 
			
		||||
					<button
 | 
			
		||||
						class="w3-button w3-rest w3-cyan"
 | 
			
		||||
						style="text-overflow: ellipsis; overflow: hidden; white-space: nowrap; max-width: 100%"
 | 
			
		||||
						@click=${self.toggle_id_dropdown}
 | 
			
		||||
					>
 | 
			
		||||
						${self.names[this.identity]}${self.names[this.identity] ===
 | 
			
		||||
						this.identity
 | 
			
		||||
							? ''
 | 
			
		||||
							: html` - ${this.identity}`}
 | 
			
		||||
						▾
 | 
			
		||||
					</button>
 | 
			
		||||
					<div
 | 
			
		||||
						id="id_dropdown"
 | 
			
		||||
						class="w3-dropdown-content w3-bar-block w3-card-4"
 | 
			
		||||
						style="max-width: 100%"
 | 
			
		||||
					>
 | 
			
		||||
						<button
 | 
			
		||||
							class="w3-bar-item w3-button w3-border"
 | 
			
		||||
							@click=${() => (window.location.href = '/~core/identity')}
 | 
			
		||||
						>
 | 
			
		||||
							Manage Identities...
 | 
			
		||||
						</button>
 | 
			
		||||
						<button
 | 
			
		||||
							class="w3-bar-item w3-button w3-border"
 | 
			
		||||
							@click=${self.edit_profile}
 | 
			
		||||
						>
 | 
			
		||||
							Edit Profile...
 | 
			
		||||
						</button>
 | 
			
		||||
						${this.identities.map(
 | 
			
		||||
							(x) => html`
 | 
			
		||||
								<button
 | 
			
		||||
									class="w3-bar-item w3-button ${x === self.identity
 | 
			
		||||
										? 'w3-cyan'
 | 
			
		||||
										: ''}"
 | 
			
		||||
									@click=${() => self.set_active_identity(x)}
 | 
			
		||||
									style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap"
 | 
			
		||||
								>
 | 
			
		||||
									${self.names[x]}${self.names[x] === x ? '' : html` - ${x}`}
 | 
			
		||||
								</button>
 | 
			
		||||
							`
 | 
			
		||||
						)}
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			`;
 | 
			
		||||
		} else {
 | 
			
		||||
			return html`
 | 
			
		||||
				<link type="text/css" rel="stylesheet" href="/static/w3.css" />
 | 
			
		||||
				<button
 | 
			
		||||
					id="create_identity"
 | 
			
		||||
					@click=${this.create_identity}
 | 
			
		||||
					class="w3-button w3-mobile w3-blue w3-right"
 | 
			
		||||
				>
 | 
			
		||||
					Create an Identity
 | 
			
		||||
				</button>
 | 
			
		||||
			`;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * TODOC
 | 
			
		||||
	 * @returns
 | 
			
		||||
@@ -239,17 +145,11 @@ class TfNavigationElement extends LitElement {
 | 
			
		||||
						<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)}
 | 
			
		||||
										class="w3-button w3-red"
 | 
			
		||||
									>
 | 
			
		||||
										Reset
 | 
			
		||||
									</button>
 | 
			
		||||
								</div>
 | 
			
		||||
							`
 | 
			
		||||
							<div>
 | 
			
		||||
								<span>${key}</span>: ${this.permissions[key] ? '✅ Allowed' : '❌ Denied'}
 | 
			
		||||
								<button @click=${() => this.reset_permission(key)} class='w3-button w3-red">Reset</button>
 | 
			
		||||
							</div>
 | 
			
		||||
						`
 | 
			
		||||
						)}
 | 
			
		||||
						<button
 | 
			
		||||
							@click=${() => (this.show_permissions = false)}
 | 
			
		||||
@@ -263,10 +163,6 @@ class TfNavigationElement extends LitElement {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clear_error() {
 | 
			
		||||
		this.status = {};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * TODOC
 | 
			
		||||
	 * @returns
 | 
			
		||||
@@ -274,7 +170,6 @@ class TfNavigationElement extends LitElement {
 | 
			
		||||
	render() {
 | 
			
		||||
		let self = this;
 | 
			
		||||
		return html`
 | 
			
		||||
			<link type="text/css" rel="stylesheet" href="/static/w3.css" />
 | 
			
		||||
			<style>
 | 
			
		||||
				${k_global_style} .tooltip {
 | 
			
		||||
					position: absolute;
 | 
			
		||||
@@ -290,17 +185,17 @@ class TfNavigationElement extends LitElement {
 | 
			
		||||
					display: inline-block;
 | 
			
		||||
				}
 | 
			
		||||
			</style>
 | 
			
		||||
			<div class="w3-black w3-bar">
 | 
			
		||||
			<div
 | 
			
		||||
				style="margin: 4px; display: flex; flex-direction: row; flex-wrap: nowrap; gap: 3px; align-items: center"
 | 
			
		||||
			>
 | 
			
		||||
				<span
 | 
			
		||||
					class="w3-bar-item"
 | 
			
		||||
					style="cursor: pointer"
 | 
			
		||||
					@click=${() => (this.show_version = !this.show_version)}
 | 
			
		||||
					>😎</span
 | 
			
		||||
				>
 | 
			
		||||
				<span
 | 
			
		||||
					class="w3-bar-item"
 | 
			
		||||
					style=${'white-space: nowrap' +
 | 
			
		||||
					(this.show_version ? '' : '; display: none')}
 | 
			
		||||
					?hidden=${!this.show_version}
 | 
			
		||||
					style="flex: 0 0; white-space: nowrap"
 | 
			
		||||
					title=${this.version?.name +
 | 
			
		||||
					' ' +
 | 
			
		||||
					Object.entries(this.version || {})
 | 
			
		||||
@@ -309,7 +204,6 @@ class TfNavigationElement extends LitElement {
 | 
			
		||||
					>${this.version?.number}</span
 | 
			
		||||
				>
 | 
			
		||||
				<a
 | 
			
		||||
					class="w3-bar-item"
 | 
			
		||||
					accesskey="h"
 | 
			
		||||
					@mouseover=${set_access_key_title}
 | 
			
		||||
					data-tip="Open home app."
 | 
			
		||||
@@ -318,7 +212,6 @@ class TfNavigationElement extends LitElement {
 | 
			
		||||
					>TF</a
 | 
			
		||||
				>
 | 
			
		||||
				<a
 | 
			
		||||
					class="w3-bar-item"
 | 
			
		||||
					accesskey="a"
 | 
			
		||||
					@mouseover=${set_access_key_title}
 | 
			
		||||
					data-tip="Open apps list."
 | 
			
		||||
@@ -326,7 +219,6 @@ class TfNavigationElement extends LitElement {
 | 
			
		||||
					>apps</a
 | 
			
		||||
				>
 | 
			
		||||
				<a
 | 
			
		||||
					class="w3-bar-item"
 | 
			
		||||
					accesskey="e"
 | 
			
		||||
					@mouseover=${set_access_key_title}
 | 
			
		||||
					data-tip="Toggle the app editor."
 | 
			
		||||
@@ -335,7 +227,6 @@ class TfNavigationElement extends LitElement {
 | 
			
		||||
					>edit</a
 | 
			
		||||
				>
 | 
			
		||||
				<a
 | 
			
		||||
					class="w3-bar-item"
 | 
			
		||||
					accesskey="p"
 | 
			
		||||
					@mouseover=${set_access_key_title}
 | 
			
		||||
					data-tip="View and change permissions."
 | 
			
		||||
@@ -343,34 +234,27 @@ class TfNavigationElement extends LitElement {
 | 
			
		||||
					@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()}
 | 
			
		||||
				${this.status?.message && !this.status.is_error
 | 
			
		||||
					? html`
 | 
			
		||||
							<link type="text/css" rel="stylesheet" href="/static/w3.css" />
 | 
			
		||||
							<div
 | 
			
		||||
								class="w3-bar-item"
 | 
			
		||||
								style="color: ${this.status.color ?? kStatusColor}"
 | 
			
		||||
							>
 | 
			
		||||
								${this.status.message}
 | 
			
		||||
							</div>
 | 
			
		||||
						`
 | 
			
		||||
					: undefined}
 | 
			
		||||
				${Object.keys(this.spark_lines)
 | 
			
		||||
					.sort()
 | 
			
		||||
					.map((x) => this.spark_lines[x])}
 | 
			
		||||
				${this.render_login()} ${this.render_identity()}
 | 
			
		||||
				<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) => [
 | 
			
		||||
							html`<span style="font-size: xx-small">${x.dataset.emoji}</span>`,
 | 
			
		||||
							x,
 | 
			
		||||
						])}</span
 | 
			
		||||
				>
 | 
			
		||||
				<span style="flex: 0 0; white-space: nowrap"
 | 
			
		||||
					>${this.render_login()}</span
 | 
			
		||||
				>
 | 
			
		||||
			</div>
 | 
			
		||||
			${this.status?.is_error
 | 
			
		||||
				? html`
 | 
			
		||||
					<link type="text/css" rel="stylesheet" href="/static/w3.css" />
 | 
			
		||||
					<div class="w3-model w3-animate-top" style="position: absolute; left: 50%; transform: translate(-50%); z-index: 1">
 | 
			
		||||
						<dijv class="w3-modal-content w3-card-4" style="display: block; padding: 1em">
 | 
			
		||||
							<span @click=${self.clear_error} class="w3-button w3-display-topright">×</span>
 | 
			
		||||
							<div style="color: ${this.status.color ?? kErrorColor}"><b>ERROR:</b><p style="white-space: pre">${this.status.message}</p></div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					`
 | 
			
		||||
				: undefined}
 | 
			
		||||
		`;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -694,13 +578,13 @@ class TfSparkLineElement extends LitElement {
 | 
			
		||||
			) / 10.0;
 | 
			
		||||
		return html`
 | 
			
		||||
			<svg
 | 
			
		||||
				style="max-width: 7.5em; margin: 0; padding: 0; background: #000; height: 1em"
 | 
			
		||||
				style="max-width: 7.5em; max-height: 1.5em; margin: 0; padding: 0; background: #000"
 | 
			
		||||
				viewBox="0 0 50 10"
 | 
			
		||||
				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">
 | 
			
		||||
					${this.dataset.emoji}${max}
 | 
			
		||||
					${max}
 | 
			
		||||
				</text>
 | 
			
		||||
			</svg>
 | 
			
		||||
		`;
 | 
			
		||||
@@ -1116,9 +1000,9 @@ function api_postMessage(message) {
 | 
			
		||||
function api_error(error) {
 | 
			
		||||
	if (error) {
 | 
			
		||||
		if (typeof error == 'string') {
 | 
			
		||||
			setStatusMessage('⚠️ ' + error, kErrorColor);
 | 
			
		||||
			setStatusMessage('⚠️ ' + error, '#f00');
 | 
			
		||||
		} else {
 | 
			
		||||
			setStatusMessage('⚠️ ' + error.message + '\n' + error.stack, kErrorColor);
 | 
			
		||||
			setStatusMessage('⚠️ ' + error.message + '\n' + error.stack, '#f00');
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	console.log('error', error);
 | 
			
		||||
@@ -1235,19 +1119,11 @@ function api_setHash(hash) {
 | 
			
		||||
function _receive_websocket_message(message) {
 | 
			
		||||
	if (message && message.action == 'session') {
 | 
			
		||||
		setStatusMessage('🟢 Executing...', kStatusColor);
 | 
			
		||||
		let navigation = document.getElementsByTagName('tf-navigation')[0];
 | 
			
		||||
		navigation.credentials = message.credentials;
 | 
			
		||||
		navigation.identities = message.identities;
 | 
			
		||||
		navigation.identity = message.identity;
 | 
			
		||||
		navigation.names = message.names;
 | 
			
		||||
		document.getElementsByTagName('tf-navigation')[0].credentials =
 | 
			
		||||
			message.credentials;
 | 
			
		||||
	} else if (message && message.action == 'permissions') {
 | 
			
		||||
		let navigation = document.getElementsByTagName('tf-navigation')[0];
 | 
			
		||||
		navigation.permissions = message.permissions ?? {};
 | 
			
		||||
	} else if (message && message.action == 'identities') {
 | 
			
		||||
		let navigation = document.getElementsByTagName('tf-navigation')[0];
 | 
			
		||||
		navigation.identities = message.identities;
 | 
			
		||||
		navigation.identity = message.identity;
 | 
			
		||||
		navigation.names = message.names;
 | 
			
		||||
		document.getElementsByTagName('tf-navigation')[0].permissions =
 | 
			
		||||
			message.permissions ?? {};
 | 
			
		||||
	} else if (message && message.action == 'ready') {
 | 
			
		||||
		setStatusMessage(null);
 | 
			
		||||
		if (window.location.hash) {
 | 
			
		||||
@@ -1335,7 +1211,6 @@ function setStatusMessage(message, color) {
 | 
			
		||||
	document.getElementsByTagName('tf-navigation')[0].status = {
 | 
			
		||||
		message: message,
 | 
			
		||||
		color: color,
 | 
			
		||||
		is_error: color == kErrorColor,
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										156
									
								
								core/core.js
									
									
									
									
									
								
							
							
						
						
									
										156
									
								
								core/core.js
									
									
									
									
									
								
							@@ -1,4 +1,5 @@
 | 
			
		||||
import * as app from './app.js';
 | 
			
		||||
import * as auth from './auth.js';
 | 
			
		||||
import * as form from './form.js';
 | 
			
		||||
import * as http from './http.js';
 | 
			
		||||
 | 
			
		||||
@@ -244,7 +245,6 @@ function broadcastEvent(eventName, argv) {
 | 
			
		||||
	}
 | 
			
		||||
	return Promise.all(promises);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * TODOC
 | 
			
		||||
 * @param {*} message
 | 
			
		||||
@@ -266,34 +266,6 @@ function broadcast(message) {
 | 
			
		||||
	return Promise.all(promises);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * TODOC
 | 
			
		||||
 * @param {String} eventName
 | 
			
		||||
 * @param {*} argv
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
function broadcastAppEventToUser(
 | 
			
		||||
	user,
 | 
			
		||||
	packageOwner,
 | 
			
		||||
	packageName,
 | 
			
		||||
	eventName,
 | 
			
		||||
	argv
 | 
			
		||||
) {
 | 
			
		||||
	let promises = [];
 | 
			
		||||
	for (let process of Object.values(gProcesses)) {
 | 
			
		||||
		if (
 | 
			
		||||
			process.credentials?.session?.name === user &&
 | 
			
		||||
			process.packageOwner == packageOwner &&
 | 
			
		||||
			process.packageName == packageName
 | 
			
		||||
		) {
 | 
			
		||||
			if (process.eventHandlers[eventName]) {
 | 
			
		||||
				promises.push(invoke(process.eventHandlers[eventName], argv));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return Promise.all(promises);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * TODOC
 | 
			
		||||
 * @param {*} caller
 | 
			
		||||
@@ -389,8 +361,6 @@ async function getProcessBlob(blobId, key, options) {
 | 
			
		||||
			process.key = key;
 | 
			
		||||
			process.credentials = options.credentials || {};
 | 
			
		||||
			process.task = new Task();
 | 
			
		||||
			process.packageOwner = options.packageOwner;
 | 
			
		||||
			process.packageName = options.packageName;
 | 
			
		||||
			process.eventHandlers = {};
 | 
			
		||||
			if (!options?.script || options?.script === 'app.js') {
 | 
			
		||||
				process.app = new app.App();
 | 
			
		||||
@@ -539,64 +509,6 @@ async function getProcessBlob(blobId, key, options) {
 | 
			
		||||
					url: options?.url,
 | 
			
		||||
				},
 | 
			
		||||
			};
 | 
			
		||||
			process.sendIdentities = async function () {
 | 
			
		||||
				process.app.send(
 | 
			
		||||
					Object.assign(
 | 
			
		||||
						{
 | 
			
		||||
							action: 'identities',
 | 
			
		||||
						},
 | 
			
		||||
						await ssb.getIdentityInfo(
 | 
			
		||||
							process?.credentials?.session?.name,
 | 
			
		||||
							options?.packageOwner,
 | 
			
		||||
							options?.packageName
 | 
			
		||||
						)
 | 
			
		||||
					)
 | 
			
		||||
				);
 | 
			
		||||
			};
 | 
			
		||||
			process.setActiveIdentity = async function (identity) {
 | 
			
		||||
				if (
 | 
			
		||||
					process?.credentials?.session?.name &&
 | 
			
		||||
					options.packageOwner &&
 | 
			
		||||
					options.packageName
 | 
			
		||||
				) {
 | 
			
		||||
					await new Database(process?.credentials?.session?.name).set(
 | 
			
		||||
						`id:${options.packageOwner}:${options.packageName}`,
 | 
			
		||||
						identity
 | 
			
		||||
					);
 | 
			
		||||
				}
 | 
			
		||||
				process.sendIdentities();
 | 
			
		||||
				broadcastAppEventToUser(
 | 
			
		||||
					process?.credentials?.session?.name,
 | 
			
		||||
					options.packageOwner,
 | 
			
		||||
					options.packageName,
 | 
			
		||||
					'setActiveIdentity',
 | 
			
		||||
					[identity]
 | 
			
		||||
				);
 | 
			
		||||
			};
 | 
			
		||||
			process.createIdentity = async function () {
 | 
			
		||||
				if (
 | 
			
		||||
					process.credentials &&
 | 
			
		||||
					process.credentials.session &&
 | 
			
		||||
					process.credentials.session.name
 | 
			
		||||
				) {
 | 
			
		||||
					let id = ssb.createIdentity(process.credentials.session.name);
 | 
			
		||||
					await process.sendIdentities();
 | 
			
		||||
					broadcastAppEventToUser(
 | 
			
		||||
						process?.credentials?.session?.name,
 | 
			
		||||
						options.packageOwner,
 | 
			
		||||
						options.packageName,
 | 
			
		||||
						'setActiveIdentity',
 | 
			
		||||
						[
 | 
			
		||||
							await ssb.getActiveIdentity(
 | 
			
		||||
								process.credentials?.session?.name,
 | 
			
		||||
								options.packageOwner,
 | 
			
		||||
								options.packageName
 | 
			
		||||
							),
 | 
			
		||||
						]
 | 
			
		||||
					);
 | 
			
		||||
					return id;
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
			if (process.credentials?.permissions?.administration) {
 | 
			
		||||
				imports.core.globalSettingsDescriptions = function () {
 | 
			
		||||
					let settings = Object.assign({}, k_global_settings);
 | 
			
		||||
@@ -667,7 +579,15 @@ async function getProcessBlob(blobId, key, options) {
 | 
			
		||||
				Object.keys(ssb).map((key) => [key, ssb[key].bind(ssb)])
 | 
			
		||||
			);
 | 
			
		||||
			imports.ssb.port = tildefriends.ssb_port;
 | 
			
		||||
			imports.ssb.createIdentity = () => process.createIdentity();
 | 
			
		||||
			imports.ssb.createIdentity = function () {
 | 
			
		||||
				if (
 | 
			
		||||
					process.credentials &&
 | 
			
		||||
					process.credentials.session &&
 | 
			
		||||
					process.credentials.session.name
 | 
			
		||||
				) {
 | 
			
		||||
					return ssb.createIdentity(process.credentials.session.name);
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
			imports.ssb.addIdentity = function (id) {
 | 
			
		||||
				if (
 | 
			
		||||
					process.credentials &&
 | 
			
		||||
@@ -694,13 +614,6 @@ async function getProcessBlob(blobId, key, options) {
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
			imports.ssb.setActiveIdentity = (id) => process.setActiveIdentity(id);
 | 
			
		||||
			imports.ssb.getActiveIdentity = () =>
 | 
			
		||||
				ssb.getActiveIdentity(
 | 
			
		||||
					process.credentials?.session?.name,
 | 
			
		||||
					options.packageOwner,
 | 
			
		||||
					options.packageName
 | 
			
		||||
				);
 | 
			
		||||
			imports.ssb.getOwnerIdentities = function () {
 | 
			
		||||
				if (options.packageOwner) {
 | 
			
		||||
					return ssb.getIdentities(options.packageOwner);
 | 
			
		||||
@@ -785,7 +698,6 @@ async function getProcessBlob(blobId, key, options) {
 | 
			
		||||
					);
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
			imports.ssb.getIdentityInfo = undefined;
 | 
			
		||||
			imports.fetch = function (url, options) {
 | 
			
		||||
				return http.fetch(url, options, gGlobalSettings.fetch_hosts);
 | 
			
		||||
			};
 | 
			
		||||
@@ -1055,7 +967,7 @@ async function useAppHandler(
 | 
			
		||||
					},
 | 
			
		||||
					respond: do_resolve,
 | 
			
		||||
				},
 | 
			
		||||
				credentials: httpd.auth_query(headers),
 | 
			
		||||
				credentials: auth.query(headers),
 | 
			
		||||
				packageOwner: packageOwner,
 | 
			
		||||
				packageName: packageName,
 | 
			
		||||
			}
 | 
			
		||||
@@ -1186,7 +1098,7 @@ async function blobHandler(request, response, blobId, uri) {
 | 
			
		||||
		if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
 | 
			
		||||
			let user = match[1];
 | 
			
		||||
			let appName = match[2];
 | 
			
		||||
			let credentials = httpd.auth_query(request.headers);
 | 
			
		||||
			let credentials = auth.query(request.headers);
 | 
			
		||||
			if (
 | 
			
		||||
				credentials &&
 | 
			
		||||
				credentials.session &&
 | 
			
		||||
@@ -1249,7 +1161,7 @@ async function blobHandler(request, response, blobId, uri) {
 | 
			
		||||
		if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
 | 
			
		||||
			let user = match[1];
 | 
			
		||||
			let appName = match[2];
 | 
			
		||||
			let credentials = httpd.auth_query(request.headers);
 | 
			
		||||
			let credentials = auth.query(request.headers);
 | 
			
		||||
			if (
 | 
			
		||||
				credentials &&
 | 
			
		||||
				credentials.session &&
 | 
			
		||||
@@ -1422,10 +1334,39 @@ loadSettings()
 | 
			
		||||
		if (tildefriends.https_port && gGlobalSettings.http_redirect) {
 | 
			
		||||
			httpd.set_http_redirect(gGlobalSettings.http_redirect);
 | 
			
		||||
		}
 | 
			
		||||
		httpd.all('/login', auth.handler);
 | 
			
		||||
		httpd.all('/login/logout', auth.handler);
 | 
			
		||||
		httpd.all('/app/socket', app.socket);
 | 
			
		||||
		httpd.all('', function default_http_handler(request, response) {
 | 
			
		||||
			let match;
 | 
			
		||||
			if ((match = /^(\/~[^\/]+\/[^\/]+)(\/?.*)$/.exec(request.uri))) {
 | 
			
		||||
			if (request.uri === '/' || request.uri === '') {
 | 
			
		||||
				let host = request.headers['x-forwarded-host'] ?? request.headers.host;
 | 
			
		||||
				try {
 | 
			
		||||
					for (let line of (gGlobalSettings.index_map || '').split('\n')) {
 | 
			
		||||
						let parts = line.split('=');
 | 
			
		||||
						if (parts.length == 2 && host.match(new RegExp(parts[0], 'i'))) {
 | 
			
		||||
							response.writeHead(303, {
 | 
			
		||||
								Location:
 | 
			
		||||
									(request.client.tls ? 'https://' : 'http://') +
 | 
			
		||||
									host +
 | 
			
		||||
									parts[1],
 | 
			
		||||
								'Content-Length': '0',
 | 
			
		||||
							});
 | 
			
		||||
							return response.end();
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				} catch (e) {
 | 
			
		||||
					print(e);
 | 
			
		||||
				}
 | 
			
		||||
				response.writeHead(303, {
 | 
			
		||||
					Location:
 | 
			
		||||
						(request.client.tls ? 'https://' : 'http://') +
 | 
			
		||||
						host +
 | 
			
		||||
						gGlobalSettings.index,
 | 
			
		||||
					'Content-Length': '0',
 | 
			
		||||
				});
 | 
			
		||||
				return response.end();
 | 
			
		||||
			} else if ((match = /^(\/~[^\/]+\/[^\/]+)(\/?.*)$/.exec(request.uri))) {
 | 
			
		||||
				return blobHandler(request, response, match[1], match[2]);
 | 
			
		||||
			} else if (
 | 
			
		||||
				(match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri))
 | 
			
		||||
@@ -1465,15 +1406,8 @@ loadSettings()
 | 
			
		||||
			async function start_tls() {
 | 
			
		||||
				const kCertificatePath = 'data/httpd/certificate.pem';
 | 
			
		||||
				const kPrivateKeyPath = 'data/httpd/privatekey.pem';
 | 
			
		||||
				let privateKey;
 | 
			
		||||
				let certificate;
 | 
			
		||||
				try {
 | 
			
		||||
					privateKey = utf8Decode(await File.readFile(kPrivateKeyPath));
 | 
			
		||||
					certificate = utf8Decode(await File.readFile(kCertificatePath));
 | 
			
		||||
				} catch (e) {
 | 
			
		||||
					print(`TLS disabled (${e.message}).`);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
				let privateKey = utf8Decode(await File.readFile(kPrivateKeyPath));
 | 
			
		||||
				let certificate = utf8Decode(await File.readFile(kCertificatePath));
 | 
			
		||||
				let context = new TlsContext();
 | 
			
		||||
				context.setPrivateKey(privateKey);
 | 
			
		||||
				context.setCertificate(certificate);
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,22 @@ body {
 | 
			
		||||
	margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:link {
 | 
			
		||||
	color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:visited {
 | 
			
		||||
	color: #6c71c4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:hover {
 | 
			
		||||
	color: #859900;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:active {
 | 
			
		||||
	color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#logo {
 | 
			
		||||
	vertical-align: middle;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								default.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								default.nix
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
with import <nixpkgs> {};
 | 
			
		||||
stdenv.mkDerivation rec {
 | 
			
		||||
  pname = "tildefriends";
 | 
			
		||||
  version = "0.0.16";
 | 
			
		||||
 | 
			
		||||
  src = fetchurl {
 | 
			
		||||
    url = "https://dev.tildefriends.net/cory/${pname}/archive/v${version}.tar.gz";
 | 
			
		||||
    sha256 = "19iay794xxs3j3mhnpl4vwx65sflw5vvjaahp0jk85wlwlrc7ddw";
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  nativeBuildInputs = [
 | 
			
		||||
    gnumake
 | 
			
		||||
    openssl
 | 
			
		||||
  ];
 | 
			
		||||
  # buildInputs = [  ]
 | 
			
		||||
  #doCheck = true;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  strictDeps = true;
 | 
			
		||||
 | 
			
		||||
  outputs = [ "out" ];
 | 
			
		||||
 | 
			
		||||
  meta = with lib; {
 | 
			
		||||
    #description = "A program that produces a familiar, friendly greeting";
 | 
			
		||||
    #longDescription = ''
 | 
			
		||||
    #  GNU Hello is a program that prints "Hello, world!" when you run it.
 | 
			
		||||
    #  It is fully customizable.
 | 
			
		||||
    #'';
 | 
			
		||||
    #homepage = "https://www.gnu.org/software/hello/manual/";
 | 
			
		||||
    #changelog = "https://git.savannah.gnu.org/cgit/hello.git/plain/NEWS?h=v${version}";
 | 
			
		||||
 | 
			
		||||
    license = licenses.gpl3Plus;
 | 
			
		||||
    maintainers = [ maintainers.tasiaiso ];
 | 
			
		||||
    platforms = platforms.all;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								deps/codemirror/cm6.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								deps/codemirror/cm6.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										215
									
								
								deps/codemirror_src/package-lock.json
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										215
									
								
								deps/codemirror_src/package-lock.json
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -5,23 +5,23 @@
 | 
			
		||||
  "packages": {
 | 
			
		||||
    "": {
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@codemirror/lang-css": "^6.2.1",
 | 
			
		||||
        "@codemirror/lang-html": "^6.4.8",
 | 
			
		||||
        "@codemirror/lang-javascript": "^6.2.2",
 | 
			
		||||
        "@codemirror/lang-json": "^6.0.1",
 | 
			
		||||
        "@codemirror/theme-one-dark": "^6.1.2",
 | 
			
		||||
        "@rollup/plugin-node-resolve": "^15.2.3",
 | 
			
		||||
        "codemirror": "^6.0.1",
 | 
			
		||||
        "rollup": "^4.13.0"
 | 
			
		||||
        "@codemirror/lang-css": "6.2.1",
 | 
			
		||||
        "@codemirror/lang-html": "6.4.8",
 | 
			
		||||
        "@codemirror/lang-javascript": "6.2.2",
 | 
			
		||||
        "@codemirror/lang-json": "6.0.1",
 | 
			
		||||
        "@codemirror/theme-one-dark": "6.1.2",
 | 
			
		||||
        "@rollup/plugin-node-resolve": "15.2.3",
 | 
			
		||||
        "codemirror": "6.0.1",
 | 
			
		||||
        "rollup": "4.13.0"
 | 
			
		||||
      },
 | 
			
		||||
      "devDependencies": {
 | 
			
		||||
        "@rollup/plugin-terser": "^0.4.4"
 | 
			
		||||
        "@rollup/plugin-terser": "0.4.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@codemirror/autocomplete": {
 | 
			
		||||
      "version": "6.16.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.16.0.tgz",
 | 
			
		||||
      "integrity": "sha512-P/LeCTtZHRTCU4xQsa89vSKWecYv1ZqwzOd5topheGRf+qtacFgBeIMQi3eL8Kt/BUNvxUWkx+5qP2jlGoARrg==",
 | 
			
		||||
      "version": "6.15.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.15.0.tgz",
 | 
			
		||||
      "integrity": "sha512-G2Zm0mXznxz97JhaaOdoEG2cVupn4JjPaS4AcNvZzhOsnnG9YVN68VzfoUw6dYTsIxT6a/cmoFEN47KAWhXaOg==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@codemirror/language": "^6.0.0",
 | 
			
		||||
        "@codemirror/state": "^6.0.0",
 | 
			
		||||
@@ -36,9 +36,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@codemirror/commands": {
 | 
			
		||||
      "version": "6.5.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.5.0.tgz",
 | 
			
		||||
      "integrity": "sha512-rK+sj4fCAN/QfcY9BEzYMgp4wwL/q5aj/VfNSoH1RWPF9XS/dUwBkvlL3hpWgEjOqlpdN1uLC9UkjJ4tmyjJYg==",
 | 
			
		||||
      "version": "6.3.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.3.3.tgz",
 | 
			
		||||
      "integrity": "sha512-dO4hcF0fGT9tu1Pj1D2PvGvxjeGkbC6RGcZw6Qs74TH+Ed1gw98jmUgd2axWvIZEqTeTuFrg1lEB1KV6cK9h1A==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@codemirror/language": "^6.0.0",
 | 
			
		||||
        "@codemirror/state": "^6.4.0",
 | 
			
		||||
@@ -59,9 +59,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@codemirror/lang-html": {
 | 
			
		||||
      "version": "6.4.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
 | 
			
		||||
      "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
 | 
			
		||||
      "version": "6.4.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.8.tgz",
 | 
			
		||||
      "integrity": "sha512-tE2YK7wDlb9ZpAH6mpTPiYm6rhfdQKVDa5r9IwIFlwwgvVaKsCfuKKZoJGWsmMZIf3FQAuJ5CHMPLymOtg1hXw==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@codemirror/autocomplete": "^6.0.0",
 | 
			
		||||
        "@codemirror/lang-css": "^6.0.0",
 | 
			
		||||
@@ -111,9 +111,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@codemirror/lint": {
 | 
			
		||||
      "version": "6.7.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.7.0.tgz",
 | 
			
		||||
      "integrity": "sha512-LTLOL2nT41ADNSCCCCw8Q/UmdAFzB23OUYSjsHTdsVaH0XEo+orhuqbDNWzrzodm14w6FOxqxpmy4LF8Lixqjw==",
 | 
			
		||||
      "version": "6.5.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.5.0.tgz",
 | 
			
		||||
      "integrity": "sha512-+5YyicIaaAZKU8K43IQi8TBy6mF6giGeWAH7N96Z5LC30Wm5JMjqxOYIE9mxwMG1NbhT2mA3l9hA4uuKUM3E5g==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@codemirror/state": "^6.0.0",
 | 
			
		||||
        "@codemirror/view": "^6.0.0",
 | 
			
		||||
@@ -147,9 +147,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@codemirror/view": {
 | 
			
		||||
      "version": "6.26.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.3.tgz",
 | 
			
		||||
      "integrity": "sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==",
 | 
			
		||||
      "version": "6.25.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.25.1.tgz",
 | 
			
		||||
      "integrity": "sha512-2LXLxsQnHDdfGzDvjzAwZh2ZviNJm7im6tGpa0IONIDnFd8RZ80D2SNi8PDi6YjKcMoMRK20v6OmKIdsrwsyoQ==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@codemirror/state": "^6.4.0",
 | 
			
		||||
        "style-mod": "^4.1.0",
 | 
			
		||||
@@ -248,9 +248,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@lezer/javascript": {
 | 
			
		||||
      "version": "1.4.15",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.15.tgz",
 | 
			
		||||
      "integrity": "sha512-B082ZdjI0vo2AgLqD834GlRTE9gwRX8NzHzKq5uDwEnQ9Dq+A/CEhd3nf68tiNA2f9O+8jS1NeSTUYT9IAqcTw==",
 | 
			
		||||
      "version": "1.4.13",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.13.tgz",
 | 
			
		||||
      "integrity": "sha512-5IBr8LIO3xJdJH1e9aj/ZNLE4LSbdsx25wFmGRAZsj2zSmwAYjx26JyU/BYOCpRQlu1jcv1z3vy4NB9+UkfRow==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@lezer/common": "^1.2.0",
 | 
			
		||||
        "@lezer/highlight": "^1.1.3",
 | 
			
		||||
@@ -343,9 +343,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-android-arm-eabi": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==",
 | 
			
		||||
      "version": "4.13.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz",
 | 
			
		||||
      "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm"
 | 
			
		||||
      ],
 | 
			
		||||
@@ -355,9 +355,9 @@
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-android-arm64": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==",
 | 
			
		||||
      "version": "4.13.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz",
 | 
			
		||||
      "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm64"
 | 
			
		||||
      ],
 | 
			
		||||
@@ -367,9 +367,9 @@
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-darwin-arm64": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==",
 | 
			
		||||
      "version": "4.13.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm64"
 | 
			
		||||
      ],
 | 
			
		||||
@@ -379,9 +379,9 @@
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-darwin-x64": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==",
 | 
			
		||||
      "version": "4.13.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz",
 | 
			
		||||
      "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "x64"
 | 
			
		||||
      ],
 | 
			
		||||
@@ -391,21 +391,9 @@
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm"
 | 
			
		||||
      ],
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "os": [
 | 
			
		||||
        "linux"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==",
 | 
			
		||||
      "version": "4.13.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz",
 | 
			
		||||
      "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm"
 | 
			
		||||
      ],
 | 
			
		||||
@@ -415,9 +403,9 @@
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-linux-arm64-gnu": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==",
 | 
			
		||||
      "version": "4.13.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm64"
 | 
			
		||||
      ],
 | 
			
		||||
@@ -427,9 +415,9 @@
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-linux-arm64-musl": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==",
 | 
			
		||||
      "version": "4.13.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz",
 | 
			
		||||
      "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm64"
 | 
			
		||||
      ],
 | 
			
		||||
@@ -438,22 +426,10 @@
 | 
			
		||||
        "linux"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "ppc64"
 | 
			
		||||
      ],
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "os": [
 | 
			
		||||
        "linux"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==",
 | 
			
		||||
      "version": "4.13.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "riscv64"
 | 
			
		||||
      ],
 | 
			
		||||
@@ -462,22 +438,10 @@
 | 
			
		||||
        "linux"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-linux-s390x-gnu": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "s390x"
 | 
			
		||||
      ],
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "os": [
 | 
			
		||||
        "linux"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-linux-x64-gnu": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==",
 | 
			
		||||
      "version": "4.13.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz",
 | 
			
		||||
      "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "x64"
 | 
			
		||||
      ],
 | 
			
		||||
@@ -487,9 +451,9 @@
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-linux-x64-musl": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==",
 | 
			
		||||
      "version": "4.13.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz",
 | 
			
		||||
      "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "x64"
 | 
			
		||||
      ],
 | 
			
		||||
@@ -499,9 +463,9 @@
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-win32-arm64-msvc": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==",
 | 
			
		||||
      "version": "4.13.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz",
 | 
			
		||||
      "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "arm64"
 | 
			
		||||
      ],
 | 
			
		||||
@@ -511,9 +475,9 @@
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-win32-ia32-msvc": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==",
 | 
			
		||||
      "version": "4.13.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz",
 | 
			
		||||
      "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "ia32"
 | 
			
		||||
      ],
 | 
			
		||||
@@ -523,9 +487,9 @@
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rollup/rollup-win32-x64-msvc": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==",
 | 
			
		||||
      "version": "4.13.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz",
 | 
			
		||||
      "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==",
 | 
			
		||||
      "cpu": [
 | 
			
		||||
        "x64"
 | 
			
		||||
      ],
 | 
			
		||||
@@ -715,9 +679,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/rollup": {
 | 
			
		||||
      "version": "4.17.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz",
 | 
			
		||||
      "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==",
 | 
			
		||||
      "version": "4.13.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz",
 | 
			
		||||
      "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/estree": "1.0.5"
 | 
			
		||||
      },
 | 
			
		||||
@@ -729,22 +693,19 @@
 | 
			
		||||
        "npm": ">=8.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "optionalDependencies": {
 | 
			
		||||
        "@rollup/rollup-android-arm-eabi": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-android-arm64": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-darwin-arm64": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-darwin-x64": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-linux-arm-gnueabihf": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-linux-arm-musleabihf": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-linux-arm64-gnu": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-linux-arm64-musl": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-linux-riscv64-gnu": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-linux-s390x-gnu": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-linux-x64-gnu": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-linux-x64-musl": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-win32-arm64-msvc": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-win32-ia32-msvc": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-win32-x64-msvc": "4.17.2",
 | 
			
		||||
        "@rollup/rollup-android-arm-eabi": "4.13.0",
 | 
			
		||||
        "@rollup/rollup-android-arm64": "4.13.0",
 | 
			
		||||
        "@rollup/rollup-darwin-arm64": "4.13.0",
 | 
			
		||||
        "@rollup/rollup-darwin-x64": "4.13.0",
 | 
			
		||||
        "@rollup/rollup-linux-arm-gnueabihf": "4.13.0",
 | 
			
		||||
        "@rollup/rollup-linux-arm64-gnu": "4.13.0",
 | 
			
		||||
        "@rollup/rollup-linux-arm64-musl": "4.13.0",
 | 
			
		||||
        "@rollup/rollup-linux-riscv64-gnu": "4.13.0",
 | 
			
		||||
        "@rollup/rollup-linux-x64-gnu": "4.13.0",
 | 
			
		||||
        "@rollup/rollup-linux-x64-musl": "4.13.0",
 | 
			
		||||
        "@rollup/rollup-win32-arm64-msvc": "4.13.0",
 | 
			
		||||
        "@rollup/rollup-win32-ia32-msvc": "4.13.0",
 | 
			
		||||
        "@rollup/rollup-win32-x64-msvc": "4.13.0",
 | 
			
		||||
        "fsevents": "~2.3.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
@@ -778,9 +739,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/smob": {
 | 
			
		||||
      "version": "1.5.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz",
 | 
			
		||||
      "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==",
 | 
			
		||||
      "version": "1.4.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz",
 | 
			
		||||
      "integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/source-map": {
 | 
			
		||||
@@ -819,9 +780,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/terser": {
 | 
			
		||||
      "version": "5.31.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==",
 | 
			
		||||
      "version": "5.29.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz",
 | 
			
		||||
      "integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@jridgewell/source-map": "^0.3.3",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								deps/codemirror_src/package.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								deps/codemirror_src/package.json
									
									
									
									
										vendored
									
									
								
							@@ -3,16 +3,16 @@
 | 
			
		||||
    "build": "rollup --config rollup.config.mjs --input editor.mjs"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@codemirror/lang-css": "^6.2.1",
 | 
			
		||||
    "@codemirror/lang-html": "^6.4.8",
 | 
			
		||||
    "@codemirror/lang-javascript": "^6.2.2",
 | 
			
		||||
    "@codemirror/lang-json": "^6.0.1",
 | 
			
		||||
    "@codemirror/theme-one-dark": "^6.1.2",
 | 
			
		||||
    "@rollup/plugin-node-resolve": "^15.2.3",
 | 
			
		||||
    "codemirror": "^6.0.1",
 | 
			
		||||
    "rollup": "^4.13.0"
 | 
			
		||||
    "@codemirror/lang-css": "6.2.1",
 | 
			
		||||
    "@codemirror/lang-html": "6.4.8",
 | 
			
		||||
    "@codemirror/lang-javascript": "6.2.2",
 | 
			
		||||
    "@codemirror/lang-json": "6.0.1",
 | 
			
		||||
    "@codemirror/theme-one-dark": "6.1.2",
 | 
			
		||||
    "@rollup/plugin-node-resolve": "15.2.3",
 | 
			
		||||
    "codemirror": "6.0.1",
 | 
			
		||||
    "rollup": "4.13.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@rollup/plugin-terser": "^0.4.4"
 | 
			
		||||
    "@rollup/plugin-terser": "0.4.4"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								deps/libbacktrace
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								deps/libbacktrace
									
									
									
									
										vendored
									
									
								
							 Submodule deps/libbacktrace updated: 11427f31a6...9ae4f4ae44
									
								
							
							
								
								
									
										120
									
								
								deps/lit/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										120
									
								
								deps/lit/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								deps/lit/lit-all.min.js.map
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								deps/lit/lit-all.min.js.map
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								deps/picohttpparser
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								deps/picohttpparser
									
									
									
									
										vendored
									
									
								
							 Submodule deps/picohttpparser updated: f8d0513f1a...4e7bc76fa7
									
								
							
							
								
								
									
										34
									
								
								deps/prettier/standalone.mjs
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										34
									
								
								deps/prettier/standalone.mjs
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								deps/quickjs
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								deps/quickjs
									
									
									
									
										vendored
									
									
								
							 Submodule deps/quickjs updated: 3f81070e57...65ecb0b0d6
									
								
							
							
								
								
									
										33
									
								
								deps/sqlite/shell.c
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								deps/sqlite/shell.c
									
									
									
									
										vendored
									
									
								
							@@ -14753,15 +14753,6 @@ static void dbdataValue(
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* This macro is a copy of the MX_CELL() macro in the SQLite core. Given
 | 
			
		||||
** a page-size, it returns the maximum number of cells that may be present
 | 
			
		||||
** on the page.  */
 | 
			
		||||
#define DBDATA_MX_CELL(pgsz) ((pgsz-8)/6)
 | 
			
		||||
 | 
			
		||||
/* Maximum number of fields that may appear in a single record. This is
 | 
			
		||||
** the "hard-limit", according to comments in sqliteLimit.h. */
 | 
			
		||||
#define DBDATA_MX_FIELD 32676
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
** Move an sqlite_dbdata or sqlite_dbptr cursor to the next entry.
 | 
			
		||||
*/
 | 
			
		||||
@@ -14790,9 +14781,6 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
 | 
			
		||||
      assert( iOff+3+2<=pCsr->nPage );
 | 
			
		||||
      pCsr->iCell = pTab->bPtr ? -2 : 0;
 | 
			
		||||
      pCsr->nCell = get_uint16(&pCsr->aPage[iOff+3]);
 | 
			
		||||
      if( pCsr->nCell>DBDATA_MX_CELL(pCsr->nPage) ){
 | 
			
		||||
        pCsr->nCell = DBDATA_MX_CELL(pCsr->nPage);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if( pTab->bPtr ){
 | 
			
		||||
@@ -14837,19 +14825,19 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
 | 
			
		||||
        if( pCsr->iCell>=pCsr->nCell ){
 | 
			
		||||
          bNextPage = 1;
 | 
			
		||||
        }else{
 | 
			
		||||
          int iCellPtr = iOff + 8 + nPointer + pCsr->iCell*2;
 | 
			
		||||
  
 | 
			
		||||
          if( iCellPtr>pCsr->nPage ){
 | 
			
		||||
          iOff += 8 + nPointer + pCsr->iCell*2;
 | 
			
		||||
          if( iOff>pCsr->nPage ){
 | 
			
		||||
            bNextPage = 1;
 | 
			
		||||
          }else{
 | 
			
		||||
            iOff = get_uint16(&pCsr->aPage[iCellPtr]);
 | 
			
		||||
            iOff = get_uint16(&pCsr->aPage[iOff]);
 | 
			
		||||
          }
 | 
			
		||||
    
 | 
			
		||||
          /* For an interior node cell, skip past the child-page number */
 | 
			
		||||
          iOff += nPointer;
 | 
			
		||||
    
 | 
			
		||||
          /* Load the "byte of payload including overflow" field */
 | 
			
		||||
          if( bNextPage || iOff>pCsr->nPage || iOff<=iCellPtr ){
 | 
			
		||||
          if( bNextPage || iOff>pCsr->nPage ){
 | 
			
		||||
            bNextPage = 1;
 | 
			
		||||
          }else{
 | 
			
		||||
            iOff += dbdataGetVarintU32(&pCsr->aPage[iOff], &nPayload);
 | 
			
		||||
@@ -14932,9 +14920,7 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
 | 
			
		||||
        pCsr->iField++;
 | 
			
		||||
        if( pCsr->iField>0 ){
 | 
			
		||||
          sqlite3_int64 iType;
 | 
			
		||||
          if( pCsr->pHdrPtr>=&pCsr->pRec[pCsr->nRec] 
 | 
			
		||||
           || pCsr->iField>=DBDATA_MX_FIELD
 | 
			
		||||
          ){
 | 
			
		||||
          if( pCsr->pHdrPtr>&pCsr->pRec[pCsr->nRec] ){
 | 
			
		||||
            bNextPage = 1;
 | 
			
		||||
          }else{
 | 
			
		||||
            int szField = 0;
 | 
			
		||||
@@ -16422,7 +16408,7 @@ static int recoverWriteSchema1(sqlite3_recover *p){
 | 
			
		||||
        if( bTable && !bVirtual ){
 | 
			
		||||
          if( SQLITE_ROW==sqlite3_step(pTblname) ){
 | 
			
		||||
            const char *zTbl = (const char*)sqlite3_column_text(pTblname, 0);
 | 
			
		||||
            if( zTbl ) recoverAddTable(p, zTbl, iRoot);
 | 
			
		||||
            recoverAddTable(p, zTbl, iRoot);
 | 
			
		||||
          }
 | 
			
		||||
          recoverReset(p, pTblname);
 | 
			
		||||
        }
 | 
			
		||||
@@ -28785,7 +28771,6 @@ static const char zOptions[] =
 | 
			
		||||
  "   -newline SEP         set output row separator. Default: '\\n'\n"
 | 
			
		||||
  "   -nofollow            refuse to open symbolic links to database files\n"
 | 
			
		||||
  "   -nonce STRING        set the safe-mode escape nonce\n"
 | 
			
		||||
  "   -no-rowid-in-view    Disable rowid-in-view using sqlite3_config()\n"
 | 
			
		||||
  "   -nullvalue TEXT      set text string for NULL values. Default ''\n"
 | 
			
		||||
  "   -pagecache SIZE N    use N slots of SZ bytes each for page cache memory\n"
 | 
			
		||||
  "   -pcachetrace         trace all page cache operations\n"
 | 
			
		||||
@@ -29076,10 +29061,6 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
 | 
			
		||||
      stdin_is_interactive = 0;
 | 
			
		||||
    }else if( cli_strcmp(z,"-utf8")==0 ){
 | 
			
		||||
    }else if( cli_strcmp(z,"-no-utf8")==0 ){
 | 
			
		||||
    }else if( cli_strcmp(z,"-no-rowid-in-view")==0 ){
 | 
			
		||||
      int val = 0;
 | 
			
		||||
      sqlite3_config(SQLITE_CONFIG_ROWID_IN_VIEW, &val);
 | 
			
		||||
      assert( val==0 );
 | 
			
		||||
    }else if( cli_strcmp(z,"-heap")==0 ){
 | 
			
		||||
#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
 | 
			
		||||
      const char *zSize;
 | 
			
		||||
@@ -29355,8 +29336,6 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
 | 
			
		||||
      /* already handled */
 | 
			
		||||
    }else if( cli_strcmp(z,"-no-utf8")==0 ){
 | 
			
		||||
      /* already handled */
 | 
			
		||||
    }else if( cli_strcmp(z,"-no-rowid-in-view")==0 ){
 | 
			
		||||
      /* already handled */
 | 
			
		||||
    }else if( cli_strcmp(z,"-heap")==0 ){
 | 
			
		||||
      i++;
 | 
			
		||||
    }else if( cli_strcmp(z,"-pagecache")==0 ){
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										205
									
								
								deps/sqlite/sqlite3.c
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										205
									
								
								deps/sqlite/sqlite3.c
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
/******************************************************************************
 | 
			
		||||
** This file is an amalgamation of many separate C source files from SQLite
 | 
			
		||||
** version 3.45.3.  By combining all the individual C code files into this
 | 
			
		||||
** version 3.45.2.  By combining all the individual C code files into this
 | 
			
		||||
** single large file, the entire code can be compiled as a single translation
 | 
			
		||||
** unit.  This allows many compilers to do optimizations that would not be
 | 
			
		||||
** possible if the files were compiled separately.  Performance improvements
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
** separate file. This file contains only code for the core SQLite library.
 | 
			
		||||
**
 | 
			
		||||
** The content in this amalgamation comes from Fossil check-in
 | 
			
		||||
** 8653b758870e6ef0c98d46b3ace27849054a.
 | 
			
		||||
** d8cd6d49b46a395b13955387d05e9e1a2a47.
 | 
			
		||||
*/
 | 
			
		||||
#define SQLITE_CORE 1
 | 
			
		||||
#define SQLITE_AMALGAMATION 1
 | 
			
		||||
@@ -459,9 +459,9 @@ extern "C" {
 | 
			
		||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
 | 
			
		||||
** [sqlite_version()] and [sqlite_source_id()].
 | 
			
		||||
*/
 | 
			
		||||
#define SQLITE_VERSION        "3.45.3"
 | 
			
		||||
#define SQLITE_VERSION_NUMBER 3045003
 | 
			
		||||
#define SQLITE_SOURCE_ID      "2024-04-15 13:34:05 8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355"
 | 
			
		||||
#define SQLITE_VERSION        "3.45.2"
 | 
			
		||||
#define SQLITE_VERSION_NUMBER 3045002
 | 
			
		||||
#define SQLITE_SOURCE_ID      "2024-03-12 11:06:23 d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77"
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
** CAPI3REF: Run-Time Library Version Numbers
 | 
			
		||||
@@ -2456,22 +2456,6 @@ struct sqlite3_mem_methods {
 | 
			
		||||
** configuration setting is never used, then the default maximum is determined
 | 
			
		||||
** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option.  If that
 | 
			
		||||
** compile-time option is not set, then the default maximum is 1073741824.
 | 
			
		||||
**
 | 
			
		||||
** [[SQLITE_CONFIG_ROWID_IN_VIEW]]
 | 
			
		||||
** <dt>SQLITE_CONFIG_ROWID_IN_VIEW
 | 
			
		||||
** <dd>The SQLITE_CONFIG_ROWID_IN_VIEW option enables or disables the ability
 | 
			
		||||
** for VIEWs to have a ROWID.  The capability can only be enabled if SQLite is
 | 
			
		||||
** compiled with -DSQLITE_ALLOW_ROWID_IN_VIEW, in which case the capability
 | 
			
		||||
** defaults to on.  This configuration option queries the current setting or
 | 
			
		||||
** changes the setting to off or on.  The argument is a pointer to an integer.
 | 
			
		||||
** If that integer initially holds a value of 1, then the ability for VIEWs to
 | 
			
		||||
** have ROWIDs is activated.  If the integer initially holds zero, then the
 | 
			
		||||
** ability is deactivated.  Any other initial value for the integer leaves the
 | 
			
		||||
** setting unchanged.  After changes, if any, the integer is written with
 | 
			
		||||
** a 1 or 0, if the ability for VIEWs to have ROWIDs is on or off.  If SQLite
 | 
			
		||||
** is compiled without -DSQLITE_ALLOW_ROWID_IN_VIEW (which is the usual and
 | 
			
		||||
** recommended case) then the integer is always filled with zero, regardless
 | 
			
		||||
** if its initial value.
 | 
			
		||||
** </dl>
 | 
			
		||||
*/
 | 
			
		||||
#define SQLITE_CONFIG_SINGLETHREAD         1  /* nil */
 | 
			
		||||
@@ -2503,7 +2487,6 @@ struct sqlite3_mem_methods {
 | 
			
		||||
#define SQLITE_CONFIG_SMALL_MALLOC        27  /* boolean */
 | 
			
		||||
#define SQLITE_CONFIG_SORTERREF_SIZE      28  /* int nByte */
 | 
			
		||||
#define SQLITE_CONFIG_MEMDB_MAXSIZE       29  /* sqlite3_int64 */
 | 
			
		||||
#define SQLITE_CONFIG_ROWID_IN_VIEW       30  /* int* */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
** CAPI3REF: Database Connection Configuration Options
 | 
			
		||||
@@ -18447,15 +18430,6 @@ struct Table {
 | 
			
		||||
#define HasRowid(X)     (((X)->tabFlags & TF_WithoutRowid)==0)
 | 
			
		||||
#define VisibleRowid(X) (((X)->tabFlags & TF_NoVisibleRowid)==0)
 | 
			
		||||
 | 
			
		||||
/* Macro is true if the SQLITE_ALLOW_ROWID_IN_VIEW (mis-)feature is
 | 
			
		||||
** available.  By default, this macro is false
 | 
			
		||||
*/
 | 
			
		||||
#ifndef SQLITE_ALLOW_ROWID_IN_VIEW
 | 
			
		||||
# define ViewCanHaveRowid     0
 | 
			
		||||
#else
 | 
			
		||||
# define ViewCanHaveRowid     (sqlite3Config.mNoVisibleRowid==0)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
** Each foreign key constraint is an instance of the following structure.
 | 
			
		||||
**
 | 
			
		||||
@@ -20170,11 +20144,6 @@ struct Sqlite3Config {
 | 
			
		||||
#endif
 | 
			
		||||
#ifndef SQLITE_UNTESTABLE
 | 
			
		||||
  int (*xTestCallback)(int);        /* Invoked by sqlite3FaultSim() */
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
 | 
			
		||||
  u32 mNoVisibleRowid;              /* TF_NoVisibleRowid if the ROWID_IN_VIEW
 | 
			
		||||
                                    ** feature is disabled.  0 if rowids can
 | 
			
		||||
                                    ** occur in views. */
 | 
			
		||||
#endif
 | 
			
		||||
  int bLocaltimeFault;              /* True to fail localtime() calls */
 | 
			
		||||
  int (*xAltLocaltime)(const void*,void*); /* Alternative localtime() routine */
 | 
			
		||||
@@ -20631,13 +20600,10 @@ SQLITE_PRIVATE void sqlite3MutexWarnOnContention(sqlite3_mutex*);
 | 
			
		||||
# define EXP754 (((u64)0x7ff)<<52)
 | 
			
		||||
# define MAN754 ((((u64)1)<<52)-1)
 | 
			
		||||
# define IsNaN(X) (((X)&EXP754)==EXP754 && ((X)&MAN754)!=0)
 | 
			
		||||
# define IsOvfl(X) (((X)&EXP754)==EXP754)
 | 
			
		||||
SQLITE_PRIVATE   int sqlite3IsNaN(double);
 | 
			
		||||
SQLITE_PRIVATE   int sqlite3IsOverflow(double);
 | 
			
		||||
#else
 | 
			
		||||
# define IsNaN(X)             0
 | 
			
		||||
# define sqlite3IsNaN(X)      0
 | 
			
		||||
# define sqlite3IsOVerflow(X) 0
 | 
			
		||||
# define IsNaN(X)         0
 | 
			
		||||
# define sqlite3IsNaN(X)  0
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@@ -21873,9 +21839,6 @@ static const char * const sqlite3azCompileOpt[] = {
 | 
			
		||||
  "ALLOW_COVERING_INDEX_SCAN=" CTIMEOPT_VAL(SQLITE_ALLOW_COVERING_INDEX_SCAN),
 | 
			
		||||
# endif
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
 | 
			
		||||
  "ALLOW_ROWID_IN_VIEW",
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef SQLITE_ALLOW_URI_AUTHORITY
 | 
			
		||||
  "ALLOW_URI_AUTHORITY",
 | 
			
		||||
#endif
 | 
			
		||||
@@ -22895,9 +22858,6 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = {
 | 
			
		||||
#endif
 | 
			
		||||
#ifndef SQLITE_UNTESTABLE
 | 
			
		||||
   0,                         /* xTestCallback */
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
 | 
			
		||||
   0,                         /* mNoVisibleRowid.  0 == allow rowid-in-view */
 | 
			
		||||
#endif
 | 
			
		||||
   0,                         /* bLocaltimeFault */
 | 
			
		||||
   0,                         /* xAltLocaltime */
 | 
			
		||||
@@ -34686,19 +34646,6 @@ SQLITE_PRIVATE int sqlite3IsNaN(double x){
 | 
			
		||||
}
 | 
			
		||||
#endif /* SQLITE_OMIT_FLOATING_POINT */
 | 
			
		||||
 | 
			
		||||
#ifndef SQLITE_OMIT_FLOATING_POINT
 | 
			
		||||
/*
 | 
			
		||||
** Return true if the floating point value is NaN or +Inf or -Inf.
 | 
			
		||||
*/
 | 
			
		||||
SQLITE_PRIVATE int sqlite3IsOverflow(double x){
 | 
			
		||||
  int rc;   /* The value return */
 | 
			
		||||
  u64 y;
 | 
			
		||||
  memcpy(&y,&x,sizeof(y));
 | 
			
		||||
  rc = IsOvfl(y);
 | 
			
		||||
  return rc;
 | 
			
		||||
}
 | 
			
		||||
#endif /* SQLITE_OMIT_FLOATING_POINT */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
** Compute a string length that is limited to what can be stored in
 | 
			
		||||
** lower 30 bits of a 32-bit signed integer.
 | 
			
		||||
@@ -63855,7 +63802,7 @@ SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager *pPager){
 | 
			
		||||
** This will be either the rollback journal or the WAL file.
 | 
			
		||||
*/
 | 
			
		||||
SQLITE_PRIVATE sqlite3_file *sqlite3PagerJrnlFile(Pager *pPager){
 | 
			
		||||
#ifdef SQLITE_OMIT_WAL
 | 
			
		||||
#if SQLITE_OMIT_WAL
 | 
			
		||||
  return pPager->jfd;
 | 
			
		||||
#else
 | 
			
		||||
  return pPager->pWal ? sqlite3WalFile(pPager->pWal) : pPager->jfd;
 | 
			
		||||
@@ -79672,7 +79619,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
 | 
			
		||||
  }else if( loc<0 && pPage->nCell>0 ){
 | 
			
		||||
    assert( pPage->leaf );
 | 
			
		||||
    idx = ++pCur->ix;
 | 
			
		||||
    pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl);
 | 
			
		||||
    pCur->curFlags &= ~BTCF_ValidNKey;
 | 
			
		||||
  }else{
 | 
			
		||||
    assert( pPage->leaf );
 | 
			
		||||
  }
 | 
			
		||||
@@ -79702,7 +79649,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
 | 
			
		||||
  */
 | 
			
		||||
  if( pPage->nOverflow ){
 | 
			
		||||
    assert( rc==SQLITE_OK );
 | 
			
		||||
    pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl);
 | 
			
		||||
    pCur->curFlags &= ~(BTCF_ValidNKey);
 | 
			
		||||
    rc = balance(pCur);
 | 
			
		||||
 | 
			
		||||
    /* Must make sure nOverflow is reset to zero even if the balance()
 | 
			
		||||
@@ -106709,37 +106656,8 @@ static int lookupName(
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        if( 0==cnt && VisibleRowid(pTab) ){
 | 
			
		||||
          /* pTab is a potential ROWID match.  Keep track of it and match
 | 
			
		||||
          ** the ROWID later if that seems appropriate.  (Search for "cntTab"
 | 
			
		||||
          ** to find related code.)  Only allow a ROWID match if there is
 | 
			
		||||
          ** a single ROWID match candidate.
 | 
			
		||||
          */
 | 
			
		||||
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
 | 
			
		||||
          /* In SQLITE_ALLOW_ROWID_IN_VIEW mode, allow a ROWID match
 | 
			
		||||
          ** if there is a single VIEW candidate or if there is a single
 | 
			
		||||
          ** non-VIEW candidate plus multiple VIEW candidates.  In other
 | 
			
		||||
          ** words non-VIEW candidate terms take precedence over VIEWs.
 | 
			
		||||
          */
 | 
			
		||||
          if( cntTab==0
 | 
			
		||||
           || (cntTab==1
 | 
			
		||||
               && ALWAYS(pMatch!=0)
 | 
			
		||||
               && ALWAYS(pMatch->pTab!=0)
 | 
			
		||||
               && (pMatch->pTab->tabFlags & TF_Ephemeral)!=0
 | 
			
		||||
               && (pTab->tabFlags & TF_Ephemeral)==0)
 | 
			
		||||
          ){
 | 
			
		||||
            cntTab = 1;
 | 
			
		||||
            pMatch = pItem;
 | 
			
		||||
          }else{
 | 
			
		||||
            cntTab++;
 | 
			
		||||
          }
 | 
			
		||||
#else
 | 
			
		||||
          /* The (much more common) non-SQLITE_ALLOW_ROWID_IN_VIEW case is
 | 
			
		||||
          ** simpler since we require exactly one candidate, which will
 | 
			
		||||
          ** always be a non-VIEW
 | 
			
		||||
          */
 | 
			
		||||
          cntTab++;
 | 
			
		||||
          pMatch = pItem;
 | 
			
		||||
#endif
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if( pMatch ){
 | 
			
		||||
@@ -106865,13 +106783,13 @@ static int lookupName(
 | 
			
		||||
    ** Perhaps the name is a reference to the ROWID
 | 
			
		||||
    */
 | 
			
		||||
    if( cnt==0
 | 
			
		||||
     && cntTab>=1
 | 
			
		||||
     && cntTab==1
 | 
			
		||||
     && pMatch
 | 
			
		||||
     && (pNC->ncFlags & (NC_IdxExpr|NC_GenCol))==0
 | 
			
		||||
     && sqlite3IsRowid(zCol)
 | 
			
		||||
     && ALWAYS(VisibleRowid(pMatch->pTab) || pMatch->fg.isNestedFrom)
 | 
			
		||||
    ){
 | 
			
		||||
      cnt = cntTab;
 | 
			
		||||
      cnt = 1;
 | 
			
		||||
      if( pMatch->fg.isNestedFrom==0 ) pExpr->iColumn = -1;
 | 
			
		||||
      pExpr->affExpr = SQLITE_AFF_INTEGER;
 | 
			
		||||
    }
 | 
			
		||||
@@ -108729,10 +108647,9 @@ SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr *pExpr){
 | 
			
		||||
      assert( pExpr->x.pList->nExpr>0 );
 | 
			
		||||
      assert( pExpr->op==TK_FUNCTION );
 | 
			
		||||
      pExpr = pExpr->x.pList->a[0].pExpr;
 | 
			
		||||
    }else if( pExpr->op==TK_COLLATE ){
 | 
			
		||||
      pExpr = pExpr->pLeft;
 | 
			
		||||
    }else{
 | 
			
		||||
      break;
 | 
			
		||||
      assert( pExpr->op==TK_COLLATE );
 | 
			
		||||
      pExpr = pExpr->pLeft;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return pExpr;
 | 
			
		||||
@@ -111251,12 +111168,9 @@ SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr *p){
 | 
			
		||||
      return 0;
 | 
			
		||||
    case TK_COLUMN:
 | 
			
		||||
      assert( ExprUseYTab(p) );
 | 
			
		||||
      return ExprHasProperty(p, EP_CanBeNull)
 | 
			
		||||
          || NEVER(p->y.pTab==0) /* Reference to column of index on expr */
 | 
			
		||||
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
 | 
			
		||||
          || (p->iColumn==XN_ROWID && IsView(p->y.pTab))
 | 
			
		||||
#endif
 | 
			
		||||
          || (p->iColumn>=0
 | 
			
		||||
      return ExprHasProperty(p, EP_CanBeNull) ||
 | 
			
		||||
             NEVER(p->y.pTab==0) ||  /* Reference to column of index on expr */
 | 
			
		||||
             (p->iColumn>=0
 | 
			
		||||
              && p->y.pTab->aCol!=0 /* Possible due to prior error */
 | 
			
		||||
              && ALWAYS(p->iColumn<p->y.pTab->nCol)
 | 
			
		||||
              && p->y.pTab->aCol[p->iColumn].notNull==0);
 | 
			
		||||
@@ -123747,12 +123661,9 @@ SQLITE_PRIVATE void sqlite3CreateView(
 | 
			
		||||
  ** on a view, even though views do not have rowids.  The following flag
 | 
			
		||||
  ** setting fixes this problem.  But the fix can be disabled by compiling
 | 
			
		||||
  ** with -DSQLITE_ALLOW_ROWID_IN_VIEW in case there are legacy apps that
 | 
			
		||||
  ** depend upon the old buggy behavior.  The ability can also be toggled
 | 
			
		||||
  ** using sqlite3_config(SQLITE_CONFIG_ROWID_IN_VIEW,...) */
 | 
			
		||||
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
 | 
			
		||||
  p->tabFlags |= sqlite3Config.mNoVisibleRowid; /* Optional. Allow by default */
 | 
			
		||||
#else
 | 
			
		||||
  p->tabFlags |= TF_NoVisibleRowid;             /* Never allow rowid in view */
 | 
			
		||||
  ** depend upon the old buggy behavior. */
 | 
			
		||||
#ifndef SQLITE_ALLOW_ROWID_IN_VIEW
 | 
			
		||||
  p->tabFlags |= TF_NoVisibleRowid;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  sqlite3TwoPartName(pParse, pName1, pName2, &pName);
 | 
			
		||||
@@ -129916,7 +129827,7 @@ static void sumFinalize(sqlite3_context *context){
 | 
			
		||||
    if( p->approx ){
 | 
			
		||||
      if( p->ovrfl ){
 | 
			
		||||
        sqlite3_result_error(context,"integer overflow",-1);
 | 
			
		||||
      }else if( !sqlite3IsOverflow(p->rErr) ){
 | 
			
		||||
      }else if( !sqlite3IsNaN(p->rErr) ){
 | 
			
		||||
        sqlite3_result_double(context, p->rSum+p->rErr);
 | 
			
		||||
      }else{
 | 
			
		||||
        sqlite3_result_double(context, p->rSum);
 | 
			
		||||
@@ -129933,7 +129844,7 @@ static void avgFinalize(sqlite3_context *context){
 | 
			
		||||
    double r;
 | 
			
		||||
    if( p->approx ){
 | 
			
		||||
      r = p->rSum;
 | 
			
		||||
      if( !sqlite3IsOverflow(p->rErr) ) r += p->rErr;
 | 
			
		||||
      if( !sqlite3IsNaN(p->rErr) ) r += p->rErr;
 | 
			
		||||
    }else{
 | 
			
		||||
      r = (double)(p->iSum);
 | 
			
		||||
    }
 | 
			
		||||
@@ -129947,7 +129858,7 @@ static void totalFinalize(sqlite3_context *context){
 | 
			
		||||
  if( p ){
 | 
			
		||||
    if( p->approx ){
 | 
			
		||||
      r = p->rSum;
 | 
			
		||||
      if( !sqlite3IsOverflow(p->rErr) ) r += p->rErr;
 | 
			
		||||
      if( !sqlite3IsNaN(p->rErr) ) r += p->rErr;
 | 
			
		||||
    }else{
 | 
			
		||||
      r = (double)(p->iSum);
 | 
			
		||||
    }
 | 
			
		||||
@@ -135245,10 +135156,7 @@ static int xferOptimization(
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#ifndef SQLITE_OMIT_CHECK
 | 
			
		||||
  if( pDest->pCheck
 | 
			
		||||
   && (db->mDbFlags & DBFLAG_Vacuum)==0
 | 
			
		||||
   && sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1)
 | 
			
		||||
  ){
 | 
			
		||||
  if( pDest->pCheck && sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1) ){
 | 
			
		||||
    return 0;   /* Tables have different CHECK constraints.  Ticket #2252 */
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
@@ -140649,11 +140557,7 @@ static int pragmaVtabBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
 | 
			
		||||
  j = seen[0]-1;
 | 
			
		||||
  pIdxInfo->aConstraintUsage[j].argvIndex = 1;
 | 
			
		||||
  pIdxInfo->aConstraintUsage[j].omit = 1;
 | 
			
		||||
  if( seen[1]==0 ){
 | 
			
		||||
    pIdxInfo->estimatedCost = (double)1000;
 | 
			
		||||
    pIdxInfo->estimatedRows = 1000;
 | 
			
		||||
    return SQLITE_OK;
 | 
			
		||||
  }
 | 
			
		||||
  if( seen[1]==0 ) return SQLITE_OK;
 | 
			
		||||
  pIdxInfo->estimatedCost = (double)20;
 | 
			
		||||
  pIdxInfo->estimatedRows = 20;
 | 
			
		||||
  j = seen[1]-1;
 | 
			
		||||
@@ -143880,7 +143784,11 @@ static const char *columnTypeImpl(
 | 
			
		||||
        ** data for the result-set column of the sub-select.
 | 
			
		||||
        */
 | 
			
		||||
        if( iCol<pS->pEList->nExpr
 | 
			
		||||
         && (!ViewCanHaveRowid || iCol>=0)
 | 
			
		||||
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
 | 
			
		||||
         && iCol>=0
 | 
			
		||||
#else
 | 
			
		||||
         && ALWAYS(iCol>=0)
 | 
			
		||||
#endif
 | 
			
		||||
        ){
 | 
			
		||||
          /* If iCol is less than zero, then the expression requests the
 | 
			
		||||
          ** rowid of the sub-select or view. This expression is legal (see
 | 
			
		||||
@@ -147055,10 +146963,6 @@ static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){
 | 
			
		||||
**
 | 
			
		||||
**  (11) The subquery is not a VALUES clause
 | 
			
		||||
**
 | 
			
		||||
**  (12) The WHERE clause is not "rowid ISNULL" or the equivalent.  This
 | 
			
		||||
**       case only comes up if SQLite is compiled using
 | 
			
		||||
**       SQLITE_ALLOW_ROWID_IN_VIEW.
 | 
			
		||||
**
 | 
			
		||||
** Return 0 if no changes are made and non-zero if one or more WHERE clause
 | 
			
		||||
** terms are duplicated into the subquery.
 | 
			
		||||
*/
 | 
			
		||||
@@ -147169,18 +147073,6 @@ static int pushDownWhereTerms(
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
 | 
			
		||||
  if( ViewCanHaveRowid && (pWhere->op==TK_ISNULL || pWhere->op==TK_NOTNULL) ){
 | 
			
		||||
    Expr *pLeft = pWhere->pLeft;
 | 
			
		||||
    if( ALWAYS(pLeft)
 | 
			
		||||
     && pLeft->op==TK_COLUMN
 | 
			
		||||
     && pLeft->iColumn < 0
 | 
			
		||||
    ){
 | 
			
		||||
      return 0;  /* Restriction (12) */
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if( sqlite3ExprIsSingleTableConstraint(pWhere, pSrcList, iSrc) ){
 | 
			
		||||
    nChng++;
 | 
			
		||||
    pSubq->selFlags |= SF_PushDown;
 | 
			
		||||
@@ -147808,14 +147700,12 @@ SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse *pParse, SrcItem *pFrom){
 | 
			
		||||
  while( pSel->pPrior ){ pSel = pSel->pPrior; }
 | 
			
		||||
  sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol);
 | 
			
		||||
  pTab->iPKey = -1;
 | 
			
		||||
  pTab->eTabType = TABTYP_VIEW;
 | 
			
		||||
  pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) );
 | 
			
		||||
#ifndef SQLITE_ALLOW_ROWID_IN_VIEW
 | 
			
		||||
  /* The usual case - do not allow ROWID on a subquery */
 | 
			
		||||
  pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid;
 | 
			
		||||
#else
 | 
			
		||||
  /* Legacy compatibility mode */
 | 
			
		||||
  pTab->tabFlags |= TF_Ephemeral | sqlite3Config.mNoVisibleRowid;
 | 
			
		||||
  pTab->tabFlags |= TF_Ephemeral;  /* Legacy compatibility mode */
 | 
			
		||||
#endif
 | 
			
		||||
  return pParse->nErr ? SQLITE_ERROR : SQLITE_OK;
 | 
			
		||||
}
 | 
			
		||||
@@ -148083,7 +147973,7 @@ static int selectExpander(Walker *pWalker, Select *p){
 | 
			
		||||
            pNestedFrom = pFrom->pSelect->pEList;
 | 
			
		||||
            assert( pNestedFrom!=0 );
 | 
			
		||||
            assert( pNestedFrom->nExpr==pTab->nCol );
 | 
			
		||||
            assert( VisibleRowid(pTab)==0 || ViewCanHaveRowid );
 | 
			
		||||
            assert( VisibleRowid(pTab)==0 );
 | 
			
		||||
          }else{
 | 
			
		||||
            if( zTName && sqlite3StrICmp(zTName, zTabName)!=0 ){
 | 
			
		||||
              continue;
 | 
			
		||||
@@ -148115,8 +148005,7 @@ static int selectExpander(Walker *pWalker, Select *p){
 | 
			
		||||
            pUsing = 0;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          nAdd = pTab->nCol;
 | 
			
		||||
          if( VisibleRowid(pTab) && (selFlags & SF_NestedFrom)!=0 ) nAdd++;
 | 
			
		||||
          nAdd = pTab->nCol + (VisibleRowid(pTab) && (selFlags&SF_NestedFrom));
 | 
			
		||||
          for(j=0; j<nAdd; j++){
 | 
			
		||||
            const char *zName;
 | 
			
		||||
            struct ExprList_item *pX; /* Newly added ExprList term */
 | 
			
		||||
@@ -148198,8 +148087,7 @@ static int selectExpander(Walker *pWalker, Select *p){
 | 
			
		||||
            pX = &pNew->a[pNew->nExpr-1];
 | 
			
		||||
            assert( pX->zEName==0 );
 | 
			
		||||
            if( (selFlags & SF_NestedFrom)!=0 && !IN_RENAME_OBJECT ){
 | 
			
		||||
              if( pNestedFrom && (!ViewCanHaveRowid || j<pNestedFrom->nExpr) ){
 | 
			
		||||
                assert( j<pNestedFrom->nExpr );
 | 
			
		||||
              if( pNestedFrom ){
 | 
			
		||||
                pX->zEName = sqlite3DbStrDup(db, pNestedFrom->a[j].zEName);
 | 
			
		||||
                testcase( pX->zEName==0 );
 | 
			
		||||
              }else{
 | 
			
		||||
@@ -153133,9 +153021,6 @@ SQLITE_PRIVATE void sqlite3Update(
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if( chngRowid==0 && pPk==0 ){
 | 
			
		||||
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
 | 
			
		||||
      if( isView ) sqlite3VdbeAddOp2(v, OP_Null, 0, regOldRowid);
 | 
			
		||||
#endif
 | 
			
		||||
      sqlite3VdbeAddOp2(v, OP_Copy, regOldRowid, regNewRowid);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -166845,10 +166730,16 @@ static SQLITE_NOINLINE void whereAddIndexedExpr(
 | 
			
		||||
  for(i=0; i<pIdx->nColumn; i++){
 | 
			
		||||
    Expr *pExpr;
 | 
			
		||||
    int j = pIdx->aiColumn[i];
 | 
			
		||||
    int bMaybeNullRow;
 | 
			
		||||
    if( j==XN_EXPR ){
 | 
			
		||||
      pExpr = pIdx->aColExpr->a[i].pExpr;
 | 
			
		||||
      testcase( pTabItem->fg.jointype & JT_LEFT );
 | 
			
		||||
      testcase( pTabItem->fg.jointype & JT_RIGHT );
 | 
			
		||||
      testcase( pTabItem->fg.jointype & JT_LTORJ );
 | 
			
		||||
      bMaybeNullRow = (pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0;
 | 
			
		||||
    }else if( j>=0 && (pTab->aCol[j].colFlags & COLFLAG_VIRTUAL)!=0 ){
 | 
			
		||||
      pExpr = sqlite3ColumnExpr(pTab, &pTab->aCol[j]);
 | 
			
		||||
      bMaybeNullRow = 0;
 | 
			
		||||
    }else{
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
@@ -166880,7 +166771,7 @@ static SQLITE_NOINLINE void whereAddIndexedExpr(
 | 
			
		||||
    p->iDataCur = pTabItem->iCursor;
 | 
			
		||||
    p->iIdxCur = iIdxCur;
 | 
			
		||||
    p->iIdxCol = i;
 | 
			
		||||
    p->bMaybeNullRow = (pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0;
 | 
			
		||||
    p->bMaybeNullRow = bMaybeNullRow;
 | 
			
		||||
    if( sqlite3IndexAffinityStr(pParse->db, pIdx) ){
 | 
			
		||||
      p->aff = pIdx->zColAff[i];
 | 
			
		||||
    }
 | 
			
		||||
@@ -179085,18 +178976,6 @@ SQLITE_API int sqlite3_config(int op, ...){
 | 
			
		||||
    }
 | 
			
		||||
#endif /* SQLITE_OMIT_DESERIALIZE */
 | 
			
		||||
 | 
			
		||||
    case SQLITE_CONFIG_ROWID_IN_VIEW: {
 | 
			
		||||
      int *pVal = va_arg(ap,int*);
 | 
			
		||||
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
 | 
			
		||||
      if( 0==*pVal ) sqlite3GlobalConfig.mNoVisibleRowid = TF_NoVisibleRowid;
 | 
			
		||||
      if( 1==*pVal ) sqlite3GlobalConfig.mNoVisibleRowid = 0;
 | 
			
		||||
      *pVal = (sqlite3GlobalConfig.mNoVisibleRowid==0);
 | 
			
		||||
#else
 | 
			
		||||
      *pVal = 0;
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    default: {
 | 
			
		||||
      rc = SQLITE_ERROR;
 | 
			
		||||
      break;
 | 
			
		||||
@@ -250799,7 +250678,7 @@ static void fts5SourceIdFunc(
 | 
			
		||||
){
 | 
			
		||||
  assert( nArg==0 );
 | 
			
		||||
  UNUSED_PARAM2(nArg, apUnused);
 | 
			
		||||
  sqlite3_result_text(pCtx, "fts5: 2024-04-15 13:34:05 8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355", -1, SQLITE_TRANSIENT);
 | 
			
		||||
  sqlite3_result_text(pCtx, "fts5: 2024-03-12 11:06:23 d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77", -1, SQLITE_TRANSIENT);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								deps/sqlite/sqlite3.h
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								deps/sqlite/sqlite3.h
									
									
									
									
										vendored
									
									
								
							@@ -146,9 +146,9 @@ extern "C" {
 | 
			
		||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
 | 
			
		||||
** [sqlite_version()] and [sqlite_source_id()].
 | 
			
		||||
*/
 | 
			
		||||
#define SQLITE_VERSION        "3.45.3"
 | 
			
		||||
#define SQLITE_VERSION_NUMBER 3045003
 | 
			
		||||
#define SQLITE_SOURCE_ID      "2024-04-15 13:34:05 8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355"
 | 
			
		||||
#define SQLITE_VERSION        "3.45.2"
 | 
			
		||||
#define SQLITE_VERSION_NUMBER 3045002
 | 
			
		||||
#define SQLITE_SOURCE_ID      "2024-03-12 11:06:23 d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77"
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
** CAPI3REF: Run-Time Library Version Numbers
 | 
			
		||||
@@ -2143,22 +2143,6 @@ struct sqlite3_mem_methods {
 | 
			
		||||
** configuration setting is never used, then the default maximum is determined
 | 
			
		||||
** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option.  If that
 | 
			
		||||
** compile-time option is not set, then the default maximum is 1073741824.
 | 
			
		||||
**
 | 
			
		||||
** [[SQLITE_CONFIG_ROWID_IN_VIEW]]
 | 
			
		||||
** <dt>SQLITE_CONFIG_ROWID_IN_VIEW
 | 
			
		||||
** <dd>The SQLITE_CONFIG_ROWID_IN_VIEW option enables or disables the ability
 | 
			
		||||
** for VIEWs to have a ROWID.  The capability can only be enabled if SQLite is
 | 
			
		||||
** compiled with -DSQLITE_ALLOW_ROWID_IN_VIEW, in which case the capability
 | 
			
		||||
** defaults to on.  This configuration option queries the current setting or
 | 
			
		||||
** changes the setting to off or on.  The argument is a pointer to an integer.
 | 
			
		||||
** If that integer initially holds a value of 1, then the ability for VIEWs to
 | 
			
		||||
** have ROWIDs is activated.  If the integer initially holds zero, then the
 | 
			
		||||
** ability is deactivated.  Any other initial value for the integer leaves the
 | 
			
		||||
** setting unchanged.  After changes, if any, the integer is written with
 | 
			
		||||
** a 1 or 0, if the ability for VIEWs to have ROWIDs is on or off.  If SQLite
 | 
			
		||||
** is compiled without -DSQLITE_ALLOW_ROWID_IN_VIEW (which is the usual and
 | 
			
		||||
** recommended case) then the integer is always filled with zero, regardless
 | 
			
		||||
** if its initial value.
 | 
			
		||||
** </dl>
 | 
			
		||||
*/
 | 
			
		||||
#define SQLITE_CONFIG_SINGLETHREAD         1  /* nil */
 | 
			
		||||
@@ -2190,7 +2174,6 @@ struct sqlite3_mem_methods {
 | 
			
		||||
#define SQLITE_CONFIG_SMALL_MALLOC        27  /* boolean */
 | 
			
		||||
#define SQLITE_CONFIG_SORTERREF_SIZE      28  /* int nByte */
 | 
			
		||||
#define SQLITE_CONFIG_MEMDB_MAXSIZE       29  /* sqlite3_int64 */
 | 
			
		||||
#define SQLITE_CONFIG_ROWID_IN_VIEW       30  /* int* */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
** CAPI3REF: Database Connection Configuration Options
 | 
			
		||||
 
 | 
			
		||||
@@ -1,50 +0,0 @@
 | 
			
		||||
# Writing Tilde Friends applications7
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
 | 
			
		||||
## Creating your environment
 | 
			
		||||
 | 
			
		||||
1. Open an existing application (ie: `identity`);
 | 
			
		||||
2. Open the editing panel;
 | 
			
		||||
3. Save the app under a new name (ie `/~YOUR_USERNAME/my-app/`);
 | 
			
		||||
4. Go back to the main menu and open your new app;
 | 
			
		||||
5. You can now edit your app, save it and see changes in the real time.
 | 
			
		||||
 | 
			
		||||
## Project structure
 | 
			
		||||
 | 
			
		||||
An application has a `app.js` file that gets run when a user enters the app.
 | 
			
		||||
This file contains a function (typically called `main()`) that's considered the entry point.
 | 
			
		||||
 | 
			
		||||
Paste this in `app.js`:
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
async function main() {
 | 
			
		||||
	let ids = await ssb.getIdentities();
 | 
			
		||||
	await app.setDocument(`
 | 
			
		||||
		<body style="font-family: sans-serif; color: white">
 | 
			
		||||
			<h1>Hello world!</h1>
 | 
			
		||||
		</body>
 | 
			
		||||
	</body>`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
main();
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Save the app, and you should now be seeing `Hello world!` on the screen.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Components
 | 
			
		||||
 | 
			
		||||
Once your app grows to a certain size, you'll want to introduce components.
 | 
			
		||||
In Tilde Friends, the de facto standard is [Lit](TODO).
 | 
			
		||||
Althogh you an use any framework you want, you're encouraged to use Lit as you can reuse
 | 
			
		||||
 | 
			
		||||
First, add lit-all-min.js into your project.
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
 | 
			
		||||
<!-- mention shadow dom -->
 | 
			
		||||
 | 
			
		||||
TODO: tfrpc
 | 
			
		||||
 | 
			
		||||
TODO: sharing apps
 | 
			
		||||
@@ -1,76 +0,0 @@
 | 
			
		||||
# How to build Tilde Friends
 | 
			
		||||
 | 
			
		||||
> Disclaimer: this documentation has been written by a Linux user and has not been reviewed by other people on other platforms. The procedure may vary slightly depending on your operating system.
 | 
			
		||||
 | 
			
		||||
Builds **on** Linux (`x86_64` and `aarch64`), MacOS, OpenBSD, and Haiku.
 | 
			
		||||
 | 
			
		||||
Builds **for** all of those host platforms plus `mingw64`, iOS, and android.
 | 
			
		||||
 | 
			
		||||
Dependencies:
 | 
			
		||||
 | 
			
		||||
- `openssl` (`libssl-dev`, in debian-speak)
 | 
			
		||||
 | 
			
		||||
Dependencies for Android:
 | 
			
		||||
 | 
			
		||||
- TODO
 | 
			
		||||
 | 
			
		||||
Dependencies for iOS:
 | 
			
		||||
 | 
			
		||||
- TODO
 | 
			
		||||
 | 
			
		||||
Dependencies for Windows:
 | 
			
		||||
 | 
			
		||||
- TODO
 | 
			
		||||
 | 
			
		||||
> All other dependencies are kept up to date as git submodules.
 | 
			
		||||
 | 
			
		||||
1. Clone the repository with the submodules: `git clone --recursive https://dev.tildefriends.net/cory/tildefriends.git`
 | 
			
		||||
 | 
			
		||||
2. Run `make -j $(nproc) debug` or `make -j $(nproc) release`
 | 
			
		||||
 | 
			
		||||
If you're unsure whether you should choose `debug` or `release`, stick to `release`.
 | 
			
		||||
 | 
			
		||||
> `-j $(nproc)` will start a compiler for every CPU thread, which will dramatically reduce the time needed to compile Tilde Friends.
 | 
			
		||||
 | 
			
		||||
An executable will be generated in a subdirectory of `out/`
 | 
			
		||||
 | 
			
		||||
It's possible to build for Android, iOS, and Windows on Linux, if you have the right dependencies in the right places. Run `make -j $(nproc) windebug winrelease iosdebug-ipa iosrelease-ipa release-apk`
 | 
			
		||||
 | 
			
		||||
To build in docker, `docker build .`
 | 
			
		||||
 | 
			
		||||
<!-- On NixOS: TODO -->
 | 
			
		||||
<!-- Add shell.nix and nix derivs first -->
 | 
			
		||||
 | 
			
		||||
Now that you have a binary, head over to <running.md>.
 | 
			
		||||
 | 
			
		||||
## Troubleshooting
 | 
			
		||||
 | 
			
		||||
### The compiler throws an error and I can't build the binary
 | 
			
		||||
 | 
			
		||||
Open `GNUMakefile` and edit the CFLAGS environment variable around line 50.
 | 
			
		||||
 | 
			
		||||
For example given this error:
 | 
			
		||||
 | 
			
		||||
```text
 | 
			
		||||
src/http.c: In function 'tf_http_get_cookie':
 | 
			
		||||
src/http.c:1089:128: error: check of 'name' for NULL after already dereferencing it [-Werror=analyzer-deref-before-check]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Add:
 | 
			
		||||
 | 
			
		||||
```diff
 | 
			
		||||
CFLAGS += \
 | 
			
		||||
	-std=gnu11 \
 | 
			
		||||
	-Wall \
 | 
			
		||||
	-Wextra \
 | 
			
		||||
	-Wno-unused-parameter \
 | 
			
		||||
+	-Wno-analyzer-deref-before-check \
 | 
			
		||||
	-MMD \
 | 
			
		||||
	-MP \
 | 
			
		||||
	-ffunction-sections \
 | 
			
		||||
	-fdata-sections \
 | 
			
		||||
	-fno-exceptions \
 | 
			
		||||
	-g
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Now the compiler will ignore this error and *should* continue building anyways.
 | 
			
		||||
@@ -1,42 +0,0 @@
 | 
			
		||||
# How to contribute
 | 
			
		||||
 | 
			
		||||
- Fork this repository
 | 
			
		||||
 | 
			
		||||
- Clone your repository
 | 
			
		||||
 | 
			
		||||
Alternatively, you can change the `origin` remote on your existing clone:
 | 
			
		||||
 | 
			
		||||
`git remote set-url origin https://dev.tildefriends.net/YOUR_USERNAME/tildefriends.git`
 | 
			
		||||
 | 
			
		||||
- Make your changes
 | 
			
		||||
 | 
			
		||||
  - I want to edit C code !
 | 
			
		||||
 | 
			
		||||
    TODO
 | 
			
		||||
 | 
			
		||||
  - I want to edit JavaScript code !
 | 
			
		||||
 | 
			
		||||
    TODO
 | 
			
		||||
 | 
			
		||||
  - I want to write documentation !
 | 
			
		||||
 | 
			
		||||
    Great! Before you do, have a look at the [documentation guidelines](guidelines/documentation-guidelines.md) to learn how to write consistent documentation.
 | 
			
		||||
 | 
			
		||||
    In all cases:
 | 
			
		||||
 | 
			
		||||
    - Make sure that your commit messages are descriptive.
 | 
			
		||||
    <!-- - hi -->
 | 
			
		||||
 | 
			
		||||
- Format your changes:
 | 
			
		||||
 | 
			
		||||
If you've edited C code: run `make format`
 | 
			
		||||
 | 
			
		||||
If you've edited JavaScript code or the documentation: run `npm run format`
 | 
			
		||||
 | 
			
		||||
- Open a pull request
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
 | 
			
		||||
- Get your changes reviewed and merged
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
@@ -1,19 +0,0 @@
 | 
			
		||||
# Tilde Friends documentation
 | 
			
		||||
 | 
			
		||||
## Building
 | 
			
		||||
 | 
			
		||||
See <building.md>.
 | 
			
		||||
 | 
			
		||||
## Contibuting
 | 
			
		||||
 | 
			
		||||
See <contributing.md>.
 | 
			
		||||
 | 
			
		||||
## FAQ / Troubleshooting
 | 
			
		||||
 | 
			
		||||
See <faq.md>.
 | 
			
		||||
 | 
			
		||||
## Guide
 | 
			
		||||
 | 
			
		||||
This document will be phased out and integrated into the new documentation.
 | 
			
		||||
 | 
			
		||||
See <.guide.md>.
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
# Troubleshooting
 | 
			
		||||
 | 
			
		||||
## I started tildefriends. Now what ?
 | 
			
		||||
 | 
			
		||||
See <running.md>.
 | 
			
		||||
 | 
			
		||||
### The compiler throws an error and I can't build the binary
 | 
			
		||||
 | 
			
		||||
See <building.md>.
 | 
			
		||||
@@ -1,6 +1,4 @@
 | 
			
		||||
# Tilde Friends
 | 
			
		||||
 | 
			
		||||
## Philosophy
 | 
			
		||||
# Philosophy
 | 
			
		||||
 | 
			
		||||
Tilde Friends is a platform for making, running, and sharing web applications.
 | 
			
		||||
 | 
			
		||||
@@ -20,7 +18,7 @@ Above the terminal, an "Edit" link brings a visitor to the source code for the
 | 
			
		||||
current Tilde Friends application, which they can then edit, save as their own,
 | 
			
		||||
and run.
 | 
			
		||||
 | 
			
		||||
## Architecture
 | 
			
		||||
# Architecture
 | 
			
		||||
 | 
			
		||||
Tilde Friends is a C++ application with a JavaScript runtime that provides
 | 
			
		||||
restricted access to filesystem, network, and other system resources. The core
 | 
			
		||||
@@ -68,7 +66,7 @@ performance reasons to minimize the data size transferred between processes.
 | 
			
		||||
 | 
			
		||||
    // Receive the above message and call the function.
 | 
			
		||||
    core.register("onMessage", function(sender, message) {
 | 
			
		||||
        message.add(3, 4).then(x => terminal.print(x.toString()));
 | 
			
		||||
    	message.add(3, 4).then(x => terminal.print(x.toString()));
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
Finally, there is a core web interface that runs on the client's browser that
 | 
			
		||||
@@ -137,18 +135,16 @@ Sets the browser window/tab title.
 | 
			
		||||
 | 
			
		||||
Reconfigures the terminal layout, potentially into multiple split panes.
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
terminal.split(
 | 
			
		||||
    [{
 | 
			
		||||
        type: "horizontal",
 | 
			
		||||
        children: [
 | 
			
		||||
            {name: "left", basis: "2in", grow: 0, shrink: 0},
 | 
			
		||||
            {name: "middle", grow: 1},
 | 
			
		||||
            {name: "right", basis: "2in", grow: 0, shrink: 0},
 | 
			
		||||
        ],
 | 
			
		||||
    }]
 | 
			
		||||
);
 | 
			
		||||
```
 | 
			
		||||
    terminal.split([
 | 
			
		||||
    	{
 | 
			
		||||
    		type: "horizontal",
 | 
			
		||||
    		children: [
 | 
			
		||||
    			{name: "left", basis: "2in", grow: 0, shrink: 0},
 | 
			
		||||
    			{name: "middle", grow: 1},
 | 
			
		||||
    			{name: "right", basis: "2in", grow: 0, shrink: 0},
 | 
			
		||||
    		],
 | 
			
		||||
    	},
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
#### terminal.select(name)
 | 
			
		||||
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
TODO
 | 
			
		||||
@@ -1,64 +0,0 @@
 | 
			
		||||
# Documentation guidelines
 | 
			
		||||
 | 
			
		||||
This document defines the rules used to write documentation in order to make it more consistent.
 | 
			
		||||
 | 
			
		||||
This documentation is a living document and so are it's rules; you are free to propose changes but in the meantime, please stick to them.
 | 
			
		||||
 | 
			
		||||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119/).
 | 
			
		||||
 | 
			
		||||
## File naming
 | 
			
		||||
 | 
			
		||||
Files SHOULD be named using [kebab-case](https://www.freecodecamp.org/news/snake-case-vs-camel-case-vs-pascal-case-vs-kebab-case-whats-the-difference/#kebab-case).
 | 
			
		||||
 | 
			
		||||
Their names should be meaningful and SHOULD not conflict with other files in other directories:
 | 
			
		||||
 | 
			
		||||
> Example: this document is named `docs/guidelines/documentation-guidelines.md` instead of `docs/guidelines/documentation.md` because it could cause confusion with `docs/documentation.md`.
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
When writing documentation, the author should have in mind it's target audience: people with varying technical skills and backgrounds, fluency in peer-to-peer-specific terms and mental ability.
 | 
			
		||||
The documentation should therefore be acessible and usefule to most people interested in building, using and contributing to Tilde Friends.
 | 
			
		||||
 | 
			
		||||
### Terminology
 | 
			
		||||
 | 
			
		||||
`Tilde Friends` refers to the projectas a whole. This can be abbreviated to `TF`.
 | 
			
		||||
 | 
			
		||||
`tildefriends` refers to the program.
 | 
			
		||||
 | 
			
		||||
### Style guide
 | 
			
		||||
 | 
			
		||||
1. Lines SHOULD NOT be wrapped, to allow clients to dynamically wrap them however they want:
 | 
			
		||||
 | 
			
		||||
    ```text
 | 
			
		||||
    This is not very pleasant to read because
 | 
			
		||||
    the text
 | 
			
		||||
    is manually wrapped, but the size of the
 | 
			
		||||
    screen is
 | 
			
		||||
    smaller than the size the text is wrapped
 | 
			
		||||
    at. I
 | 
			
		||||
    need to write even more useless text here
 | 
			
		||||
    so I get
 | 
			
		||||
    my point across. Also hi! If you're here
 | 
			
		||||
    that
 | 
			
		||||
    means you're either going to contribute to
 | 
			
		||||
    Tilde
 | 
			
		||||
    Friends, or that you're reviewing my
 | 
			
		||||
    stupid
 | 
			
		||||
    changes. Either way, you're awesome!
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    You MAY use one line per sentence.
 | 
			
		||||
 | 
			
		||||
2. Lines ending with an `inline code block` SHOULD NOT end with a period.
 | 
			
		||||
 | 
			
		||||
> Example: To build in docker, `docker build .`
 | 
			
		||||
 | 
			
		||||
NB: this does not apply to file names or other text that are not meant to be copy-pasted.
 | 
			
		||||
 | 
			
		||||
> Example: this document is named `docs/guidelines/documentation-guidelines.md` instead of `docs/guidelines/documentation.md` because it could cause confusion with `docs/documentation.md`.
 | 
			
		||||
 | 
			
		||||
More TODO
 | 
			
		||||
 | 
			
		||||
## License
 | 
			
		||||
 | 
			
		||||
As per the rest of the code in this repository, the documentation is shared under the [MIT](https://opensource.org/licenses/MIT/) license.
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
TODO
 | 
			
		||||
@@ -1,50 +0,0 @@
 | 
			
		||||
# Running Tilde Friends
 | 
			
		||||
 | 
			
		||||
> Disclaimer: this documentation has been written by a Linux user and has not been reviewed by other people on other platforms. The procedure may vary slightly depending on your operating system.
 | 
			
		||||
 | 
			
		||||
The binaries should appear at `out/debug/tildefriends` and `out/release/tildefriends`.
 | 
			
		||||
 | 
			
		||||
For Android, iOS and Windows: TODO
 | 
			
		||||
 | 
			
		||||
You can now start the server by running `./out/debug/tildefriends` or `./out/release/tildefriends`.
 | 
			
		||||
 | 
			
		||||
By default, running the built `tildefriends` executable will start a web server
 | 
			
		||||
at <http://localhost:12345/>. `tildefriends -h` lists further options.
 | 
			
		||||
 | 
			
		||||
## How to use TF
 | 
			
		||||
 | 
			
		||||
### Initial setup
 | 
			
		||||
 | 
			
		||||
Now you have a Tilde Friends instance running. The first thing you'll want to do is create your account. Click "login" in the top right corner, then "Register".
 | 
			
		||||
Enter your username and password.
 | 
			
		||||
 | 
			
		||||
> The first user to create an account and log in will be granted administrative privileges.
 | 
			
		||||
> Further administration can be done at <http://localhost:12345/~core/admin/>
 | 
			
		||||
 | 
			
		||||
Next, create a Scuttlebutt identity by pressing the "Create an identity" button.
 | 
			
		||||
This will create a pair of keys that are used to sign your messages with.
 | 
			
		||||
 | 
			
		||||
Because of the way Scuttlebutt is designed, you cannot log into your account without your keys. 
 | 
			
		||||
Tilde Friends locks your keys behind a password, but if you were to destroy your database, the keys would be gone forever, and with it your possibility to send messages using this account. Click on the `identity` app and under "Identities", export your newly created identity.
 | 
			
		||||
 | 
			
		||||
You'll be prompted with a dialog box saying "This app is requesting the following permission:ssb_id_export".
 | 
			
		||||
This is because applications are not trusted to have access to your keys by default.
 | 
			
		||||
Click on "Allow" and you'll see a list of 12 words. You need to write those down in a password manager or on a piece of paperand keep it private and secure.
 | 
			
		||||
 | 
			
		||||
> Warning: Nobody needs to know these 12 words. Anybody that has access to those keys can post messages as you, see your private messages and documents and much more.
 | 
			
		||||
 | 
			
		||||
Now that your keys are safe, we can start connecting to the outside world.
 | 
			
		||||
 | 
			
		||||
### Replication
 | 
			
		||||
 | 
			
		||||
You've probably noticed asdtring of random characters by now. This is your public key, a unique identifier for your account you can share to anyone. If you go back to the home menu and into the `ssb` app, you can click on your public key. This will lead you to your profile, which is empty at the time. Edit it and enter your name.
 | 
			
		||||
 | 
			
		||||
TODO: joining a room
 | 
			
		||||
 | 
			
		||||
TODO: initial sync
 | 
			
		||||
 | 
			
		||||
TODO: send messages
 | 
			
		||||
 | 
			
		||||
TODO: how messages spread to friends
 | 
			
		||||
 | 
			
		||||
TODO: other apps
 | 
			
		||||
							
								
								
									
										717
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										717
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -5,443 +5,15 @@
 | 
			
		||||
	"packages": {
 | 
			
		||||
		"": {
 | 
			
		||||
			"name": "tildefriends",
 | 
			
		||||
			"hasInstallScript": true,
 | 
			
		||||
			"license": "MIT",
 | 
			
		||||
			"devDependencies": {
 | 
			
		||||
				"markdownlint-cli": "0.40.0",
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"prettier": "3.2.5"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/@isaacs/cliui": {
 | 
			
		||||
			"version": "8.0.2",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
 | 
			
		||||
			"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"string-width": "^5.1.2",
 | 
			
		||||
				"string-width-cjs": "npm:string-width@^4.2.0",
 | 
			
		||||
				"strip-ansi": "^7.0.1",
 | 
			
		||||
				"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
 | 
			
		||||
				"wrap-ansi": "^8.1.0",
 | 
			
		||||
				"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=12"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/@pkgjs/parseargs": {
 | 
			
		||||
			"version": "0.11.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
 | 
			
		||||
			"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"optional": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=14"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/ansi-regex": {
 | 
			
		||||
			"version": "6.0.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
 | 
			
		||||
			"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=12"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/chalk/ansi-regex?sponsor=1"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/ansi-styles": {
 | 
			
		||||
			"version": "6.2.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
 | 
			
		||||
			"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=12"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/chalk/ansi-styles?sponsor=1"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/argparse": {
 | 
			
		||||
			"version": "2.0.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
 | 
			
		||||
			"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
 | 
			
		||||
			"dev": true
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/balanced-match": {
 | 
			
		||||
			"version": "1.0.2",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
 | 
			
		||||
			"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
 | 
			
		||||
			"dev": true
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/brace-expansion": {
 | 
			
		||||
			"version": "2.0.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
 | 
			
		||||
			"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"balanced-match": "^1.0.0"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/color-convert": {
 | 
			
		||||
			"version": "2.0.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
 | 
			
		||||
			"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"color-name": "~1.1.4"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=7.0.0"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/color-name": {
 | 
			
		||||
			"version": "1.1.4",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
 | 
			
		||||
			"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
 | 
			
		||||
			"dev": true
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/commander": {
 | 
			
		||||
			"version": "12.0.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
 | 
			
		||||
			"integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=18"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/cross-spawn": {
 | 
			
		||||
			"version": "7.0.3",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
 | 
			
		||||
			"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"path-key": "^3.1.0",
 | 
			
		||||
				"shebang-command": "^2.0.0",
 | 
			
		||||
				"which": "^2.0.1"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">= 8"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/deep-extend": {
 | 
			
		||||
			"version": "0.6.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
 | 
			
		||||
			"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=4.0.0"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/eastasianwidth": {
 | 
			
		||||
			"version": "0.2.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
 | 
			
		||||
			"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
 | 
			
		||||
			"dev": true
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/emoji-regex": {
 | 
			
		||||
			"version": "9.2.2",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
 | 
			
		||||
			"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
 | 
			
		||||
			"dev": true
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/entities": {
 | 
			
		||||
			"version": "4.5.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
 | 
			
		||||
			"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=0.12"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/fb55/entities?sponsor=1"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/foreground-child": {
 | 
			
		||||
			"version": "3.1.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
 | 
			
		||||
			"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"cross-spawn": "^7.0.0",
 | 
			
		||||
				"signal-exit": "^4.0.1"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=14"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/sponsors/isaacs"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/get-stdin": {
 | 
			
		||||
			"version": "9.0.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz",
 | 
			
		||||
			"integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=12"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/sponsors/sindresorhus"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/glob": {
 | 
			
		||||
			"version": "10.3.14",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.14.tgz",
 | 
			
		||||
			"integrity": "sha512-4fkAqu93xe9Mk7le9v0y3VrPDqLKHarNi2s4Pv7f2yOvfhWfhc7hRPHC/JyqMqb8B/Dt/eGS4n7ykwf3fOsl8g==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"foreground-child": "^3.1.0",
 | 
			
		||||
				"jackspeak": "^2.3.6",
 | 
			
		||||
				"minimatch": "^9.0.1",
 | 
			
		||||
				"minipass": "^7.0.4",
 | 
			
		||||
				"path-scurry": "^1.11.0"
 | 
			
		||||
			},
 | 
			
		||||
			"bin": {
 | 
			
		||||
				"glob": "dist/esm/bin.mjs"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=16 || 14 >=14.17"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/sponsors/isaacs"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/ignore": {
 | 
			
		||||
			"version": "5.3.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
 | 
			
		||||
			"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">= 4"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/ini": {
 | 
			
		||||
			"version": "4.1.2",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz",
 | 
			
		||||
			"integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/is-fullwidth-code-point": {
 | 
			
		||||
			"version": "3.0.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
 | 
			
		||||
			"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=8"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/isexe": {
 | 
			
		||||
			"version": "2.0.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
 | 
			
		||||
			"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
 | 
			
		||||
			"dev": true
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/jackspeak": {
 | 
			
		||||
			"version": "2.3.6",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
 | 
			
		||||
			"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"@isaacs/cliui": "^8.0.2"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=14"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/sponsors/isaacs"
 | 
			
		||||
			},
 | 
			
		||||
			"optionalDependencies": {
 | 
			
		||||
				"@pkgjs/parseargs": "^0.11.0"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/js-yaml": {
 | 
			
		||||
			"version": "4.1.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
 | 
			
		||||
			"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"argparse": "^2.0.1"
 | 
			
		||||
			},
 | 
			
		||||
			"bin": {
 | 
			
		||||
				"js-yaml": "bin/js-yaml.js"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/jsonc-parser": {
 | 
			
		||||
			"version": "3.2.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz",
 | 
			
		||||
			"integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==",
 | 
			
		||||
			"dev": true
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/jsonpointer": {
 | 
			
		||||
			"version": "5.0.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz",
 | 
			
		||||
			"integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=0.10.0"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/linkify-it": {
 | 
			
		||||
			"version": "5.0.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
 | 
			
		||||
			"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"uc.micro": "^2.0.0"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/lru-cache": {
 | 
			
		||||
			"version": "10.2.2",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
 | 
			
		||||
			"integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": "14 || >=16.14"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/markdown-it": {
 | 
			
		||||
			"version": "14.1.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
 | 
			
		||||
			"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"argparse": "^2.0.1",
 | 
			
		||||
				"entities": "^4.4.0",
 | 
			
		||||
				"linkify-it": "^5.0.0",
 | 
			
		||||
				"mdurl": "^2.0.0",
 | 
			
		||||
				"punycode.js": "^2.3.1",
 | 
			
		||||
				"uc.micro": "^2.1.0"
 | 
			
		||||
			},
 | 
			
		||||
			"bin": {
 | 
			
		||||
				"markdown-it": "bin/markdown-it.mjs"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/markdownlint": {
 | 
			
		||||
			"version": "0.34.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.34.0.tgz",
 | 
			
		||||
			"integrity": "sha512-qwGyuyKwjkEMOJ10XN6OTKNOVYvOIi35RNvDLNxTof5s8UmyGHlCdpngRHoRGNvQVGuxO3BJ7uNSgdeX166WXw==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"markdown-it": "14.1.0",
 | 
			
		||||
				"markdownlint-micromark": "0.1.9"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=18"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/sponsors/DavidAnson"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/markdownlint-cli": {
 | 
			
		||||
			"version": "0.40.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.40.0.tgz",
 | 
			
		||||
			"integrity": "sha512-JXhI3dRQcaqwiFYpPz6VJ7aKYheD53GmTz9y4D/d0F1MbZDGOp9pqKlbOfUX/pHP/iAoeiE4wYRmk8/kjLakxA==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"commander": "~12.0.0",
 | 
			
		||||
				"get-stdin": "~9.0.0",
 | 
			
		||||
				"glob": "~10.3.12",
 | 
			
		||||
				"ignore": "~5.3.1",
 | 
			
		||||
				"js-yaml": "^4.1.0",
 | 
			
		||||
				"jsonc-parser": "~3.2.1",
 | 
			
		||||
				"jsonpointer": "5.0.1",
 | 
			
		||||
				"markdownlint": "~0.34.0",
 | 
			
		||||
				"minimatch": "~9.0.4",
 | 
			
		||||
				"run-con": "~1.3.2",
 | 
			
		||||
				"toml": "~3.0.0"
 | 
			
		||||
			},
 | 
			
		||||
			"bin": {
 | 
			
		||||
				"markdownlint": "markdownlint.js"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=18"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/markdownlint-micromark": {
 | 
			
		||||
			"version": "0.1.9",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.9.tgz",
 | 
			
		||||
			"integrity": "sha512-5hVs/DzAFa8XqYosbEAEg6ok6MF2smDj89ztn9pKkCtdKHVdPQuGMH7frFfYL9mLkvfFe4pTyAMffLbjf3/EyA==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=18"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/sponsors/DavidAnson"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/mdurl": {
 | 
			
		||||
			"version": "2.0.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
 | 
			
		||||
			"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
 | 
			
		||||
			"dev": true
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/minimatch": {
 | 
			
		||||
			"version": "9.0.4",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
 | 
			
		||||
			"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"brace-expansion": "^2.0.1"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=16 || 14 >=14.17"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/sponsors/isaacs"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/minimist": {
 | 
			
		||||
			"version": "1.2.8",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
 | 
			
		||||
			"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/minipass": {
 | 
			
		||||
			"version": "7.1.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz",
 | 
			
		||||
			"integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=16 || 14 >=14.17"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/path-key": {
 | 
			
		||||
			"version": "3.1.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
 | 
			
		||||
			"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=8"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/path-scurry": {
 | 
			
		||||
			"version": "1.11.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.0.tgz",
 | 
			
		||||
			"integrity": "sha512-LNHTaVkzaYaLGlO+0u3rQTz7QrHTFOuKyba9JMTQutkmtNew8dw8wOD7mTU/5fCPZzCWpfW0XnQKzY61P0aTaw==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"lru-cache": "^10.2.0",
 | 
			
		||||
				"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=16 || 14 >=14.17"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/sponsors/isaacs"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/prettier": {
 | 
			
		||||
			"version": "3.2.5",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
 | 
			
		||||
			"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"license": "MIT",
 | 
			
		||||
			"bin": {
 | 
			
		||||
				"prettier": "bin/prettier.cjs"
 | 
			
		||||
			},
 | 
			
		||||
@@ -451,289 +23,6 @@
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/prettier/prettier?sponsor=1"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/punycode.js": {
 | 
			
		||||
			"version": "2.3.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
 | 
			
		||||
			"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=6"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/run-con": {
 | 
			
		||||
			"version": "1.3.2",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/run-con/-/run-con-1.3.2.tgz",
 | 
			
		||||
			"integrity": "sha512-CcfE+mYiTcKEzg0IqS08+efdnH0oJ3zV0wSUFBNrMHMuxCtXvBCLzCJHatwuXDcu/RlhjTziTo/a1ruQik6/Yg==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"deep-extend": "^0.6.0",
 | 
			
		||||
				"ini": "~4.1.0",
 | 
			
		||||
				"minimist": "^1.2.8",
 | 
			
		||||
				"strip-json-comments": "~3.1.1"
 | 
			
		||||
			},
 | 
			
		||||
			"bin": {
 | 
			
		||||
				"run-con": "cli.js"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/shebang-command": {
 | 
			
		||||
			"version": "2.0.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
 | 
			
		||||
			"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"shebang-regex": "^3.0.0"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=8"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/shebang-regex": {
 | 
			
		||||
			"version": "3.0.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
 | 
			
		||||
			"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=8"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/signal-exit": {
 | 
			
		||||
			"version": "4.1.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
 | 
			
		||||
			"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=14"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/sponsors/isaacs"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/string-width": {
 | 
			
		||||
			"version": "5.1.2",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
 | 
			
		||||
			"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"eastasianwidth": "^0.2.0",
 | 
			
		||||
				"emoji-regex": "^9.2.2",
 | 
			
		||||
				"strip-ansi": "^7.0.1"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=12"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/sponsors/sindresorhus"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/string-width-cjs": {
 | 
			
		||||
			"name": "string-width",
 | 
			
		||||
			"version": "4.2.3",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
 | 
			
		||||
			"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"emoji-regex": "^8.0.0",
 | 
			
		||||
				"is-fullwidth-code-point": "^3.0.0",
 | 
			
		||||
				"strip-ansi": "^6.0.1"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=8"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/string-width-cjs/node_modules/ansi-regex": {
 | 
			
		||||
			"version": "5.0.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
 | 
			
		||||
			"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=8"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/string-width-cjs/node_modules/emoji-regex": {
 | 
			
		||||
			"version": "8.0.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
 | 
			
		||||
			"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
 | 
			
		||||
			"dev": true
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/string-width-cjs/node_modules/strip-ansi": {
 | 
			
		||||
			"version": "6.0.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
 | 
			
		||||
			"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"ansi-regex": "^5.0.1"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=8"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/strip-ansi": {
 | 
			
		||||
			"version": "7.1.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
 | 
			
		||||
			"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"ansi-regex": "^6.0.1"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=12"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/chalk/strip-ansi?sponsor=1"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/strip-ansi-cjs": {
 | 
			
		||||
			"name": "strip-ansi",
 | 
			
		||||
			"version": "6.0.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
 | 
			
		||||
			"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"ansi-regex": "^5.0.1"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=8"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
 | 
			
		||||
			"version": "5.0.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
 | 
			
		||||
			"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=8"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/strip-json-comments": {
 | 
			
		||||
			"version": "3.1.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
 | 
			
		||||
			"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=8"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/sponsors/sindresorhus"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/toml": {
 | 
			
		||||
			"version": "3.0.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz",
 | 
			
		||||
			"integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==",
 | 
			
		||||
			"dev": true
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/uc.micro": {
 | 
			
		||||
			"version": "2.1.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
 | 
			
		||||
			"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
 | 
			
		||||
			"dev": true
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/which": {
 | 
			
		||||
			"version": "2.0.2",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
 | 
			
		||||
			"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"isexe": "^2.0.0"
 | 
			
		||||
			},
 | 
			
		||||
			"bin": {
 | 
			
		||||
				"node-which": "bin/node-which"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">= 8"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/wrap-ansi": {
 | 
			
		||||
			"version": "8.1.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
 | 
			
		||||
			"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"ansi-styles": "^6.1.0",
 | 
			
		||||
				"string-width": "^5.0.1",
 | 
			
		||||
				"strip-ansi": "^7.0.1"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=12"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/wrap-ansi-cjs": {
 | 
			
		||||
			"name": "wrap-ansi",
 | 
			
		||||
			"version": "7.0.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
 | 
			
		||||
			"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"ansi-styles": "^4.0.0",
 | 
			
		||||
				"string-width": "^4.1.0",
 | 
			
		||||
				"strip-ansi": "^6.0.0"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=10"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
 | 
			
		||||
			"version": "5.0.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
 | 
			
		||||
			"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=8"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
 | 
			
		||||
			"version": "4.3.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
 | 
			
		||||
			"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"color-convert": "^2.0.1"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=8"
 | 
			
		||||
			},
 | 
			
		||||
			"funding": {
 | 
			
		||||
				"url": "https://github.com/chalk/ansi-styles?sponsor=1"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
 | 
			
		||||
			"version": "8.0.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
 | 
			
		||||
			"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
 | 
			
		||||
			"dev": true
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/wrap-ansi-cjs/node_modules/string-width": {
 | 
			
		||||
			"version": "4.2.3",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
 | 
			
		||||
			"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"emoji-regex": "^8.0.0",
 | 
			
		||||
				"is-fullwidth-code-point": "^3.0.0",
 | 
			
		||||
				"strip-ansi": "^6.0.1"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=8"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
 | 
			
		||||
			"version": "6.0.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
 | 
			
		||||
			"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
 | 
			
		||||
			"dev": true,
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"ansi-regex": "^5.0.1"
 | 
			
		||||
			},
 | 
			
		||||
			"engines": {
 | 
			
		||||
				"node": ">=8"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,12 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "tildefriends",
 | 
			
		||||
	"scripts": {
 | 
			
		||||
		"format": "npm run prettier && npm run markdown",
 | 
			
		||||
		"prettier": "npx prettier --cache --write --check .",
 | 
			
		||||
		"markdown": "npx markdownlint-cli --fix 'docs/**/*.md'"
 | 
			
		||||
		"prettier": "prettier . --check --cache --write",
 | 
			
		||||
		"postinstall": "sh tools/install_dependencies.sh"
 | 
			
		||||
	},
 | 
			
		||||
	"author": "Cory McWilliams",
 | 
			
		||||
	"license": "MIT",
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"markdownlint-cli": "0.40.0",
 | 
			
		||||
	"dependencies": {
 | 
			
		||||
		"prettier": "3.2.5"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								shell.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								shell.nix
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
with import <nixpkgs> {};
 | 
			
		||||
stdenv.mkDerivation {
 | 
			
		||||
  name = "env";
 | 
			
		||||
  nativeBuildInputs = [
 | 
			
		||||
    cmake
 | 
			
		||||
    openssl
 | 
			
		||||
    nodePackages.npm
 | 
			
		||||
    jdk11
 | 
			
		||||
  ];
 | 
			
		||||
  buildInputs = [
 | 
			
		||||
  
 | 
			
		||||
  ];
 | 
			
		||||
}
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
	package="com.unprompted.tildefriends"
 | 
			
		||||
	android:versionCode="19"
 | 
			
		||||
	android:versionName="0.0.19-wip">
 | 
			
		||||
	android:versionCode="17"
 | 
			
		||||
	android:versionName="0.0.17-wip">
 | 
			
		||||
	<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34"/>
 | 
			
		||||
	<uses-permission android:name="android.permission.INTERNET"/>
 | 
			
		||||
	<application
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
		android:extractNativeLibs="true">
 | 
			
		||||
		<meta-data android:name="android.max_aspect" android:value="2.1"/>
 | 
			
		||||
		<activity
 | 
			
		||||
			android:name=".TildeFriendsActivity"
 | 
			
		||||
			android:name=".MainActivity"
 | 
			
		||||
			android:icon="@drawable/icon"
 | 
			
		||||
			android:configChanges="orientation|screenSize"
 | 
			
		||||
			android:exported="true">
 | 
			
		||||
 
 | 
			
		||||
@@ -15,13 +15,10 @@ import android.os.strictmode.Violation;
 | 
			
		||||
import android.util.Base64;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
import android.view.KeyEvent;
 | 
			
		||||
import android.view.MotionEvent;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.view.ViewGroup.LayoutParams;
 | 
			
		||||
import android.view.Window;
 | 
			
		||||
import android.webkit.CookieManager;
 | 
			
		||||
import android.webkit.DownloadListener;
 | 
			
		||||
import android.webkit.JsPromptResult;
 | 
			
		||||
import android.webkit.JsResult;
 | 
			
		||||
import android.webkit.URLUtil;
 | 
			
		||||
import android.webkit.ValueCallback;
 | 
			
		||||
@@ -50,15 +47,14 @@ import java.nio.file.WatchKey;
 | 
			
		||||
import java.nio.file.WatchService;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
public class TildeFriendsActivity extends Activity {
 | 
			
		||||
	TildeFriendsWebView web_view;
 | 
			
		||||
public class MainActivity extends Activity {
 | 
			
		||||
	WebView web_view;
 | 
			
		||||
	String base_url;
 | 
			
		||||
	Process process;
 | 
			
		||||
	Thread thread;
 | 
			
		||||
 | 
			
		||||
	private ValueCallback<Uri[]> upload_message;
 | 
			
		||||
	private final static int FILECHOOSER_RESULT = 1;
 | 
			
		||||
	private float touch_down_y;
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void onCreate(Bundle savedInstanceState) {
 | 
			
		||||
@@ -70,7 +66,7 @@ public class TildeFriendsActivity extends Activity {
 | 
			
		||||
		this.requestWindowFeature(Window.FEATURE_NO_TITLE);
 | 
			
		||||
		setContentView(R.layout.activity_main);
 | 
			
		||||
 | 
			
		||||
		web_view = (TildeFriendsWebView)findViewById(R.id.web);
 | 
			
		||||
		web_view = (WebView)findViewById(R.id.web);
 | 
			
		||||
		set_status("Extracting executable...");
 | 
			
		||||
		Log.w("tildefriends", String.format("getFilesDir() is %s", getFilesDir().toString()));
 | 
			
		||||
		Log.w("tildefriends", String.format("getPackageResourcePath() is %s", getPackageResourcePath().toString()));
 | 
			
		||||
@@ -80,7 +76,7 @@ public class TildeFriendsActivity extends Activity {
 | 
			
		||||
		new File(port_file_path).delete();
 | 
			
		||||
		base_url = "http://127.0.0.1:12345/";
 | 
			
		||||
 | 
			
		||||
		TildeFriendsActivity activity = this;
 | 
			
		||||
		MainActivity activity = this;
 | 
			
		||||
 | 
			
		||||
		thread = new Thread(new Runnable() {
 | 
			
		||||
			@Override
 | 
			
		||||
@@ -184,7 +180,7 @@ public class TildeFriendsActivity extends Activity {
 | 
			
		||||
 | 
			
		||||
		web_view.setWebChromeClient(new WebChromeClient() {
 | 
			
		||||
			@Override
 | 
			
		||||
			public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
 | 
			
		||||
			public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
 | 
			
		||||
				new AlertDialog.Builder(view.getContext())
 | 
			
		||||
					.setTitle("Tilde Friends")
 | 
			
		||||
					.setMessage(message)
 | 
			
		||||
@@ -207,23 +203,6 @@ public class TildeFriendsActivity extends Activity {
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			@Override
 | 
			
		||||
			public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
 | 
			
		||||
				new AlertDialog.Builder(view.getContext())
 | 
			
		||||
					.setTitle("Tilde Friends")
 | 
			
		||||
					.setMessage(message)
 | 
			
		||||
					.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener()
 | 
			
		||||
					{
 | 
			
		||||
						public void onClick(DialogInterface dialog, int which)
 | 
			
		||||
						{
 | 
			
		||||
							result.confirm();
 | 
			
		||||
						}
 | 
			
		||||
					})
 | 
			
		||||
					.create()
 | 
			
		||||
					.show();
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			/*
 | 
			
		||||
			** https://stackoverflow.com/questions/5907369/file-upload-in-webview
 | 
			
		||||
			** https://stackoverflow.com/questions/8586691/how-to-open-file-save-dialog-in-android
 | 
			
		||||
@@ -235,7 +214,7 @@ public class TildeFriendsActivity extends Activity {
 | 
			
		||||
				Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
 | 
			
		||||
				intent.addCategory(Intent.CATEGORY_OPENABLE);
 | 
			
		||||
				intent.setType("*/*");
 | 
			
		||||
				TildeFriendsActivity.this.startActivityForResult(Intent.createChooser(intent, "File Chooser"), TildeFriendsActivity.FILECHOOSER_RESULT);
 | 
			
		||||
				MainActivity.this.startActivityForResult(Intent.createChooser(intent, "File Chooser"), MainActivity.FILECHOOSER_RESULT);
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@@ -247,6 +226,7 @@ public class TildeFriendsActivity extends Activity {
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		web_view.setWebViewClient(new WebViewClient() {
 | 
			
		||||
			@Override
 | 
			
		||||
			public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
 | 
			
		||||
			{
 | 
			
		||||
				if (request.getUrl() != null && request.getUrl().toString().startsWith(base_url)) {
 | 
			
		||||
@@ -258,9 +238,12 @@ public class TildeFriendsActivity extends Activity {
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		TextView refresh = (TextView)findViewById(R.id.refresh);
 | 
			
		||||
		refresh.setVisibility(View.GONE);
 | 
			
		||||
		refresh.setText("REFRESH");
 | 
			
		||||
		Button refresh = (Button)findViewById(R.id.refresh);
 | 
			
		||||
		refresh.setOnClickListener(new View.OnClickListener() {
 | 
			
		||||
			public void onClick(View view) {
 | 
			
		||||
				web_view.reload();
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
@@ -315,40 +298,6 @@ public class TildeFriendsActivity extends Activity {
 | 
			
		||||
		return super.onKeyDown(keyCode, event);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean dispatchTouchEvent(MotionEvent event) {
 | 
			
		||||
		final int k_drag_distance = 160;
 | 
			
		||||
		switch (event.getActionMasked()) {
 | 
			
		||||
		case MotionEvent.ACTION_DOWN:
 | 
			
		||||
			touch_down_y = event.getY();
 | 
			
		||||
			web_view.clearOverscrolledY();
 | 
			
		||||
			break;
 | 
			
		||||
		case MotionEvent.ACTION_MOVE:
 | 
			
		||||
			{
 | 
			
		||||
				float delta = web_view.getOverscrolledY() ? event.getY() - touch_down_y : 0.0f;
 | 
			
		||||
				TextView refresh = (TextView)findViewById(R.id.refresh);
 | 
			
		||||
				LayoutParams layout = refresh.getLayoutParams();
 | 
			
		||||
				layout.height =
 | 
			
		||||
					Math.min(Math.max((int)delta, 0), k_drag_distance) +
 | 
			
		||||
					(delta > k_drag_distance ? (int)Math.sqrt(delta - k_drag_distance) : 0);
 | 
			
		||||
				refresh.setLayoutParams(layout);
 | 
			
		||||
				refresh.setVisibility(layout.height > 0 ? View.VISIBLE : View.GONE);
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
		case MotionEvent.ACTION_UP:
 | 
			
		||||
			{
 | 
			
		||||
				float delta = web_view.getOverscrolledY() ? event.getY() - touch_down_y : 0.0f;
 | 
			
		||||
				if (delta > getWindow().getDecorView().getHeight() / 4) {
 | 
			
		||||
					web_view.reload();
 | 
			
		||||
				}
 | 
			
		||||
				TextView refresh = (TextView)findViewById(R.id.refresh);
 | 
			
		||||
				refresh.setVisibility(View.GONE);
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
		return super.dispatchTouchEvent(event);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private int read_port(String path) {
 | 
			
		||||
		try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
 | 
			
		||||
			return Integer.parseInt(reader.readLine());
 | 
			
		||||
@@ -1,31 +0,0 @@
 | 
			
		||||
package com.unprompted.tildefriends;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.util.AttributeSet;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
 | 
			
		||||
public class TildeFriendsWebView extends android.webkit.WebView {
 | 
			
		||||
	boolean overscrolledY = false;
 | 
			
		||||
 | 
			
		||||
	public TildeFriendsWebView(final Context context) {
 | 
			
		||||
		super(context);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public TildeFriendsWebView(final Context context, final AttributeSet attrs) {
 | 
			
		||||
		super(context, attrs);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
 | 
			
		||||
		super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
 | 
			
		||||
		overscrolledY = true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean getOverscrolledY() {
 | 
			
		||||
		return overscrolledY;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void clearOverscrolledY() {
 | 
			
		||||
		overscrolledY = false;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
	android:layout_width="match_parent"
 | 
			
		||||
	android:layout_height="match_parent"
 | 
			
		||||
	android:orientation="vertical">
 | 
			
		||||
	<com.unprompted.tildefriends.TildeFriendsWebView
 | 
			
		||||
	<WebView
 | 
			
		||||
		android:id="@+id/web"
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="match_parent"/>
 | 
			
		||||
@@ -13,12 +13,12 @@
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="match_parent"
 | 
			
		||||
		android:gravity="center_horizontal|center_vertical"/>
 | 
			
		||||
	<TextView
 | 
			
		||||
	<Button
 | 
			
		||||
		android:id="@+id/refresh"
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="160dp"
 | 
			
		||||
		android:layout_alignParentTop="true"
 | 
			
		||||
		android:gravity="center_horizontal|center_vertical"
 | 
			
		||||
		android:textColor="#fff"
 | 
			
		||||
		android:background="#44f"/>
 | 
			
		||||
		android:layout_width="fill_parent"
 | 
			
		||||
		android:layout_height="wrap_content"
 | 
			
		||||
		android:layout_alignParentBottom="true"
 | 
			
		||||
		android:layout_alignParentLeft="true"
 | 
			
		||||
		android:layout_alignParentStart="true"
 | 
			
		||||
		android:text="REFRESH"/>
 | 
			
		||||
</RelativeLayout>
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@ static void _file_write_write_callback(uv_fs_t* req)
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to write %s: %s", req->path, uv_strerror(req->result)));
 | 
			
		||||
		tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(req->result)));
 | 
			
		||||
	}
 | 
			
		||||
	int result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
 | 
			
		||||
	if (result < 0)
 | 
			
		||||
@@ -91,7 +91,6 @@ static void _file_write_write_callback(uv_fs_t* req)
 | 
			
		||||
static void _file_write_open_callback(uv_fs_t* req)
 | 
			
		||||
{
 | 
			
		||||
	fs_req_t* fsreq = (fs_req_t*)req;
 | 
			
		||||
	const char* path = tf_strdup(req->path);
 | 
			
		||||
	uv_fs_req_cleanup(req);
 | 
			
		||||
	tf_task_t* task = req->loop->data;
 | 
			
		||||
	JSContext* context = tf_task_get_context(task);
 | 
			
		||||
@@ -103,8 +102,7 @@ static void _file_write_open_callback(uv_fs_t* req)
 | 
			
		||||
		int result = uv_fs_write(req->loop, req, fsreq->file, &buf, 1, 0, _file_write_write_callback);
 | 
			
		||||
		if (result < 0)
 | 
			
		||||
		{
 | 
			
		||||
			uv_fs_req_cleanup(req);
 | 
			
		||||
			tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to write %s: %s", path, uv_strerror(result)));
 | 
			
		||||
			tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(result)));
 | 
			
		||||
			result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
 | 
			
		||||
			if (result < 0)
 | 
			
		||||
			{
 | 
			
		||||
@@ -116,9 +114,9 @@ static void _file_write_open_callback(uv_fs_t* req)
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(req->result)));
 | 
			
		||||
		uv_fs_req_cleanup(req);
 | 
			
		||||
		tf_free(req);
 | 
			
		||||
	}
 | 
			
		||||
	tf_free((void*)path);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static JSValue _file_write_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
 | 
			
		||||
@@ -158,7 +156,7 @@ static JSValue _file_write_file(JSContext* context, JSValueConst this_val, int a
 | 
			
		||||
	int result = uv_fs_open(tf_task_get_loop(task), &req->fs, file_name, UV_FS_O_CREAT | UV_FS_O_WRONLY, 0644, _file_write_open_callback);
 | 
			
		||||
	if (result < 0)
 | 
			
		||||
	{
 | 
			
		||||
		tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to open %s for write: %s", file_name, uv_strerror(result)));
 | 
			
		||||
		tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(result)));
 | 
			
		||||
	}
 | 
			
		||||
	JS_FreeCString(context, file_name);
 | 
			
		||||
	return promise_value;
 | 
			
		||||
@@ -166,6 +164,7 @@ static JSValue _file_write_file(JSContext* context, JSValueConst this_val, int a
 | 
			
		||||
 | 
			
		||||
static void _file_read_read_callback(uv_fs_t* req)
 | 
			
		||||
{
 | 
			
		||||
	uv_fs_req_cleanup(req);
 | 
			
		||||
	fs_req_t* fsreq = (fs_req_t*)req;
 | 
			
		||||
	tf_task_t* task = req->loop->data;
 | 
			
		||||
	JSContext* context = tf_task_get_context(task);
 | 
			
		||||
@@ -178,9 +177,8 @@ static void _file_read_read_callback(uv_fs_t* req)
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to read %s: %s", req->path, uv_strerror(req->result)));
 | 
			
		||||
		tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(req->result)));
 | 
			
		||||
	}
 | 
			
		||||
	uv_fs_req_cleanup(req);
 | 
			
		||||
	int result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
 | 
			
		||||
	if (result < 0)
 | 
			
		||||
	{
 | 
			
		||||
@@ -191,7 +189,6 @@ static void _file_read_read_callback(uv_fs_t* req)
 | 
			
		||||
 | 
			
		||||
static void _file_read_open_callback(uv_fs_t* req)
 | 
			
		||||
{
 | 
			
		||||
	const char* path = tf_strdup(req->path);
 | 
			
		||||
	uv_fs_req_cleanup(req);
 | 
			
		||||
	fs_req_t* fsreq = (fs_req_t*)req;
 | 
			
		||||
	tf_task_t* task = req->loop->data;
 | 
			
		||||
@@ -204,7 +201,7 @@ static void _file_read_open_callback(uv_fs_t* req)
 | 
			
		||||
		int result = uv_fs_read(req->loop, req, fsreq->file, &buf, 1, 0, _file_read_read_callback);
 | 
			
		||||
		if (result < 0)
 | 
			
		||||
		{
 | 
			
		||||
			tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to read %s: %s", path, uv_strerror(result)));
 | 
			
		||||
			tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(result)));
 | 
			
		||||
			result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
 | 
			
		||||
			if (result < 0)
 | 
			
		||||
			{
 | 
			
		||||
@@ -215,11 +212,10 @@ static void _file_read_open_callback(uv_fs_t* req)
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to open %s for read: %s", path, uv_strerror(req->result)));
 | 
			
		||||
		tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(req->result)));
 | 
			
		||||
		uv_fs_req_cleanup(req);
 | 
			
		||||
		tf_free(req);
 | 
			
		||||
	}
 | 
			
		||||
	tf_free((void*)path);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
 | 
			
		||||
@@ -242,7 +238,7 @@ static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int ar
 | 
			
		||||
	int result = uv_fs_open(tf_task_get_loop(task), &req->fs, file_name, UV_FS_O_RDONLY, 0, _file_read_open_callback);
 | 
			
		||||
	if (result < 0)
 | 
			
		||||
	{
 | 
			
		||||
		tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to open %s for read: %s", file_name, uv_strerror(result)));
 | 
			
		||||
		tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(result)));
 | 
			
		||||
		uv_fs_req_cleanup(&req->fs);
 | 
			
		||||
		tf_free(req);
 | 
			
		||||
	}
 | 
			
		||||
@@ -320,13 +316,11 @@ static void _file_read_file_zip_after_work(uv_work_t* work, int status)
 | 
			
		||||
	tf_trace_begin(trace, "file_read_zip_after_work");
 | 
			
		||||
	if (data->result >= 0)
 | 
			
		||||
	{
 | 
			
		||||
		JSValue array = tf_util_new_uint8_array(data->context, data->buffer, data->result);
 | 
			
		||||
		tf_task_resolve_promise(data->task, data->promise, array);
 | 
			
		||||
		JS_FreeValue(data->context, array);
 | 
			
		||||
		tf_task_resolve_promise(data->task, data->promise, tf_util_new_uint8_array(data->context, data->buffer, data->result));
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		tf_task_reject_promise(data->task, data->promise, JS_ThrowInternalError(data->context, "Failed to read %s: %d.", data->file_path, data->result));
 | 
			
		||||
		tf_task_reject_promise(data->task, data->promise, JS_ThrowInternalError(data->context, "Error: %d.", data->result));
 | 
			
		||||
	}
 | 
			
		||||
	tf_free(data->buffer);
 | 
			
		||||
	tf_free((void*)data->file_path);
 | 
			
		||||
@@ -356,7 +350,7 @@ static JSValue _file_read_file_zip(JSContext* context, JSValueConst this_val, in
 | 
			
		||||
	int r = uv_queue_work(tf_task_get_loop(task), &work->request, _file_read_file_zip_work, _file_read_file_zip_after_work);
 | 
			
		||||
	if (r)
 | 
			
		||||
	{
 | 
			
		||||
		tf_task_reject_promise(task, work->promise, JS_ThrowInternalError(context, "Failed to create read work for %s: %s", file_name, uv_strerror(r)));
 | 
			
		||||
		tf_task_reject_promise(task, work->promise, JS_ThrowInternalError(context, "%s", uv_strerror(r)));
 | 
			
		||||
		tf_free((void*)work->file_path);
 | 
			
		||||
		tf_free(work);
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										119
									
								
								src/http.c
									
									
									
									
									
								
							
							
						
						
									
										119
									
								
								src/http.c
									
									
									
									
									
								
							@@ -67,7 +67,6 @@ typedef struct _tf_http_connection_t
 | 
			
		||||
typedef struct _tf_http_handler_t
 | 
			
		||||
{
 | 
			
		||||
	const char* pattern;
 | 
			
		||||
	bool is_wildcard;
 | 
			
		||||
	tf_http_callback_t* callback;
 | 
			
		||||
	tf_http_cleanup_t* cleanup;
 | 
			
		||||
	void* user_data;
 | 
			
		||||
@@ -128,48 +127,12 @@ static void _http_allocate_buffer(uv_handle_t* handle, size_t suggested_size, uv
 | 
			
		||||
	*buf = uv_buf_init(connection->incoming, sizeof(connection->incoming));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool _http_pattern_matches(const char* pattern, const char* path, bool is_wildcard)
 | 
			
		||||
{
 | 
			
		||||
	if (!pattern || !*pattern || (!is_wildcard && strcmp(path, pattern) == 0))
 | 
			
		||||
	{
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (is_wildcard)
 | 
			
		||||
	{
 | 
			
		||||
		int i = 0;
 | 
			
		||||
		int j = 0;
 | 
			
		||||
		while (pattern[i] && path[j] && pattern[i] != '*' && pattern[i] == path[j])
 | 
			
		||||
		{
 | 
			
		||||
			i++;
 | 
			
		||||
			j++;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (pattern[i] == '*')
 | 
			
		||||
		{
 | 
			
		||||
			while (true)
 | 
			
		||||
			{
 | 
			
		||||
				if (_http_pattern_matches(pattern + i + 1, path + j, strchr(pattern + i + 1, '*') != NULL))
 | 
			
		||||
				{
 | 
			
		||||
					return true;
 | 
			
		||||
				}
 | 
			
		||||
				if (!path[j])
 | 
			
		||||
				{
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
				j++;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return !pattern[i] && !path[j];
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool _http_find_handler(tf_http_t* http, const char* path, tf_http_callback_t** out_callback, const char** out_trace_name, void** out_user_data)
 | 
			
		||||
{
 | 
			
		||||
	for (int i = 0; i < http->handlers_count; i++)
 | 
			
		||||
	{
 | 
			
		||||
		if (_http_pattern_matches(http->handlers[i].pattern, path, http->handlers[i].is_wildcard))
 | 
			
		||||
		if (!http->handlers[i].pattern || !*http->handlers[i].pattern || strcmp(path, http->handlers[i].pattern) == 0 ||
 | 
			
		||||
			(*http->handlers[i].pattern && strncmp(path, http->handlers[i].pattern, strlen(http->handlers[i].pattern)) == 0 && path[strlen(http->handlers[i].pattern) - 1] == '/'))
 | 
			
		||||
		{
 | 
			
		||||
			*out_callback = http->handlers[i].callback;
 | 
			
		||||
			*out_trace_name = http->handlers[i].pattern;
 | 
			
		||||
@@ -198,11 +161,10 @@ static void _http_request_destroy(tf_http_request_t* request)
 | 
			
		||||
	tf_http_close_callback* on_close = request->on_close;
 | 
			
		||||
	if (on_close)
 | 
			
		||||
	{
 | 
			
		||||
		tf_trace_t* trace = request->http->trace;
 | 
			
		||||
		request->on_close = NULL;
 | 
			
		||||
		tf_trace_begin(trace, request->connection && request->connection->trace_name ? request->connection->trace_name : "websocket");
 | 
			
		||||
		tf_trace_begin(request->http->trace, request->connection && request->connection->trace_name ? request->connection->trace_name : "websocket");
 | 
			
		||||
		on_close(request);
 | 
			
		||||
		tf_trace_end(trace);
 | 
			
		||||
		tf_trace_end(request->http->trace);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -212,9 +174,12 @@ static void _http_connection_destroy(tf_http_connection_t* connection, const cha
 | 
			
		||||
 | 
			
		||||
	if (connection->request)
 | 
			
		||||
	{
 | 
			
		||||
		tf_http_request_t* request = connection->request;
 | 
			
		||||
		_http_request_destroy(connection->request);
 | 
			
		||||
		if (connection->request && connection->request->ref_count == 0)
 | 
			
		||||
		{
 | 
			
		||||
			tf_free(connection->request);
 | 
			
		||||
		}
 | 
			
		||||
		connection->request = NULL;
 | 
			
		||||
		_http_request_destroy(request);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (connection->tcp.data && !uv_is_closing((uv_handle_t*)&connection->tcp))
 | 
			
		||||
@@ -418,19 +383,11 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d
 | 
			
		||||
			};
 | 
			
		||||
			connection->request = request;
 | 
			
		||||
 | 
			
		||||
			if (!connection->http->is_shutting_down)
 | 
			
		||||
			{
 | 
			
		||||
				tf_http_request_ref(request);
 | 
			
		||||
				tf_trace_begin(connection->http->trace, connection->trace_name ? connection->trace_name : "http");
 | 
			
		||||
				connection->callback(request);
 | 
			
		||||
				tf_trace_end(connection->http->trace);
 | 
			
		||||
				tf_http_request_unref(request);
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				const char* k_payload = tf_http_status_text(503);
 | 
			
		||||
				tf_http_respond(request, 503, NULL, 0, k_payload, strlen(k_payload));
 | 
			
		||||
			}
 | 
			
		||||
			tf_http_request_ref(request);
 | 
			
		||||
			tf_trace_begin(connection->http->trace, connection->trace_name ? connection->trace_name : "http");
 | 
			
		||||
			connection->callback(request);
 | 
			
		||||
			tf_trace_end(connection->http->trace);
 | 
			
		||||
			tf_http_request_unref(request);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -731,7 +688,6 @@ void tf_http_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_
 | 
			
		||||
	http->handlers = tf_resize_vec(http->handlers, sizeof(tf_http_handler_t) * (http->handlers_count + 1));
 | 
			
		||||
	http->handlers[http->handlers_count++] = (tf_http_handler_t) {
 | 
			
		||||
		.pattern = tf_strdup(pattern),
 | 
			
		||||
		.is_wildcard = pattern && strchr(pattern, '*') != NULL,
 | 
			
		||||
		.callback = callback,
 | 
			
		||||
		.cleanup = cleanup,
 | 
			
		||||
		.user_data = user_data,
 | 
			
		||||
@@ -830,8 +786,6 @@ const char* tf_http_status_text(int status)
 | 
			
		||||
		return "File not found";
 | 
			
		||||
	case 500:
 | 
			
		||||
		return "Internal server error";
 | 
			
		||||
	case 503:
 | 
			
		||||
		return "Service Unavailable";
 | 
			
		||||
	default:
 | 
			
		||||
		return "Unknown";
 | 
			
		||||
	}
 | 
			
		||||
@@ -1011,11 +965,11 @@ void tf_http_request_unref(tf_http_request_t* request)
 | 
			
		||||
	tf_http_connection_t* connection = request->connection;
 | 
			
		||||
	if (--request->ref_count == 0)
 | 
			
		||||
	{
 | 
			
		||||
		_http_request_destroy(request);
 | 
			
		||||
		if (connection)
 | 
			
		||||
		{
 | 
			
		||||
			connection->request = NULL;
 | 
			
		||||
		}
 | 
			
		||||
		_http_request_destroy(request);
 | 
			
		||||
		tf_free(request);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -1068,46 +1022,3 @@ void* tf_http_get_user_data(tf_http_t* http)
 | 
			
		||||
{
 | 
			
		||||
	return http->user_data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const char* tf_http_get_cookie(const char* cookie_header, const char* name)
 | 
			
		||||
{
 | 
			
		||||
	if (!cookie_header)
 | 
			
		||||
	{
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	int name_start = 0;
 | 
			
		||||
	int equals = 0;
 | 
			
		||||
	for (int i = 0;; i++)
 | 
			
		||||
	{
 | 
			
		||||
		if (cookie_header[i] == '=')
 | 
			
		||||
		{
 | 
			
		||||
			equals = i;
 | 
			
		||||
		}
 | 
			
		||||
		else if (cookie_header[i] == ',' || cookie_header[i] == ';' || cookie_header[i] == '\0')
 | 
			
		||||
		{
 | 
			
		||||
			if (equals > name_start && strncmp(cookie_header + name_start, name, equals - name_start) == 0 && (int)strlen(name) == equals - name_start)
 | 
			
		||||
			{
 | 
			
		||||
				int length = i - equals - 1;
 | 
			
		||||
				char* result = tf_malloc(length + 1);
 | 
			
		||||
				memcpy(result, cookie_header + equals + 1, length);
 | 
			
		||||
				result[length] = '\0';
 | 
			
		||||
				return result;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (cookie_header[i] == '\0')
 | 
			
		||||
			{
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				name_start = i + 1;
 | 
			
		||||
				while (cookie_header[name_start] == ' ')
 | 
			
		||||
				{
 | 
			
		||||
					name_start++;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -196,15 +196,6 @@ void tf_http_request_unref(tf_http_request_t* request);
 | 
			
		||||
*/
 | 
			
		||||
const char* tf_http_request_get_header(tf_http_request_t* request, const char* name);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
** Get a cookie value from request headers.
 | 
			
		||||
** @param cookie_header The value of the "Cookie" header of the form
 | 
			
		||||
** "name1=value1; name2=value2".
 | 
			
		||||
** @param name The cookie name.
 | 
			
		||||
** @return The cookie value, if found, or NULL.  Must be freed with tf_free().
 | 
			
		||||
*/
 | 
			
		||||
const char* tf_http_get_cookie(const char* cookie_header, const char* name);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
** Send a websocket message.
 | 
			
		||||
** @param request The HTTP request which was previously updated to a websocket
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user