Compare commits

..

2 Commits

Author SHA1 Message Date
d67e47ae4b
nix(module): module boilerplate 2025-02-20 10:27:18 +01:00
b43b8da9ab
nix (test): fix build 2025-02-20 10:17:14 +01:00
188 changed files with 3460 additions and 8031 deletions

View File

@ -943,9 +943,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched. # Note: If this tag is empty the current directory is searched.
INPUT = README.md \ INPUT = README.md docs/ src/
docs/ \
src/
# This tag can be used to specify the character encoding of the source files # This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@ -1051,7 +1049,7 @@ EXAMPLE_RECURSIVE = NO
# that contain images that are to be included in the documentation (see the # that contain images that are to be included in the documentation (see the
# \image command). # \image command).
IMAGE_PATH = docs/images/ IMAGE_PATH =
# The INPUT_FILTER tag can be used to specify a program that doxygen should # The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program # invoke to filter for each input file. Doxygen will invoke the filter program
@ -2270,7 +2268,7 @@ GENERATE_AUTOGEN_DEF = NO
# database with symbols found by doxygen stored in tables. # database with symbols found by doxygen stored in tables.
# The default value is: NO. # The default value is: NO.
#GENERATE_SQLITE3 = NO GENERATE_SQLITE3 = NO
# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be # The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be
# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put # put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put
@ -2278,7 +2276,7 @@ GENERATE_AUTOGEN_DEF = NO
# The default directory is: sqlite3. # The default directory is: sqlite3.
# This tag requires that the tag GENERATE_SQLITE3 is set to YES. # This tag requires that the tag GENERATE_SQLITE3 is set to YES.
#SQLITE3_OUTPUT = sqlite3 SQLITE3_OUTPUT = sqlite3
# The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db # The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db
# database file will be recreated with each doxygen run. If set to NO, doxygen # database file will be recreated with each doxygen run. If set to NO, doxygen
@ -2286,7 +2284,7 @@ GENERATE_AUTOGEN_DEF = NO
# The default value is: YES. # The default value is: YES.
# This tag requires that the tag GENERATE_SQLITE3 is set to YES. # This tag requires that the tag GENERATE_SQLITE3 is set to YES.
#SQLITE3_RECREATE_DB = YES SQLITE3_RECREATE_DB = YES
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# Configuration options related to the Perl module output # Configuration options related to the Perl module output

View File

@ -16,14 +16,14 @@ MAKEFLAGS += --no-builtin-rules
## LD := Linker. ## LD := Linker.
## ANDROID_SDK := Path to the Android SDK. ## ANDROID_SDK := Path to the Android SDK.
VERSION_CODE := 38 VERSION_CODE := 33
VERSION_CODE_IOS := 14 VERSION_CODE_IOS := 8
VERSION_NUMBER := 0.0.32-wip VERSION_NUMBER := 0.0.28-wip
VERSION_NAME := This program kills fascists. VERSION_NAME := This program kills fascists.
IPHONEOS_VERSION_MIN=14.0 IPHONEOS_VERSION_MIN=14.0
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3500000.zip SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3490100.zip
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar
APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
@ -45,10 +45,6 @@ export TZ=UTC
ifeq ($(UNAME_S),Darwin) ifeq ($(UNAME_S),Darwin)
BUILD_TYPES := debug release iosdebug iosrelease iossimdebug iossimrelease BUILD_TYPES := debug release iosdebug iosrelease iossimdebug iossimrelease
HAVE_ANDROID = 0
HAVE_LINUX_IOS = 0
HAVE_LINUX_MACOS = 0
HAVE_WIN = 0
else ifeq ($(UNAME_S),Linux) else ifeq ($(UNAME_S),Linux)
BUILD_TYPES := debug release BUILD_TYPES := debug release
HAVE_ANDROID = $(if $(shell which $(ANDROID_SDK)/platform-tools/adb),1) HAVE_ANDROID = $(if $(shell which $(ANDROID_SDK)/platform-tools/adb),1)
@ -66,10 +62,6 @@ LDFLAGS += \
-lnetwork \ -lnetwork \
-Wno-stringop-overflow -Wno-stringop-overflow
USE_SYSTEM_SSL := 1 USE_SYSTEM_SSL := 1
HAVE_ANDROID = 0
HAVE_LINUX_IOS = 0
HAVE_LINUX_MACOS = 0
HAVE_WIN = 0
else ifeq ($(UNAME_S),OpenBSD) else ifeq ($(UNAME_S),OpenBSD)
BUILD_TYPES := debug release BUILD_TYPES := debug release
CFLAGS += \ CFLAGS += \
@ -614,16 +606,15 @@ $(UV_OBJS): CFLAGS += \
-Ideps/libuv/include \ -Ideps/libuv/include \
-Ideps/libuv/src \ -Ideps/libuv/src \
-Wno-dangling-pointer \ -Wno-dangling-pointer \
-Wno-format-truncation \
-Wno-incompatible-pointer-types \ -Wno-incompatible-pointer-types \
-Wno-maybe-uninitialized \ -Wno-maybe-uninitialized \
-Wno-nonnull \
-Wno-sign-compare \ -Wno-sign-compare \
-Wno-unknown-attributes \ -Wno-unknown-attributes \
-Wno-unused-but-set-parameter \ -Wno-unused-but-set-parameter \
-Wno-unused-but-set-variable \ -Wno-unused-but-set-variable \
-Wno-unused-result \ -Wno-unused-result \
-Wno-unused-variable -Wno-unused-variable \
-Wno-nonnull
$(filter out/win%,$(UV_OBJS)): \ $(filter out/win%,$(UV_OBJS)): \
CFLAGS += \ CFLAGS += \
-Wno-cast-function-type \ -Wno-cast-function-type \
@ -719,12 +710,12 @@ $(SQLITE_OBJS): CFLAGS += \
-DSQLITE_MAX_COMPOUND_SELECT=300 \ -DSQLITE_MAX_COMPOUND_SELECT=300 \
-DSQLITE_MAX_EXPR_DEPTH=40 \ -DSQLITE_MAX_EXPR_DEPTH=40 \
-DSQLITE_MAX_FUNCTION_ARG=8 \ -DSQLITE_MAX_FUNCTION_ARG=8 \
-DSQLITE_MAX_LENGTH=10485760 \ -DSQLITE_MAX_LENGTH=5242880 \
-DSQLITE_MAX_LIKE_PATTERN_LENGTH=50 \ -DSQLITE_MAX_LIKE_PATTERN_LENGTH=50 \
-DSQLITE_MAX_SQL_LENGTH=100000 \ -DSQLITE_MAX_SQL_LENGTH=100000 \
-DSQLITE_MAX_TRIGGER_DEPTH=10 \ -DSQLITE_MAX_TRIGGER_DEPTH=10 \
-DSQLITE_MAX_VARIABLE_NUMBER=100 \ -DSQLITE_MAX_VARIABLE_NUMBER=100 \
-DSQLITE_MAX_VDBE_OP=50000 \ -DSQLITE_MAX_VDBE_OP=25000 \
-DSQLITE_OMIT_DEPRECATED \ -DSQLITE_OMIT_DEPRECATED \
-DSQLITE_OMIT_DESERIALIZE \ -DSQLITE_OMIT_DESERIALIZE \
-DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_OMIT_LOAD_EXTENSION \
@ -743,7 +734,7 @@ $(SQLITE_OBJS): CFLAGS += \
QUICKJS_SOURCES := \ QUICKJS_SOURCES := \
deps/quickjs/cutils.c \ deps/quickjs/cutils.c \
deps/quickjs/dtoa.c \ deps/quickjs/libbf.c \
deps/quickjs/libregexp.c \ deps/quickjs/libregexp.c \
deps/quickjs/libunicode.c \ deps/quickjs/libunicode.c \
deps/quickjs/quickjs.c deps/quickjs/quickjs.c
@ -994,8 +985,7 @@ PACKAGE_DIRS := \
core \ core \
deps/codemirror \ deps/codemirror \
deps/prettier \ deps/prettier \
deps/lit \ deps/lit
deps/speedscope
RAW_FILES := $(sort $(filter-out apps/blog% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f -not -name '.*'))) RAW_FILES := $(sort $(filter-out apps/blog% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f -not -name '.*')))
@ -1016,7 +1006,7 @@ $(BUNDLETOOL):
@curl -q -L --create-dirs -o $@ $(BUNDLETOOL_URL) @curl -q -L --create-dirs -o $@ $(BUNDLETOOL_URL)
out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGETS)) $(RAW_FILES) out/apk/res.apk src/android/AndroidManifest.xml $(BUNDLETOOL) out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGETS)) $(RAW_FILES) out/apk/res.apk src/android/AndroidManifest.xml $(BUNDLETOOL)
@rm -rf out/aab/staging/ out/aab/base.zip @rm -rf out/aab/staging/
@mkdir -p out/aab/staging @mkdir -p out/aab/staging
@$(ANDROID_BUILD_TOOLS)/aapt2 link --proto-format -o out/aab/temporary.apk \ @$(ANDROID_BUILD_TOOLS)/aapt2 link --proto-format -o out/aab/temporary.apk \
-I $(ANDROID_PLATFORM)/android.jar \ -I $(ANDROID_PLATFORM)/android.jar \
@ -1036,11 +1026,14 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
@cp out/apk/classes.dex out/aab/staging/dex/ @cp out/apk/classes.dex out/aab/staging/dex/
@rm -fv out/base.zip @rm -fv out/base.zip
@mkdir -p out/aab/staging/lib/arm64-v8a out/aab/staging/lib/armeabi-v7a out/aab/staging/lib/x86_64 out/aab/staging/lib/x86 @mkdir -p out/aab/staging/lib/arm64-v8a out/aab/staging/lib/armeabi-v7a out/aab/staging/lib/x86_64 out/aab/staging/lib/x86
@mkdir -p out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64 out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86 @cp out/androidrelease/tildefriends out/aab/staging/lib/arm64-v8a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease/tildefriends -o out/aab/staging/lib/arm64-v8a/libtildefriends.so @cp out/androidrelease-armv7a/tildefriends out/aab/staging/lib/armeabi-v7a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-armv7a/tildefriends -o out/aab/staging/lib/armeabi-v7a/libtildefriends.so @cp out/androidrelease-x86_64/tildefriends out/aab/staging/lib/x86_64/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86_64/tildefriends -o out/aab/staging/lib/x86_64/libtildefriends.so @cp out/androidrelease-x86/tildefriends out/aab/staging/lib/x86/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86/tildefriends -o out/aab/staging/lib/x86/libtildefriends.so @$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/arm64-v8a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/armeabi-v7a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86_64/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86/libtildefriends.so
@cp -r apps/ out/aab/staging/root/ @cp -r apps/ out/aab/staging/root/
@rm -rf out/aab/staging/root/apps/welcome* @rm -rf out/aab/staging/root/apps/welcome*
@cp -r core/ out/aab/staging/root/ @cp -r core/ out/aab/staging/root/
@ -1049,11 +1042,6 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
@cp -r deps/codemirror/ out/aab/staging/root/deps/ @cp -r deps/codemirror/ out/aab/staging/root/deps/
@cd out/aab/staging/; zip -r ../base.zip *; cd ../../../ @cd out/aab/staging/; zip -r ../base.zip *; cd ../../../
@java -jar $(BUNDLETOOL) build-bundle --overwrite --config=src/android/BundleConfig.json --modules=out/aab/base.zip --output=$@ @java -jar $(BUNDLETOOL) build-bundle --overwrite --config=src/android/BundleConfig.json --modules=out/aab/base.zip --output=$@
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-armv7a/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86_64/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym
@cd out/aab/staging; zip -u ../../../$@ BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym; cd ../../../
@$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) $@ @$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) $@
aab: out/TildeFriends.aab ## Build an Android App Bundle. aab: out/TildeFriends.aab ## Build an Android App Bundle.
@ -1165,13 +1153,7 @@ out/zsign_build/zsign: $(wildcard deps/zsign/*.cpp deps/zsign/*.h deps/zsign/*.t
@cmake -B out/zsign_build deps/zsign @cmake -B out/zsign_build deps/zsign
@cmake --build out/zsign_build -- COLOR=0 VERBOSE=0 MAKESILENT=-s @cmake --build out/zsign_build -- COLOR=0 VERBOSE=0 MAKESILENT=-s
ifeq ($(HAVE_LINUX_IOS),1) out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/data.zip $(if $(HAVE_LINUX_IOS),out/zsign_build/zsign)
ZSIGN_DEP = out/zsign_build/zsign
else
ZSIGN_DEP =
endif
out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/data.zip $(ZSIGN_DEP)
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
@cp -v $(filter-out out/zsign%,$<) $@ @cp -v $(filter-out out/zsign%,$<) $@
@cp -v out/data.zip $(@D)/ @cp -v out/data.zip $(@D)/
@ -1437,7 +1419,7 @@ dist-ios: iosrelease-app
mkdir -p out/Payload/tildefriends.app mkdir -p out/Payload/tildefriends.app
cp -avR out/tildefriends-iosrelease.app/* out/Payload/tildefriends.app/ cp -avR out/tildefriends-iosrelease.app/* out/Payload/tildefriends.app/
cp src/ios/tildefriends.png out/Payload/tildefriends.app/ cp src/ios/tildefriends.png out/Payload/tildefriends.app/
xcrun -sdk iphoneos actool --compile out/Payload/tildefriends.app/ --platform iphoneos --minimum-deployment-target $(IPHONEOS_VERSION_MIN) --app-icon AppIcon src/ios/icons/Assets.xcassets src/ios/icons/*.png --output-partial-info-plist out/actool.plist cp src/ios/icons/Assets.car out/Payload/tildefriends.app/
cp src/ios/distribution.mobileprovision out/Payload/tildefriends.app/embedded.mobileprovision cp src/ios/distribution.mobileprovision out/Payload/tildefriends.app/embedded.mobileprovision
xcrun -sdk iphoneos codesign -f -s 'Apple Distribution' --entitlements src/ios/Entitlements.plist --generate-entitlement-der out/Payload/tildefriends.app xcrun -sdk iphoneos codesign -f -s 'Apple Distribution' --entitlements src/ios/Entitlements.plist --generate-entitlement-der out/Payload/tildefriends.app
cd out; zip -r tildefriends.ipa Payload; cd .. cd out; zip -r tildefriends.ipa Payload; cd ..

View File

@ -38,7 +38,7 @@ dependencies in the right places.
### Requirements ### Requirements
System OpenSSL libraries are assumed to be available on Haiku and OpenBSD. System OpenSSL libraries are assumed to be available on Haiku and OpenSSL.
On MacOS, Xcode's command-line tools are expected to be available. On MacOS, Xcode's command-line tools are expected to be available.
@ -55,8 +55,9 @@ standard.
## Running ## Running
By default, running the built `out/debug/tildefriends` executable will start a By default, running the built `out/debug/tildefriends` executable will start a
web server at <http://localhost:12345/>. `tildefriends -h` lists further web server at <http://localhost:12345/>. It expects to be run with the
options. repository root as the current working directory. `tildefriends -h` lists
further options.
The first user to create an account and log in will be granted administrative The first user to create an account and log in will be granted administrative
privileges. Further administration can be done in the `admin` app at privileges. Further administration can be done in the `admin` app at

View File

@ -1,4 +1,4 @@
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */ /* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* 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} html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@ -108,8 +108,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.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-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-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-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size: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-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-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@ -150,7 +148,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-button:hover{color:#000!important;background-color:#ccc!important} .w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} .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-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
@ -178,19 +175,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!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-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-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!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-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-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}

View File

@ -1,4 +1,4 @@
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */ /* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* 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} html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@ -108,8 +108,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.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-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-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-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size: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-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-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@ -150,7 +148,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-button:hover{color:#000!important;background-color:#ccc!important} .w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} .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-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
@ -178,19 +175,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!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-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-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!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-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-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */ /* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* 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} html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@ -108,8 +108,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.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-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-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-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size: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-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-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@ -150,7 +148,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-button:hover{color:#000!important;background-color:#ccc!important} .w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} .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-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
@ -178,19 +175,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!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-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-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!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-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-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}

View File

@ -1,5 +0,0 @@
{
"type": "tildefriends-app",
"emoji": "💡",
"previous": "&ckE7T/dt9f1xV8jNSgXVcXYkAzMhU9689MRQbhOi9Wo=.sha256"
}

View File

@ -1,13 +0,0 @@
import * as tfrpc from '/tfrpc.js';
async function main() {
await app.setDocument(utf8Decode(getFile('index.html')));
}
tfrpc.register(async function complete() {
if ((await core.globalSettingsGet('index')) == '/~core/intro/') {
return await core.globalSettingsSet('index', '/~core/ssb/');
}
});
main();

View File

@ -1,286 +0,0 @@
<!doctype html>
<html style="height: 100%; margin: 0; padding: 0; box-sizing: border-box">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css" href="w3.css" />
<style>
.slide {
display: none;
margin-left: auto;
margin-right: auto;
}
.dot {
width: 1em;
height: 1em;
cursor: pointer;
}
.w3-left,
.w3-right {
cursor: pointer;
}
</style>
</head>
<body
style="
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
margin: 0;
padding: 0;
flex-direction: column;
align-items: center;
"
class="w3-flex w3-dark-gray w3-center"
>
<div
style="
flex: 1 1 auto;
overflow: auto;
contain: content;
padding-top: 16px;
padding-bottom: 16px;
"
>
<div class="slide">
<div
class="w3-content w3-xlarge w3-card-4 w3-blue w3-panel w3-padding-32 w3-round-xlarge"
style="margin: 32px"
>
<div>
<div>Welcome to</div>
<div>~😎 Tilde Friends.</div>
</div>
<footer>
<button class="w3-button w3-yellow proceed">Next</button>
</footer>
</div>
</div>
<div class="slide w3-card-4 w3-gray" style="width: 90%">
<header class="w3-container w3-blue w3-xlarge">
<h1>This brief tutorial will introduce:</h1>
</header>
<ul class="w3-large w3-left-align">
<li><b>Secure Scuttlebutt</b>, a decentralized social network.</li>
<li>
<b>Tilde Friends</b>, the application platform that you are using
right now.
</li>
<li>
<b>How to get started</b> if you want to get gossiping right away.
</li>
</ul>
<footer class="w3-center w3-xlarge w3-padding">
<button class="w3-button w3-yellow proceed">Onward</button>
</footer>
</div>
<div class="slide w3-gray" style="width: 90%">
<div class="w3-card-4 w3-xlarge">
<header class="w3-container w3-blue">
<h1>💻Secure Scuttlebutt in a Nutshell🦀</h1>
</header>
<div class="w3-container w3-large w3-left-align">
<p>
Secure Scuttlebutt is a social network whose technical operation
attempts to mirrors human social interaction.
</p>
<ul>
<li>
You can create your own account and post to your own feed on
your own device. This is all <b>local</b> with no external
communication. This puts you fully in control of your own words
and actions.
</li>
<li>
Before you can interact with others, you need to
<b>connect over the network</b>, either directly to your friends
(i.e., peer-to-peer between your phones on coffee shop Wi-Fi) or
to 🚪<i>rooms</i> and 🍻<i>pubs</i> (hint: search the web for
<i>#ssbroom</i>).
</li>
<li>
Who you choose to <b>follow</b> determines what you see, with
most people choosing to see messages from friends and friends of
those friends. If you encounter content you'd rather not see,
<b>block</b> the offending account to improve the experience for
you and your followers.
</li>
<li>
Your feed is an <b>immutable</b> log of your activity. Post with
care, because like your words in real life, posts can't be taken
back.
</li>
</ul>
</div>
<footer class="w3-center w3-xlarge w3-padding">
<a
class="w3-button w3-light-gray"
href="https://scuttlebutt.nz/"
target="_blank"
>See scuttlebutt.nz</a
>
<button class="w3-button w3-yellow proceed">Got It</button>
</footer>
</div>
</div>
<div class="slide w3-gray" style="width: 90%">
<div class="w3-card-4 w3-xlarge">
<header class="w3-container w3-blue w3-center">
<h1>~😎 Let's Talk Tilde Friends ~😎</h1>
</header>
<div class="w3-container w3-large w3-left-align">
<p>
Tilde Friends is an application platform that is an application of
its own.
</p>
<ul>
<li>
This intro is a Tilde Friends app. You can click <b>edit</b> at
the top to look under the hood and make changes.
</li>
<li>
It is already possible to make and share new applications using
only Tilde Friends and Secure Scuttlebutt without having to set
up development environments, configure web servers, register
domain names, or pay for hosting services.
</li>
<li>
But it's also set up so that you can't just break an app that
everybody is using or do malicious things with personal content.
There are <b>protections</b> in place like an operating system.
The intent is also for it to be <b>safe</b> to run strange apps
without worrying about adverse effects.
</li>
<li>
But this is all a big 🚧work in progress🚧 and
<b>experiment</b>. Let's see where it takes us.
</li>
</ul>
</div>
<footer class="w3-center w3-xlarge w3-padding">
<button class="w3-button w3-yellow proceed">Okay</button>
</footer>
</div>
</div>
<div class="slide w3-gray" style="width: 90%">
<div class="w3-card-4 w3-xlarge">
<header class="w3-container w3-blue w3-center">
<h1>🦀Let's Get this Tilde Friends Party Started🎉</h1>
</header>
<div class="w3-container w3-large w3-left-align">
<p>The button below will take you to the Secure Scuttlebutt app.</p>
<ul>
<li>
Remember:
<ol>
<li>You are in charge. This is all on your device.</li>
<li>
Make network connections to exchange messages with others.
</li>
<li>
Follow more accounts to see more content, and block those
posting content you'd rather not see.
</li>
<li>
Be respectful, and consider the consequences of what you
post.
</li>
<li>
This is all under active development. Exercise patience, and
report issues encountered.
</li>
</ol>
</li>
<li>
To see this tutorial again later, select <b>apps</b> -&gt;
<b>Core Apps</b> -&gt; <b>intro</b>.
</li>
</ul>
</div>
<footer class="w3-center w3-xlarge w3-padding">
<button class="w3-button w3-yellow" id="complete">Let's Go!</button>
</footer>
</div>
</div>
</div>
<div
class="w3-text-white w3-xlarge w3-center w3-flex"
style="
width: 100%;
flex: 0 1;
flex-direction: row;
align-items: center;
gap: 8px;
"
>
<div class="w3-jumbo" id="left" style="flex: 1 0; cursor: pointer">
&#10094;
</div>
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
<div class="w3-jumbo" style="flex: 1 0; cursor: pointer" id="right">
&#10095;
</div>
</div>
<script type="module">
import * as tfrpc from '/static/tfrpc.js';
let index = 0;
function set(i) {
show(i - index);
}
function show(delta) {
let slides = [...document.getElementsByClassName('slide')];
let dots = [...document.getElementsByClassName('dot')];
index = (index + delta + slides.length) % slides.length;
for (let slide of slides) {
slide.style.display =
slides.indexOf(slide) == index ? 'block' : 'none';
}
for (let dot of dots) {
if (dots.indexOf(dot) == index) {
dot.classList.add('w3-white');
} else {
dot.classList.remove('w3-white');
}
}
document.getElementById('left').style.visibility =
index == 0 ? 'hidden' : 'visible';
document.getElementById('right').style.visibility =
index == slides.length - 1 ? 'hidden' : 'visible';
}
let dots = [...document.getElementsByClassName('dot')];
for (let dot of dots) {
dot.onclick = () => set(dots.indexOf(dot));
}
for (let button of document.getElementsByClassName('proceed')) {
button.onclick = () => show(1);
}
document.getElementById('left').onclick = () => show(-1);
document.getElementById('right').onclick = () => show(1);
document.getElementById('complete').onclick = function () {
console.log('completing');
tfrpc.rpc.complete().finally(function () {
console.log('completed');
let a = document.createElement('a');
a.href = '/~core/ssb/';
a.target = '_top';
document.body.appendChild(a);
a.click();
});
};
window.addEventListener('keyup', function (event) {
if (event.key == 'ArrowLeft') {
show(-1);
} else if (event.key == 'ArrowRight') {
show(1);
}
});
show(0);
</script>
</body>
</html>

View File

@ -1,251 +0,0 @@
/* W3.CSS 5.02 March 31 2025 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-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.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}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* 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-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!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}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🚪", "emoji": "🚪",
"previous": "&DJwkqNfYWtW9yBtJQMseEXm7l04Enpi+yAxZulLq9Vk=.sha256" "previous": "&HXCdDG8gGYXElTyEFbg85jqa6lDXNL2ENPIA9UoJNbI=.sha256"
} }

View File

@ -1,9 +1,7 @@
async function main() { async function main() {
print(core.url); let host = core.url.match(/.*\/\/(.*?)\//)[1];
let host = core.url.match(/.*?\/\/([^:/]*)/)[1]; let id = (await ssb.getServerIdentity()).substring(1);
let port = await ssb.port(); let room = `net:${host}:${ssb.port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
let id = (await ssb.getServerIdentity()).substring(1).split('.')[0];
let room = `net:${host}:${port}~shs:${id}:SSB+Room+PSK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
await app.setDocument(` await app.setDocument(`
<body style="color: #fff"> <body style="color: #fff">
<h1>Server</h1> <h1>Server</h1>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🦀", "emoji": "🦀",
"previous": "&i6mkfmh4sW8PpILznWL8VxDzI3MX6V5OJWDs47Qxias=.sha256" "previous": "&jAAzd36Nmpw0sRA1Dx9wLiIwGX+q//+S/Han+RLlEOw=.sha256"
} }

View File

@ -14,8 +14,23 @@ function get_emojis() {
}); });
} }
export async function picker(callback, anchor, author, recent) { 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 async function picker(callback, anchor, author) {
let json = await get_emojis(); let json = await get_emojis();
let recent = await get_recent(author);
let div = document.createElement('div'); let div = document.createElement('div');
div.id = 'emoji_picker'; div.id = 'emoji_picker';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,6 @@ class TfElement extends LitElement {
broadcasts: {type: Array}, broadcasts: {type: Array},
connections: {type: Array}, connections: {type: Array},
loading: {type: Boolean}, loading: {type: Boolean},
loading_about: {type: Number},
loaded: {type: Boolean}, loaded: {type: Boolean},
following: {type: Array}, following: {type: Array},
users: {type: Object}, users: {type: Object},
@ -22,7 +21,6 @@ class TfElement extends LitElement {
guest: {type: Boolean}, guest: {type: Boolean},
url: {type: String}, url: {type: String},
private_messages: {type: Array}, private_messages: {type: Array},
recent_reactions: {type: Array},
}; };
} }
@ -38,13 +36,11 @@ class TfElement extends LitElement {
this.following = []; this.following = [];
this.users = {}; this.users = {};
this.loaded = false; this.loaded = false;
this.loading_about = 0;
this.channels = []; this.channels = [];
this.channels_unread = {}; this.channels_unread = {};
this.channels_latest = {}; this.channels_latest = {};
this.loading_latest = 0; this.loading_latest = 0;
this.loading_latest_scheduled = 0; this.loading_latest_scheduled = 0;
this.recent_reactions = [];
tfrpc.rpc.getBroadcasts().then((b) => { tfrpc.rpc.getBroadcasts().then((b) => {
self.broadcasts = b || []; self.broadcasts = b || [];
}); });
@ -128,13 +124,7 @@ class TfElement extends LitElement {
} }
next_channel(delta) { next_channel(delta) {
let channel_names = [ let channel_names = ['', '@', '🔐', ...this.channels.map((x) => '#' + x)];
'',
'@',
'🔐',
'👍',
...this.channels.map((x) => '#' + x),
];
let index = channel_names.indexOf(this.hash.substring(1)); let index = channel_names.indexOf(this.hash.substring(1));
index = index != -1 ? index + delta : 0; index = index != -1 ? index + delta : 0;
tfrpc.rpc.setHash( tfrpc.rpc.setHash(
@ -159,9 +149,8 @@ class TfElement extends LitElement {
} }
async fetch_about(following, users) { async fetch_about(following, users) {
this.loading_about++;
let ids = Object.keys(following).sort(); let ids = Object.keys(following).sort();
const k_cache_version = 3; const k_cache_version = 1;
let cache = await tfrpc.rpc.databaseGet('about'); let cache = await tfrpc.rpc.databaseGet('about');
let original_cache = cache; let original_cache = cache;
cache = cache ? JSON.parse(cache) : {}; cache = cache ? JSON.parse(cache) : {};
@ -169,86 +158,81 @@ class TfElement extends LitElement {
cache = { cache = {
version: k_cache_version, version: k_cache_version,
about: {}, about: {},
last_row_id: 0,
}; };
} }
let max_row_id = (
let ids_out_of_date = ids.filter( await tfrpc.rpc.query(
(x) => `
(users[x]?.seq && !cache.about[x]?.seq) || SELECT MAX(rowid) AS max_row_id FROM messages
(users[x]?.seq && users[x]?.seq > cache.about[x].seq) `,
); []
)
)[0].max_row_id;
for (let id of Object.keys(cache.about)) { for (let id of Object.keys(cache.about)) {
if (ids.indexOf(id) == -1) { if (ids.indexOf(id) == -1) {
delete cache.about[id]; delete cache.about[id];
} else {
users[id] = Object.assign(cache.about[id], users[id] || {});
} }
} }
console.log( let abouts = await tfrpc.rpc.query(
'loading about for',
ids.length,
'accounts',
ids_out_of_date.length,
'out of date'
);
if (ids_out_of_date.length) {
try {
let rows = await tfrpc.rpc.query(
` `
SELECT all_abouts.author, json(json_group_object(all_abouts.key, all_abouts.value)) AS about
FROM (
SELECT SELECT
messages.author, messages.author, json(messages.content) AS content, messages.sequence
fields.key, FROM
RANK() OVER (PARTITION BY messages.author, fields.key ORDER BY messages.sequence DESC) AS rank, messages,
fields.value json_each(?1) AS following
FROM messages JOIN json_each(messages.content) AS fields
WHERE WHERE
messages.content ->> '$.type' = 'about' AND messages.author = following.value AND
messages.content ->> '$.about' = messages.author AND messages.rowid > ?3 AND
NOT fields.key IN ('about', 'type')) all_abouts messages.rowid <= ?4 AND
JOIN json_each(?) AS following ON all_abouts.author = following.value json_extract(messages.content, '$.type') = 'about'
WHERE rank = 1 UNION
GROUP BY all_abouts.author SELECT
messages.author, json(messages.content) AS content, messages.sequence
FROM
messages,
json_each(?2) AS following
WHERE
messages.author = following.value AND
messages.rowid <= ?4 AND
json_extract(messages.content, '$.type') = 'about'
ORDER BY messages.author, messages.sequence
`, `,
[JSON.stringify(ids_out_of_date)] [
JSON.stringify(ids.filter((id) => cache.about[id])),
JSON.stringify(ids.filter((id) => !cache.about[id])),
cache.last_row_id,
max_row_id,
]
); );
users = users || {}; for (let about of abouts) {
for (let row of rows) { let content = JSON.parse(about.content);
users[row.author] = Object.assign( if (content.about === about.author) {
users[row.author] || {}, delete content.type;
JSON.parse(row.about) delete content.about;
); cache.about[about.author] = Object.assign(
cache.about[row.author] = Object.assign( cache.about[about.author] || {},
{seq: users[row.author].seq}, content
JSON.parse(row.about)
); );
} }
} catch (e) {
console.log(e);
} }
} cache.last_row_id = max_row_id;
for (let id of ids_out_of_date) {
if (!cache.about[id]?.seq) {
cache.about[id] = Object.assign(cache.about[id] ?? {}, {
seq: users[id]?.seq ?? 0,
});
}
}
this.loading_about--;
let new_cache = JSON.stringify(cache); let new_cache = JSON.stringify(cache);
if (new_cache != original_cache) { if (new_cache !== original_cache) {
let start_time = new Date(); let start_time = new Date();
tfrpc.rpc.databaseSet('about', new_cache).then(function () { tfrpc.rpc.databaseSet('about', new_cache).then(function () {
console.log('saving about took', (new Date() - start_time) / 1000); console.log('saving about took', (new Date() - start_time) / 1000);
}); });
} }
users = users || {};
for (let id of Object.keys(cache.about)) {
users[id] = Object.assign(
{follow_depth: following[id]?.d},
users[id] || {},
cache.about[id]
);
}
return Object.assign({}, users); return Object.assign({}, users);
} }
@ -368,16 +352,6 @@ class TfElement extends LitElement {
messages.author != ?4 messages.author != ?4
GROUP by channel GROUP by channel
UNION UNION
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN messages_refs ON messages.id = messages_refs.message
JOIN json_each(?1) AS channels ON messages_refs.ref = '#' || channels.value
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'post' AND
messages.content ->> 'root' IS NULL AND
messages.author != ?4
GROUP by channel
UNION
SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN json_each(?2) AS following ON messages.author = following.value JOIN json_each(?2) AS following ON messages.author = following.value
WHERE WHERE
@ -389,12 +363,6 @@ class TfElement extends LitElement {
JOIN messages ON messages.rowid = messages_fts.rowid JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?2) AS following ON messages.author = following.value JOIN json_each(?2) AS following ON messages.author = following.value
WHERE messages.author != ?4 WHERE messages.author != ?4
UNION
SELECT '👍' AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'vote' AND
messages.author != ?4
`, `,
[ [
JSON.stringify(this.channels), JSON.stringify(this.channels),
@ -403,15 +371,9 @@ class TfElement extends LitElement {
this.whoami, this.whoami,
] ]
); );
let latest = {}; this.channels_latest = Object.fromEntries(
for (let row of channels) { channels.map((x) => [x.channel, x.rowid])
if (!latest[row.channel]) { );
latest[row.channel] = row.rowid;
} else {
latest[row.channel] = Math.max(row.rowid, latest[row.channel]);
}
}
this.channels_latest = latest;
console.log('channels took', (new Date() - start_time) / 1000.0); console.log('channels took', (new Date() - start_time) / 1000.0);
let self = this; let self = this;
start_time = new Date(); start_time = new Date();
@ -420,6 +382,7 @@ class TfElement extends LitElement {
'🔐': latest[0], '🔐': latest[0],
}); });
console.log('private took', (new Date() - start_time) / 1000.0); console.log('private took', (new Date() - start_time) / 1000.0);
console.log(latest);
self.private_messages = latest[1]; self.private_messages = latest[1];
}); });
} }
@ -449,58 +412,43 @@ class TfElement extends LitElement {
[JSON.stringify(Object.keys(users))] [JSON.stringify(Object.keys(users))]
); );
for (let row of info) { for (let row of info) {
users[row.author] = Object.assign(users[row.author], { users[row.author].seq = row.max_seq;
seq: row.max_sequence, users[row.author].ts = row.max_ts;
ts: row.max_ts,
});
} }
return users; return users;
} }
async load_recent_reactions() {
this.recent_reactions = (
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
`,
[this.whoami]
)
).map((x) => x.value);
}
async load() { async load() {
this.loading_latest = true; this.loading_latest = true;
try { try {
let start_time = new Date(); let start_time = new Date();
let whoami = this.whoami; let whoami = this.whoami;
let following = await tfrpc.rpc.following([whoami], 2); let following = await tfrpc.rpc.following([whoami], 2);
let old_users = this.users ?? {};
let users = {}; let users = {};
let by_count = []; let by_count = [];
for (let [id, v] of Object.entries(following)) { for (let [id, v] of Object.entries(following)) {
users[id] = Object.assign( users[id] = {
{
following: v.of, following: v.of,
blocking: v.ob, blocking: v.ob,
followed: v.if, followed: v.if,
blocked: v.ib, blocked: v.ib,
follow_depth: following[id]?.d, };
},
old_users[id]
);
by_count.push({count: v.of, id: id}); by_count.push({count: v.of, id: id});
} }
let reactions = this.load_recent_reactions();
this.load_channels_latest(Object.keys(following)); this.load_channels_latest(Object.keys(following));
this.channels_unread = JSON.parse( this.channels_unread = JSON.parse(
(await tfrpc.rpc.databaseGet('unread')) ?? '{}' (await tfrpc.rpc.databaseGet('unread')) ?? '{}'
); );
this.following = Object.keys(following); this.following = Object.keys(following);
let about_start_time = new Date(); let about_start_time = new Date();
users = await this.fetch_about(following, users);
console.log(
'about took',
(new Date() - about_start_time) / 1000.0,
'seconds for',
Object.keys(users).length,
'users'
);
start_time = new Date(); start_time = new Date();
users = await this.fetch_user_info(users); users = await this.fetch_user_info(users);
console.log( console.log(
@ -509,22 +457,9 @@ class TfElement extends LitElement {
'seconds' 'seconds'
); );
this.users = users; this.users = users;
let self = this;
this.fetch_about(following, users).then(function (result) {
self.users = result;
console.log(
'about took',
(new Date() - about_start_time) / 1000.0,
'seconds for',
Object.keys(users).length,
'users'
);
});
console.log( console.log(
`load finished ${whoami} => ${this.whoami} in ${(new Date() - start_time) / 1000}` `load finished ${whoami} => ${this.whoami} in ${(new Date() - start_time) / 1000}`
); );
await reactions;
this.whoami = whoami; this.whoami = whoami;
this.loaded = whoami; this.loaded = whoami;
} finally { } finally {
@ -575,14 +510,13 @@ class TfElement extends LitElement {
whoami=${this.whoami} whoami=${this.whoami}
.users=${this.users} .users=${this.users}
hash=${this.hash} hash=${this.hash}
?loading=${this.loading || this.loading_about != 0} ?loading=${this.loading}
.channels=${this.channels} .channels=${this.channels}
.channels_latest=${this.channels_latest} .channels_latest=${this.channels_latest}
.channels_unread=${this.channels_unread} .channels_unread=${this.channels_unread}
@channelsetunread=${this.channel_set_unread} @channelsetunread=${this.channel_set_unread}
.connections=${this.connections} .connections=${this.connections}
.private_messages=${this.private_messages} .private_messages=${this.private_messages}
.recent_reactions=${this.recent_reactions}
></tf-tab-news> ></tf-tab-news>
`; `;
} else if (this.tab === 'connections') { } else if (this.tab === 'connections') {

View File

@ -255,12 +255,10 @@ class TfComposeElement extends LitElement {
let self = this; let self = this;
let input = document.createElement('input'); let input = document.createElement('input');
input.type = 'file'; input.type = 'file';
input.addEventListener('change', function (event) { input.onchange = function (event) {
input.parentNode.removeChild(input);
let file = event.target.files[0]; let file = event.target.files[0];
self.add_file(file); self.add_file(file);
}); };
document.body.appendChild(input);
input.click(); input.click();
} }
@ -359,7 +357,7 @@ class TfComposeElement extends LitElement {
remove_mention(id) { remove_mention(id) {
let draft = this.get_draft(); let draft = this.get_draft();
delete draft.mentions[id]; delete draft.mentions[id];
setTimeout(() => this.notify(draft), 0); setTimeout(() => this.notify(), 0);
} }
render_mention(mention) { render_mention(mention) {
@ -446,15 +444,12 @@ class TfComposeElement extends LitElement {
self.apps = await tfrpc.rpc.apps(); self.apps = await tfrpc.rpc.apps();
} }
if (!this.apps) { if (!this.apps) {
return html`<button return html`<button class="w3-button w3-theme-d1" @click=${attach_app}>
class="w3-button w3-bar-item w3-theme-d1"
@click=${attach_app}
>
Attach App Attach App
</button>`; </button>`;
} else { } else {
return html`<button return html`<button
class="w3-button w3-bar-item w3-theme-d1" class="w3-button w3-theme-d1"
@click=${() => (this.apps = null)} @click=${() => (this.apps = null)}
> >
Discard App Discard App
@ -475,9 +470,18 @@ class TfComposeElement extends LitElement {
if (draft.content_warning !== undefined) { if (draft.content_warning !== undefined) {
return html` return html`
<div class="w3-container w3-padding"> <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>
<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=${self.input} value=${draft.content_warning}></input> <input type="text" class="w3-input w3-border w3-theme-d1" id="content_warning" placeholder="Enter a content warning here." @input=${self.input} value=${draft.content_warning}></input>
</div> </div>
`; `;
} else {
return html`
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning('')}></input>
<label for="cw">CW</label>
`;
} }
} }
@ -518,7 +522,7 @@ class TfComposeElement extends LitElement {
return html` return html`
<div style="display: flex; flex-direction: row; width: 100%"> <div style="display: flex; flex-direction: row; width: 100%">
<label for="encrypt_to">🔐 To:</label> <label for="encrypt_to">🔐 To:</label>
<input type="text" id="encrypt_to" class="w3-input w3-theme-d1 w3-border" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input> <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-theme-d1" @click=${() => this.set_encrypt(undefined)}>🚮</button>
</div> </div>
<ul> <ul>
@ -540,31 +544,6 @@ class TfComposeElement extends LitElement {
this.requestUpdate(); this.requestUpdate();
} }
toggle_menu(event) {
event.srcElement.parentNode
.querySelector('.w3-dropdown-content')
.classList.toggle('w3-show');
}
connectedCallback() {
super.connectedCallback();
this._click_callback = this.document_click.bind(this);
document.body.addEventListener('mouseup', this._click_callback);
}
disconnectedCallback() {
super.disconnectedCallback();
document.body.removeEventListener('mouseup', this._click_callback);
}
document_click(event) {
let content = this.renderRoot.querySelector('.w3-dropdown-content');
let target = event.target;
if (content && !content.contains(target)) {
content.classList.remove('w3-show');
}
}
render() { render() {
let self = this; let self = this;
let draft = self.get_draft(); let draft = self.get_draft();
@ -578,7 +557,7 @@ class TfComposeElement extends LitElement {
draft.encrypt_to !== undefined draft.encrypt_to !== undefined
? undefined ? undefined
: html`<button : html`<button
class="w3-button w3-bar-item w3-theme-d1" class="w3-button w3-theme-d1"
@click=${() => this.set_encrypt([])} @click=${() => this.set_encrypt([])}
> >
🔐 🔐
@ -633,43 +612,13 @@ class TfComposeElement extends LitElement {
> >
Submit Submit
</button> </button>
<div class="w3-dropdown-click"> <button class="w3-button w3-theme-d1" @click=${this.attach}>
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
</button>
<div class="w3-dropdown-content w3-bar-block">
${this.get_draft().content_warning === undefined
? html`
<button
class="w3-button w3-bar-item w3-theme-d1"
@click=${() => self.set_content_warning('')}
>
Add Content Warning
</button>
`
: html`
<button
class="w3-button w3-bar-item w3-theme-d1"
@click=${() => self.set_content_warning(undefined)}
>
Remove Content Warning
</button>
`}
<button
class="w3-button w3-bar-item w3-theme-d1"
@click=${this.attach}
>
Attach Attach
</button> </button>
${this.render_attach_app_button()} ${encrypt} ${this.render_attach_app_button()} ${encrypt}
<button <button class="w3-button w3-theme-d1" @click=${this.discard}>
class="w3-button w3-bar-item w3-theme-d1"
@click=${this.discard}
>
Discard Discard
</button> </button>
</div>
</div>
</footer> </footer>
</div> </div>
`; `;

View File

@ -1,11 +1,4 @@
import { import {LitElement, html, repeat, render, unsafeHTML} from './lit-all.min.js';
LitElement,
css,
html,
repeat,
render,
unsafeHTML,
} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js'; import * as tfrpc from '/static/tfrpc.js';
import * as tfutils from './tf-utils.js'; import * as tfutils from './tf-utils.js';
import * as emojis from './emojis.js'; import * as emojis from './emojis.js';
@ -23,7 +16,6 @@ class TfMessageElement extends LitElement {
expanded: {type: Object}, expanded: {type: Object},
channel: {type: String}, channel: {type: String},
channel_unread: {type: Number}, channel_unread: {type: Number},
recent_reactions: {type: Array},
}; };
} }
@ -39,26 +31,6 @@ class TfMessageElement extends LitElement {
this.format = 'message'; this.format = 'message';
this.expanded = {}; this.expanded = {};
this.channel_unread = -1; this.channel_unread = -1;
this.recent_reactions = [];
}
connectedCallback() {
super.connectedCallback();
this._click_callback = this.document_click.bind(this);
document.body.addEventListener('mouseup', this._click_callback);
}
disconnectedCallback() {
super.disconnectedCallback();
document.body.removeEventListener('mouseup', this._click_callback);
}
document_click(event) {
let content = this.renderRoot.querySelector('.w3-dropdown-content');
let target = event.target;
if (content && !content.contains(target)) {
content.classList.remove('w3-show');
}
} }
show_reply() { show_reply() {
@ -93,27 +65,20 @@ class TfMessageElement extends LitElement {
render_votes() { render_votes() {
function normalize_expression(expression) { function normalize_expression(expression) {
if ( if (expression === 'Like' || !expression) {
expression === 'Unlike' || return '👍';
expression === 'unlike' || } else if (expression === 'Unlike') {
expression == 'undig'
) {
return '👎'; return '👎';
} else if (expression === 'heart') { } else if (expression === 'heart') {
return '❤️'; return '❤️';
} else if (
(expression ?? '').split('').every((x) => x.charCodeAt(0) < 256)
) {
return '👍';
} else { } else {
return expression; return expression;
} }
} }
if (this.message?.votes?.length) { if (this.message?.votes?.length) {
return html` <footer class="w3-container"> return html` <div class="w3-container">
<div <div
class="w3-button w3-bar" class="w3-button w3-bar w3-padding-small"
style="padding: 0"
@click=${this.show_reactions} @click=${this.show_reactions}
> >
${(this.message.votes || []).map( ${(this.message.votes || []).map(
@ -128,7 +93,7 @@ class TfMessageElement extends LitElement {
` `
)} )}
</div> </div>
</footer>`; </div>`;
} }
} }
@ -171,12 +136,7 @@ class TfMessageElement extends LitElement {
} }
react(event) { react(event) {
emojis.picker( emojis.picker((x) => this.vote(x), null, this.whoami);
(x) => this.vote(x),
null,
this.whoami,
this.recent_reactions
);
} }
show_image(link) { show_image(link) {
@ -310,47 +270,40 @@ class TfMessageElement extends LitElement {
return total; return total;
} }
expanded_key() {
return this.message?.id || this.messages?.map((x) => x.id).join(':');
}
set_expanded(expanded, tag) { set_expanded(expanded, tag) {
let key = this.expanded_key();
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('tf-expand', { new CustomEvent('tf-expand', {
bubbles: true, bubbles: true,
composed: true, composed: true,
detail: {id: key + (tag || ''), expanded: expanded}, detail: {id: (this.message.id || '') + (tag || ''), expanded: expanded},
}) })
); );
} }
toggle_expanded(tag) { toggle_expanded(tag) {
let key = this.expanded_key(); this.set_expanded(
this.set_expanded(!this.expanded[key + (tag || '')], tag); !this.expanded[(this.message.id || '') + (tag || '')],
} tag
);
is_expanded(tag) {
let key = this.expanded_key();
return this.expanded[key + (tag || '')];
} }
render_children() { render_children() {
let self = this; let self = this;
if (this.message.child_messages?.length) { if (this.message.child_messages?.length) {
if (!this.expanded[this.expanded_key()]) { if (!this.expanded[this.message.id]) {
return html` return html`<button
<button class="w3-button w3-theme-d1"
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(true)} @click=${() => self.set_expanded(true)}
> >
+ ${this.total_child_messages(this.message) + ' More'} + ${this.total_child_messages(this.message) + ' More'}
</button> </button>`;
`;
} else { } else {
return html` <div class="w3-container w3-margin-bottom"> return html`<button
${repeat( class="w3-button w3-theme-d1"
@click=${() => self.set_expanded(false)}
>
Collapse</button
>${repeat(
this.message.child_messages || [], this.message.child_messages || [],
(x) => x.id, (x) => x.id,
(x) => (x) =>
@ -362,17 +315,8 @@ class TfMessageElement extends LitElement {
.expanded=${this.expanded} .expanded=${this.expanded}
channel=${this.channel} channel=${this.channel}
channel_unread=${this.channel_unread} channel_unread=${this.channel_unread}
.recent_reactions=${this.recent_reactions}
></tf-message>` ></tf-message>`
)} )}`;
</div>
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(false)}
>
Collapse
</button>`;
} }
} else { } else {
return undefined; return undefined;
@ -414,7 +358,7 @@ class TfMessageElement extends LitElement {
class_background() { class_background() {
return this.message?.decrypted return this.message?.decrypted
? 'w3-pale-red' ? 'w3-pale-red'
: this.allow_unread() && this.message?.rowid >= this.channel_unread : this.message?.rowid >= this.channel_unread
? 'w3-theme-d2' ? 'w3-theme-d2'
: 'w3-theme-d4'; : 'w3-theme-d4';
} }
@ -427,74 +371,62 @@ class TfMessageElement extends LitElement {
return content; return content;
} }
copy_id(event) { render_raw_button() {
navigator.clipboard.writeText(this.message?.id);
}
toggle_menu(event) {
event.srcElement.parentNode
.querySelector('.w3-dropdown-content')
.classList.toggle('w3-show');
}
render_menu() {
let content = this.get_content(); let content = this.get_content();
let formats = [['message', 'Message']]; let raw_button;
switch (this.format) {
case 'raw':
if (content?.type == 'post' || content?.type == 'blog') { if (content?.type == 'post' || content?.type == 'blog') {
formats.push(['md', 'Markdown']); raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'md')}
>
Markdown
</button>`;
} else {
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'message')}
>
Message
</button>`;
} }
if (this.message?.decrypted) { break;
formats.push(['decrypted', 'Decrypted']); case 'md':
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'message')}
>
Message
</button>`;
break;
case 'decrypted':
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'raw')}
>
Raw
</button>`;
break;
default:
if (this.message.decrypted) {
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'decrypted')}
>
Decrypted
</button>`;
} else {
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'raw')}
>
Raw
</button>`;
} }
formats.push(['raw', 'Raw']); break;
return html` }
<div class="w3-bar-item w3-right"> return raw_button;
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
%
</button>
<div
class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
style="right: 48px"
>
<a
target="_top"
class="w3-button w3-bar-item"
href=${'#' + encodeURIComponent(this.message?.id)}
>View Message</a
>
<button
class="w3-button w3-bar-item w3-border-bottom"
@click=${this.copy_id}
>
Copy ID
</button>
${this.drafts[this.message?.id] === undefined
? html`
<button class="w3-button w3-bar-item" @click=${this.show_reply}>
Reply
</button>
`
: undefined}
<button
class="w3-button w3-bar-item w3-border-bottom"
@click=${this.react}
>
👍 React
</button>
${formats.map(
([format, name]) => html`
<button
class="w3-button w3-bar-item"
style=${format == this.format ? 'font-weight: bold' : ''}
@click=${() => (this.format = format)}
>
${name}
</button>
`
)}
</div>
</div>
`;
} }
render_header() { render_header() {
@ -506,15 +438,16 @@ class TfMessageElement extends LitElement {
return html` return html`
<header class="w3-bar"> <header class="w3-bar">
<span class="w3-bar-item"> <span class="w3-bar-item">
${this.render_unread_icon()}<tf-user <tf-user id=${this.message.author} .users=${this.users}></tf-user>
id=${this.message.author}
.users=${this.users}
></tf-user>
</span> </span>
${is_encrypted} ${this.render_menu()} ${is_encrypted}
<div class="w3-bar-item w3-right" style="text-wrap: nowrap"> <span class="w3-bar-item w3-right">${this.render_raw_button()}</span>
${new Date(this.message.timestamp).toLocaleString()} <span class="w3-bar-item w3-right" style="text-wrap: nowrap"
</div> ><a target="_top" href=${'#' + encodeURIComponent(this.message.id)}
>%</a
>
${new Date(this.message.timestamp).toLocaleString()}</span
>
</header> </header>
`; `;
} }
@ -562,7 +495,6 @@ class TfMessageElement extends LitElement {
.expanded=${self.expanded} .expanded=${self.expanded}
channel=${self.channel} channel=${self.channel}
channel_unread=${self.channel_unread} channel_unread=${self.channel_unread}
.recent_reactions=${self.recent_reactions}
></tf-message> ></tf-message>
` `
)} )}
@ -574,7 +506,6 @@ class TfMessageElement extends LitElement {
let reply = let reply =
this.drafts[this.message?.id] !== undefined this.drafts[this.message?.id] !== undefined
? html` ? html`
<div class="w3-section w3-container">
<tf-compose <tf-compose
whoami=${this.whoami} whoami=${this.whoami}
.users=${this.users} .users=${this.users}
@ -583,63 +514,22 @@ class TfMessageElement extends LitElement {
.drafts=${this.drafts} .drafts=${this.drafts}
@tf-discard=${this.discard_reply} @tf-discard=${this.discard_reply}
author=${this.message.author} author=${this.message.author}
.recent_reactions=${this.recent_reactions}
></tf-compose> ></tf-compose>
</div>
` `
: undefined; : html`
return html` <button class="w3-button w3-theme-d1" @click=${this.show_reply}>
${reply} Reply
<footer>${this.render_children()}</footer> </button>
`;
return html`
<div class="w3-section w3-container">
${reply}
<button class="w3-button w3-theme-d1" @click=${this.react}>
React
</button>
${this.render_children()}
</div>
`; `;
}
content_group_by_author() {
let sorted = this.message.messages
.map((x) => [
x.author,
x.content.blocking !== undefined
? x.content.blocking
? 'is blocking'
: 'is no longer blocking'
: x.content.following !== undefined
? x.content.following
? 'is following'
: 'is no longer following'
: '',
x.content.contact,
x,
])
.sort();
let result = [];
let last;
let group;
for (let row of sorted) {
if (last && last[0] == row[0] && last[1] == row[1]) {
group.push(row[2]);
} else {
if (group) {
result.push({author: last[0], action: last[1], users: group});
}
last = row;
group = [row[2]];
}
}
if (group) {
result.push({author: last[0], action: last[1], users: group});
}
return result;
}
allow_unread() {
return;
!this.channel.startsWith('@') && !this.channel.startsWith('%');
}
render_unread_icon() {
return this.allow_unread() && this.message?.rowid >= this.channel_unread
? html`✉️`
: undefined;
} }
render() { render() {
@ -650,10 +540,8 @@ class TfMessageElement extends LitElement {
let class_background = this.class_background(); let class_background = this.class_background();
let self = this; let self = this;
if (this.message?.type === 'contact_group') { if (this.message?.type === 'contact_group') {
if (this.expanded[this.expanded_key()]) { return this.render_frame(
return this.render_frame(html` html` ${this.message.messages.map(
<div class="w3-padding">
${this.message.messages.map(
(x) => (x) =>
html`<tf-message html`<tf-message
.message=${x} .message=${x}
@ -664,76 +552,17 @@ class TfMessageElement extends LitElement {
channel=${this.channel} channel=${this.channel}
channel_unread=${this.channel_unread} channel_unread=${this.channel_unread}
></tf-message>` ></tf-message>`
)} )}`
</div> );
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(false)}
>
Collapse
</button>
`);
} else {
return this.render_frame(html`
<div class="w3-padding">
${this.content_group_by_author().map(
(x) => html`
<div>
<tf-user id=${x.author} .users=${this.users}></tf-user>
${x.action}
${x.users.map(
(y) => html`
<tf-user id=${y} .users=${this.users}></tf-user>
`
)}
</div>
`
)}
</div>
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(true)}
>
Expand
</button>
`);
}
} else if (this.message.placeholder) { } else if (this.message.placeholder) {
return this.render_frame( return this.render_frame(
html`<div> html`<div class="w3-padding">
<div class="w3-bar"> <p>
<a <a target="_top" href=${'#' + encodeURIComponent(this.message.id)}
class="w3-bar-item w3-panel w3-round-xlarge w3-theme-d1 w3-margin w3-button" >${this.message.id}</a
target="_top"
href=${'#' + encodeURIComponent(this.message?.id)}
> >
This message is not currently available. (placeholder)
</a> </p>
<div class="w3-bar-item w3-right">
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
%
</button>
<div
class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
style="right: 48px"
>
<a
target="_top"
class="w3-button w3-bar-item"
href=${'#' + encodeURIComponent(this.message?.id)}
>View Message</a
>
<button
class="w3-button w3-bar-item w3-border-bottom"
@click=${this.copy_id}
>
Copy ID
</button>
</div>
</div>
</div>
<div>${this.render_votes()}</div> <div>${this.render_votes()}</div>
${(this.message.child_messages || []).map( ${(this.message.child_messages || []).map(
(x) => html` (x) => html`
@ -760,7 +589,7 @@ class TfMessageElement extends LitElement {
} }
if (content.image !== undefined) { if (content.image !== undefined) {
image = html` image = html`
<div @click=${this.body_click}><img src=${'/' + (typeof content.image?.link == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div> <div><img src=${'/' + (typeof content.image?.link == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div>
`; `;
} }
if (content.description !== undefined) { if (content.description !== undefined) {
@ -783,9 +612,8 @@ class TfMessageElement extends LitElement {
</div> </div>
`); `);
} else if (content.type == 'contact') { } else if (content.type == 'contact') {
return this.render_frame(html` return html`
<div class="w3-bar"> <div class="w3-padding">
<div class="w3-bar-item">
<tf-user id=${this.message.author} .users=${this.users}></tf-user> <tf-user id=${this.message.author} .users=${this.users}></tf-user>
is is
${content.blocking === true ${content.blocking === true
@ -802,41 +630,7 @@ class TfMessageElement extends LitElement {
.users=${this.users} .users=${this.users}
></tf-user> ></tf-user>
</div> </div>
<div class="w3-bar-item w3-right"> `;
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
%
</button>
<div
class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
style="right: 48px"
>
<a
target="_top"
class="w3-button w3-bar-item"
href=${'#' + encodeURIComponent(this.message?.id)}
>View Message</a
>
<button
class="w3-button w3-bar-item w3-border-bottom"
@click=${this.copy_id}
>
Copy ID
</button>
${this.drafts[this.message?.id] === undefined
? html`
<button
class="w3-button w3-bar-item"
@click=${this.show_reply}
>
Reply
</button>
`
: undefined}
</div>
</div>
${this.render_votes()} ${this.render_actions()}
</div>
`);
} else if (content.type == 'post') { } else if (content.type == 'post') {
let self = this; let self = this;
let body; let body;
@ -859,14 +653,11 @@ class TfMessageElement extends LitElement {
} }
let content_warning = html` let content_warning = html`
<div <div
class="w3-panel w3-round-xlarge w3-theme-l4 w3" class="w3-panel w3-round-xlarge w3-theme-l4"
style="cursor: pointer" style="cursor: pointer"
@click=${(x) => this.toggle_expanded(':cw')} @click=${(x) => this.toggle_expanded(':cw')}
> >
<p>${content.contentWarning}</p> <p>${content.contentWarning}</p>
<p class="w3-small">
${this.is_expanded(':cw') ? 'Show less' : 'Show more'}
</p>
</div> </div>
`; `;
let content_html = html` let content_html = html`

View File

@ -13,7 +13,6 @@ class TfNewsElement extends LitElement {
expanded: {type: Object}, expanded: {type: Object},
channel: {type: String}, channel: {type: String},
channel_unread: {type: Number}, channel_unread: {type: Number},
recent_reactions: {type: Array},
}; };
} }
@ -29,7 +28,6 @@ class TfNewsElement extends LitElement {
this.drafts = {}; this.drafts = {};
this.expanded = {}; this.expanded = {};
this.channel_unread = -1; this.channel_unread = -1;
this.recent_reactions = [];
} }
process_messages(messages) { process_messages(messages) {
@ -166,10 +164,7 @@ class TfNewsElement extends LitElement {
if (message?.content?.type === 'contact') { if (message?.content?.type === 'contact') {
group.push(message); group.push(message);
} else { } else {
if (group.length == 1) { if (group.length > 0) {
result.push(group[0]);
group = [];
} else if (group.length > 1) {
result.push({ result.push({
rowid: Math.max(...group.map((x) => x.rowid)), rowid: Math.max(...group.map((x) => x.rowid)),
type: 'contact_group', type: 'contact_group',
@ -180,10 +175,7 @@ class TfNewsElement extends LitElement {
result.push(message); result.push(message);
} }
} }
if (group.length == 1) { if (group.length > 0) {
result.push(group[0]);
group = [];
} else if (group.length > 1) {
result.push({ result.push({
rowid: Math.max(...group.map((x) => x.rowid)), rowid: Math.max(...group.map((x) => x.rowid)),
type: 'contact_group', type: 'contact_group',
@ -219,7 +211,6 @@ class TfNewsElement extends LitElement {
collapsed="true" collapsed="true"
channel=${this.channel} channel=${this.channel}
channel_unread=${this.channel_unread} channel_unread=${this.channel_unread}
.recent_reactions=${this.recent_reactions}
></tf-message> ></tf-message>
${x.rowid == unread_rowid ${x.rowid == unread_rowid
? html`<div style="display: flex; flex-direction: row"> ? html`<div style="display: flex; flex-direction: row">

View File

@ -1,4 +1,4 @@
import {LitElement, html, until, unsafeHTML} from './lit-all.min.js'; import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js'; import * as tfrpc from '/static/tfrpc.js';
import * as tfutils from './tf-utils.js'; import * as tfutils from './tf-utils.js';
import {styles} from './tf-styles.js'; import {styles} from './tf-styles.js';
@ -11,7 +11,6 @@ class TfProfileElement extends LitElement {
id: {type: String}, id: {type: String},
users: {type: Object}, users: {type: Object},
size: {type: Number}, size: {type: Number},
sequence: {type: Number},
following: {type: Boolean}, following: {type: Boolean},
blocking: {type: Boolean}, blocking: {type: Boolean},
}; };
@ -27,7 +26,6 @@ class TfProfileElement extends LitElement {
this.id = null; this.id = null;
this.users = {}; this.users = {};
this.size = 0; this.size = 0;
this.sequence = 0;
} }
async load() { async load() {
@ -141,8 +139,7 @@ class TfProfileElement extends LitElement {
let self = this; let self = this;
let input = document.createElement('input'); let input = document.createElement('input');
input.type = 'file'; input.type = 'file';
input.addEventListener('change', function (event) { input.onchange = function (event) {
input.parentNode.removeChild(input);
let file = event.target.files[0]; let file = event.target.files[0];
file file
.arrayBuffer() .arrayBuffer()
@ -157,8 +154,7 @@ class TfProfileElement extends LitElement {
.catch(function (e) { .catch(function (e) {
alert(e.message); alert(e.message);
}); });
}); };
document.body.appendChild(input);
input.click(); input.click();
} }
@ -166,86 +162,17 @@ class TfProfileElement extends LitElement {
navigator.clipboard.writeText(this.id); navigator.clipboard.writeText(this.id);
} }
show_image(link) {
let div = document.createElement('div');
div.style.left = 0;
div.style.top = 0;
div.style.width = '100%';
div.style.height = '100%';
div.style.position = 'fixed';
div.style.background = '#000';
div.style.zIndex = 100;
div.style.display = 'grid';
let img = document.createElement('img');
img.src = link;
img.style.maxWidth = '100%';
img.style.maxHeight = '100%';
img.style.display = 'block';
img.style.margin = 'auto';
img.style.objectFit = 'contain';
img.style.width = '100%';
div.appendChild(img);
function image_close(event) {
document.body.removeChild(div);
window.removeEventListener('keydown', image_close);
}
div.onclick = image_close;
window.addEventListener('keydown', image_close);
document.body.appendChild(div);
}
body_click(event) {
if (event.srcElement.tagName == 'IMG') {
this.show_image(event.srcElement.src);
}
}
toggle_account_list(event) {
let content = event.srcElement.nextElementSibling;
if (content.classList.toggle('w3-hide')) {
event.srcElement.innerText = 'Show Followed Accounts';
} else {
event.srcElement.innerText = 'Hide Followed Accounts';
}
}
async load_follows() {
let accounts = await tfrpc.rpc.following([this.id], 1);
return html`
<div class="w3-container">
<button
class="w3-button w3-block w3-theme-d1"
@click=${this.toggle_account_list}
>
Show Followed Accounts
</button>
<div class="w3-hide w3-card">
<ul class="w3-ul w3-theme-d4 w3-border-theme">
${Object.keys(accounts).map(
(x) => html`
<li class="w3-border-theme">
<tf-user id=${x} .users=${this.users}></tf-user>
</li>
`
)}
</ul>
</div>
</div>
`;
}
render() { render() {
this.load(); this.load();
let self = this; let self = this;
let profile = this.users[this.id] || {}; let profile = this.users[this.id] || {};
tfrpc.rpc tfrpc.rpc
.query( .query(
`SELECT SUM(LENGTH(content)) AS size, MAX(sequence) AS sequence FROM messages WHERE author = ?`, `SELECT SUM(LENGTH(content)) AS size FROM messages WHERE author = ?`,
[this.id] [this.id]
) )
.then(function (result) { .then(function (result) {
self.size = result[0].size; self.size = result[0].size;
self.sequence = result[0].sequence;
}); });
let edit; let edit;
let follow; let follow;
@ -310,19 +237,15 @@ class TfProfileElement extends LitElement {
</div> </div>
</div>` </div>`
: null; : null;
let image = profile.image; let image =
if (typeof image == 'string' && !image.startsWith('&')) { typeof profile.image == 'string' ? profile.image : profile.image?.link;
try {
image = JSON.parse(image)?.link;
} catch {}
}
image = this.editing?.image ?? image; image = this.editing?.image ?? image;
let description = this.editing?.description ?? profile.description; let description = this.editing?.description ?? profile.description;
return html`<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box"> return html`<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box">
<header class="w3-container"> <header class="w3-container">
<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)} in ${this.sequence} messages)</p> <p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)})</p>
</header> </header>
<div class="w3-container" @click=${this.body_click}> <div class="w3-container">
<div class="w3-margin-bottom" style="display: flex; flex-direction: row"> <div class="w3-margin-bottom" style="display: flex; flex-direction: row">
<input type="text" class="w3-input w3-border w3-theme-d1" style="display: flex 1 1" readonly value=${this.id}></input> <input type="text" class="w3-input w3-border w3-theme-d1" style="display: flex 1 1" readonly value=${this.id}></input>
<button class="w3-button w3-theme-d1 w3-ripple" style="flex: 0 0 auto" @click=${this.copy_id}>Copy</button> <button class="w3-button w3-theme-d1 w3-ripple" style="flex: 0 0 auto" @click=${this.copy_id}>Copy</button>
@ -348,7 +271,6 @@ class TfProfileElement extends LitElement {
Blocked by ${profile.blocked} identities. Blocked by ${profile.blocked} identities.
</div> </div>
</div> </div>
${until(this.load_follows(), html`<p>Loading accounts followed...</p>`)}
<footer class="w3-container"> <footer class="w3-container">
<p> <p>
${edit} ${edit}

View File

@ -41,21 +41,18 @@ class TfReactionsModalElement extends LitElement {
> >
</header> </header>
<ul class="w3-theme-dark w3-container w3-ul"> <ul class="w3-theme-dark w3-container w3-ul">
${this.votes ${this.votes.map(
.sort((x, y) => y.timestamp - x.timestamp)
.map(
(x) => html` (x) => html`
<li style="display: flex; flex-direction: row; gap: 4px"> <li class="w3-bar">
<span style="flex-basis: 3em" <span class="w3-bar-item"
>${x?.content?.vote?.expression}</span >${x?.content?.vote?.expression}</span
> >
<tf-user <tf-user
style="flex: 1 1" class="w3-bar-item"
id=${x.author} id=${x.author}
.users=${this.users} .users=${this.users}
></tf-user> ></tf-user>
<span <span class="w3-bar-item w3-right"
style="flex-shrink: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
>${new Date(x?.timestamp).toLocaleString()}</span >${new Date(x?.timestamp).toLocaleString()}</span
> >
</li> </li>

View File

@ -48,7 +48,7 @@ const tf = css`
// prettier-ignore // prettier-ignore
const w3 = css` const w3 = css`
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */ /* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* 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} html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@ -158,10 +158,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.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-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-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-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size: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-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-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@ -203,9 +199,9 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important} .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-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!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-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-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-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
@ -220,24 +216,15 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!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-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-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!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-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-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!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-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-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-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{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-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-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!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-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-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}

View File

@ -103,23 +103,6 @@ class TfTabConnectionsElement extends LitElement {
</div>`; </div>`;
} }
render_progress(name, value, max) {
if (max && value != max) {
return html`
<div class="w3-theme-d1 w3-small">
<div
class="w3-container w3-theme-l1"
style="width: ${Math.floor(
(100.0 * value) / max
)}%; text-wrap: nowrap"
>
${name} ${value} / ${max} (${Math.round((100.0 * value) / max)}%)
</div>
</div>
`;
}
}
render_broadcast(connection) { render_broadcast(connection) {
let self = this; let self = this;
return html` return html`
@ -171,16 +154,6 @@ class TfTabConnectionsElement extends LitElement {
: undefined} : undefined}
${connection.flags.one_shot ? '🔃' : undefined} ${connection.flags.one_shot ? '🔃' : undefined}
<tf-user id=${connection.id} .users=${this.users}></tf-user> <tf-user id=${connection.id} .users=${this.users}></tf-user>
${this.render_progress(
'recv',
connection.progress.in.total - connection.progress.in.current,
connection.progress.in.total
)}
${this.render_progress(
'send',
connection.progress.out.total - connection.progress.out.current,
connection.progress.out.total
)}
${connection.tunnel !== undefined ${connection.tunnel !== undefined
? '🚇' ? '🚇'
: html`(${connection.host}:${connection.port})`} : html`(${connection.host}:${connection.port})`}
@ -233,21 +206,6 @@ class TfTabConnectionsElement extends LitElement {
}); });
} }
toggle_accordian(id) {
let element = this.renderRoot.getElementById(id);
element.classList.toggle('w3-hide');
}
valid_connections() {
return this.connections.filter((x) => x.tunnel === undefined);
}
valid_broadcasts() {
return this.broadcasts
.filter((x) => x.address)
.filter((x) => this.connections.map((c) => c.id).indexOf(x.pubkey) == -1);
}
render() { render() {
let self = this; let self = this;
return html` return html`
@ -262,33 +220,27 @@ class TfTabConnectionsElement extends LitElement {
> >
Connect Connect
</button> </button>
<h2 <h2>Broadcasts</h2>
class="w3-button w3-block w3-theme-d1" <ul class="w3-ul w3-border">
@click=${() => self.toggle_accordian('connections')} ${this.broadcasts
> .filter((x) => x.address)
Connections (${this.valid_connections().length}) .filter(
</h2> (x) => self.connections.map((c) => c.id).indexOf(x.pubkey) == -1
<ul class="w3-ul w3-border" id="connections"> )
${this.valid_connections().map( .map((x) => self.render_broadcast(x))}
(x) => html` <li class="w3-bar">${this.render_connection(x)}</li> ` </ul>
<h2>Connections</h2>
<ul class="w3-ul w3-border">
${this.connections
.filter((x) => x.tunnel === undefined)
.map(
(x) => html`
<li class="w3-bar">${this.render_connection(x)}</li>
`
)} )}
</ul> </ul>
<h2 <h2>Stored Connections</h2>
class="w3-button w3-block w3-theme-d1" <ul class="w3-ul w3-border">
@click=${() => self.toggle_accordian('broadcasts')}
>
Discovery (${this.valid_broadcasts().length})
</h2>
<ul class="w3-ul w3-border w3-hide" id="broadcasts">
${this.valid_broadcasts().map((x) => self.render_broadcast(x))}
</ul>
<h2
class="w3-button w3-block w3-theme-d1"
@click=${() => self.toggle_accordian('stored_connections')}
>
Stored Connections (${this.stored_connections.length})
</h2>
<ul class="w3-ul w3-border w3-hide" id="stored_connections">
${this.stored_connections.map( ${this.stored_connections.map(
(x) => html` (x) => html`
<li> <li>
@ -308,12 +260,6 @@ class TfTabConnectionsElement extends LitElement {
<div class="w3-bar-item"> <div class="w3-bar-item">
<tf-user id=${x.pubkey} .users=${self.users}></tf-user> <tf-user id=${x.pubkey} .users=${self.users}></tf-user>
<div><small>${x.address}:${x.port}</small></div> <div><small>${x.address}:${x.port}</small></div>
<div>
<small
>Last connection:
${new Date(x.last_success * 1000)}</small
>
</div>
</div> </div>
</div> </div>
${this.render_message(x)} ${this.render_message(x)}
@ -321,13 +267,8 @@ class TfTabConnectionsElement extends LitElement {
` `
)} )}
</ul> </ul>
<h2 <h2>Local Accounts</h2>
class="w3-button w3-block w3-theme-d1" <div class="w3-container">
@click=${() => self.toggle_accordian('local_accounts')}
>
Local Accounts (${this.identities.length})
</h2>
<div class="w3-container w3-hide" id="local_accounts">
${this.identities.map( ${this.identities.map(
(x) => (x) =>
html`<div html`<div

View File

@ -18,7 +18,6 @@ class TfTabNewsFeedElement extends LitElement {
time_range: {type: Array}, time_range: {type: Array},
time_loading: {type: Array}, time_loading: {type: Array},
private_messages: {type: Array}, private_messages: {type: Array},
recent_reactions: {type: Array},
}; };
} }
@ -38,7 +37,6 @@ class TfTabNewsFeedElement extends LitElement {
this.start_time = new Date().valueOf(); this.start_time = new Date().valueOf();
this.time_range = [0, 0]; this.time_range = [0, 0];
this.time_loading = undefined; this.time_loading = undefined;
this.recent_reactions = [];
this.loading = 0; this.loading = 0;
} }
@ -48,63 +46,6 @@ class TfTabNewsFeedElement extends LitElement {
: this.hash.substring(1); : this.hash.substring(1);
} }
async _fetch_related_messages(messages) {
let refs = await tfrpc.rpc.query(
`
WITH
news AS (
SELECT value AS id FROM json_each(?)
)
SELECT refs_out.ref AS ref FROM messages_refs refs_out JOIN news ON refs_out.message = news.id
UNION
SELECT refs_in.message AS ref FROM messages_refs refs_in JOIN news ON refs_in.ref = news.id
`,
[JSON.stringify(messages.map((x) => x.id))]
);
let related_messages = await tfrpc.rpc.query(
`
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?2) refs ON messages.id = refs.value
JOIN json_each(?1) AS following ON messages.author = following.value
`,
[JSON.stringify(this.following), JSON.stringify(refs.map((x) => x.ref))]
);
let combined = [].concat(messages, related_messages);
let refs2 = await tfrpc.rpc.query(
`
WITH
news AS (
SELECT value AS id FROM json_each(?)
)
SELECT refs_out.ref AS ref FROM messages_refs refs_out JOIN news ON refs_out.message = news.id
UNION
SELECT refs_in.message AS ref FROM messages_refs refs_in JOIN news ON refs_in.ref = news.id
`,
[JSON.stringify(combined.map((x) => x.id))]
);
let t0 = new Date();
let result = [].concat(
combined,
await tfrpc.rpc.query(
`
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM json_each(?2) refs
JOIN messages ON messages.id = refs.value
JOIN json_each(?1) following ON messages.author = following.value
WHERE messages.content ->> 'type' != 'post'
`,
[
JSON.stringify(this.following),
JSON.stringify(refs2.map((x) => x.ref)),
]
)
);
let t1 = new Date();
console.log((t1 - t0) / 1000);
return result;
}
async fetch_messages(start_time, end_time) { async fetch_messages(start_time, end_time) {
this.time_loading = [start_time, end_time]; this.time_loading = [start_time, end_time];
let result; let result;
@ -166,8 +107,7 @@ class TfTabNewsFeedElement extends LitElement {
[this.hash.substring(1)] [this.hash.substring(1)]
); );
} else if (this.hash.startsWith('##')) { } else if (this.hash.startsWith('##')) {
let t0 = new Date(); result = await tfrpc.rpc.query(
let initial_messages = await tfrpc.rpc.query(
` `
WITH WITH
all_news AS ( all_news AS (
@ -180,11 +120,21 @@ class TfTabNewsFeedElement extends LitElement {
FROM messages_fts(?5) FROM messages_fts(?5)
JOIN messages ON messages.rowid = messages_fts.rowid JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?1) AS following ON messages.author = following.value JOIN json_each(?1) AS following ON messages.author = following.value
JOIN json_tree(messages.content, '$.mentions') AS mention ON mention.value = '#' || ?4 JOIN json_tree(messages.content, '$.mentions') AS mention ON mention.value = '#' || ?4),
) news AS (SELECT * FROM all_news
SELECT TRUE AS is_primary, all_news.* FROM all_news
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3 WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
ORDER BY all_news.timestamp DESC LIMIT 20 ORDER BY all_news.timestamp DESC LIMIT 20)
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
UNION
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.message
JOIN messages ON messages_refs.ref = messages.id
UNION
SELECT TRUE AS is_primary, news.* FROM news
`, `,
[ [
JSON.stringify(this.following), JSON.stringify(this.following),
@ -194,12 +144,6 @@ class TfTabNewsFeedElement extends LitElement {
'"#' + this.hash.substring(2).replace('"', '""') + '"', '"#' + this.hash.substring(2).replace('"', '""') + '"',
] ]
); );
let t1 = new Date();
result = await this._fetch_related_messages(initial_messages);
let t2 = new Date();
console.log(
`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
);
} else if (this.hash == '#🔐') { } else if (this.hash == '#🔐') {
result = await tfrpc.rpc.query( result = await tfrpc.rpc.query(
` `
@ -214,49 +158,34 @@ class TfTabNewsFeedElement extends LitElement {
[JSON.stringify(this.private_messages), start_time, end_time] [JSON.stringify(this.private_messages), start_time, end_time]
); );
result = (await this.decrypt(result)).filter((x) => x.decrypted); result = (await this.decrypt(result)).filter((x) => x.decrypted);
} else if (this.hash == '#👍') {
result = await tfrpc.rpc.query(
`
WITH votes AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?1) AS following ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'vote' AND
(?2 IS NULL OR messages.timestamp >= ?2) AND messages.timestamp < ?3
ORDER BY timestamp DESC limit 20)
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM votes
JOIN messages ON messages.id = votes.content ->> '$.vote.link'
UNION
SELECT TRUE AS is_primary, * FROM votes
`,
[JSON.stringify(this.following), start_time, end_time]
);
} else { } else {
let t0 = new Date(); result = await tfrpc.rpc.query(
let initial_messages = await tfrpc.rpc.query(
` `
WITH WITH
all_news AS ( all_news AS (
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages FROM messages
JOIN json_each(?) AS following ON messages.author = following.value JOIN json_each(?) AS following ON messages.author = following.value
), WHERE timestamp >= 0 AND timestamp < ?3),
news AS ( news AS (
SELECT * FROM all_news SELECT * FROM all_news
WHERE all_news.timestamp < ?3 AND (?2 IS NULL OR all_news.timestamp >= ?2) WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
ORDER BY timestamp DESC LIMIT 20 ORDER BY timestamp DESC LIMIT 20
) )
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
UNION
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.message
JOIN messages ON messages_refs.ref = messages.id
UNION
SELECT TRUE AS is_primary, news.* FROM news SELECT TRUE AS is_primary, news.* FROM news
`, `,
[JSON.stringify(this.following), start_time, end_time] [JSON.stringify(this.following), start_time, end_time]
); );
let t1 = new Date();
result = await this._fetch_related_messages(initial_messages);
let t2 = new Date();
console.log(
`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
);
} }
this.time_loading = undefined; this.time_loading = undefined;
return result; return result;
@ -282,11 +211,7 @@ class TfTabNewsFeedElement extends LitElement {
try { try {
let more = []; let more = [];
let last_start_time = this.time_range[0]; let last_start_time = this.time_range[0];
try {
more = await this.fetch_messages(null, last_start_time); more = await this.fetch_messages(null, last_start_time);
} catch (e) {
console.log(e);
}
this.update_time_range_from_messages( this.update_time_range_from_messages(
more.filter((x) => x.timestamp < last_start_time) more.filter((x) => x.timestamp < last_start_time)
); );
@ -386,7 +311,7 @@ class TfTabNewsFeedElement extends LitElement {
this.messages = this.merge_messages(this.messages, messages); this.messages = this.merge_messages(this.messages, messages);
this.time_loading = undefined; this.time_loading = undefined;
console.log( console.log(
`loading ${messages.length} messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s` `loading messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s`
); );
} }
@ -459,14 +384,9 @@ class TfTabNewsFeedElement extends LitElement {
`; `;
} }
return cache(html` return cache(html`
${!this.hash.startsWith('#%') <button class="w3-button w3-theme-d1" @click=${this.mark_all_read}>
? html`<button
class="w3-button w3-theme-d1"
@click=${this.mark_all_read}
>
Mark All Read Mark All Read
</button>` </button>
: undefined}
<tf-news <tf-news
id="news" id="news"
whoami=${this.whoami} whoami=${this.whoami}
@ -477,7 +397,6 @@ class TfTabNewsFeedElement extends LitElement {
.expanded=${this.expanded} .expanded=${this.expanded}
channel=${this.channel()} channel=${this.channel()}
channel_unread=${this.channels_unread?.[this.channel()]} channel_unread=${this.channels_unread?.[this.channel()]}
.recent_reactions=${this.recent_reactions}
></tf-news> ></tf-news>
${more} ${more}
`); `);

View File

@ -24,7 +24,6 @@ class TfTabNewsElement extends LitElement {
channels_latest: {type: Object}, channels_latest: {type: Object},
connections: {type: Array}, connections: {type: Array},
private_messages: {type: Array}, private_messages: {type: Array},
recent_reactions: {type: Array},
}; };
} }
@ -44,7 +43,6 @@ class TfTabNewsElement extends LitElement {
this.channels_latest = {}; this.channels_latest = {};
this.channels = []; this.channels = [];
this.connections = []; this.connections = [];
this.recent_reactions = [];
tfrpc.rpc.localStorageGet('drafts').then(function (d) { tfrpc.rpc.localStorageGet('drafts').then(function (d) {
self.drafts = JSON.parse(d || '{}'); self.drafts = JSON.parse(d || '{}');
}); });
@ -97,13 +95,7 @@ class TfTabNewsElement extends LitElement {
} }
unread_status(channel) { unread_status(channel) {
if (channel === undefined) {
if ( if (
Object.keys(this.channels_unread).some((x) => this.unread_status(x))
) {
return '✉️ ';
}
} else if (
this.channels_latest[channel] && this.channels_latest[channel] &&
this.channels_latest[channel] > 0 && this.channels_latest[channel] > 0 &&
(this.channels_unread[channel] === undefined || (this.channels_unread[channel] === undefined ||
@ -202,12 +194,6 @@ class TfTabNewsElement extends LitElement {
style=${this.hash == '#@' ? 'font-weight: bold' : undefined} style=${this.hash == '#@' ? 'font-weight: bold' : undefined}
>${this.unread_status('@')}@mentions</a >${this.unread_status('@')}@mentions</a
> >
<a
href="#👍"
class="w3-bar-item w3-button"
style=${this.hash == '#👍' ? 'font-weight: bold' : undefined}
>${this.unread_status('👍')}👍votes</a
>
<a <a
href="#🔐" href="#🔐"
class="w3-bar-item w3-button" class="w3-bar-item w3-button"
@ -237,24 +223,15 @@ class TfTabNewsElement extends LitElement {
` `
)} )}
<a class="w3-bar-item w3-theme-d2 w3-button" href="#connections"> <h4 class="w3-bar-item w3-theme-d2">Connections</h4>
<h4 style="margin: 0">Connections</h4>
</a>
${this.connections ${this.connections
.filter((x) => x.id) .filter((x) => x.id && !x.destroy_reason)
.map( .map(
(x) => html` (x) => html`
<tf-user <tf-user
class="w3-bar-item" class="w3-bar-item"
style=${x.destroy_reason style="max-width: 100%"
? 'border-left: 4px solid red; border-right: 4px solid red'
: x.connected
? x.flags?.one_shot
? 'border-left: 4px solid blue; border-right: 4px solid blue'
: 'border-left: 4px solid green; border-right: 4px solid green'
: ''}
id=${x.id} id=${x.id}
fallback_name=${x.host}
.users=${this.users} .users=${this.users}
></tf-user> ></tf-user>
` `
@ -308,7 +285,7 @@ class TfTabNewsElement extends LitElement {
return cache(html` return cache(html`
${this.render_sidebar()} ${this.render_sidebar()}
<div <div
style="margin-left: 2in; padding: 0px; top: 0; max-height: 100%; overflow: auto; contain: layout" style="margin-left: 2in; padding: 0px; top: 0; max-height: 100%; overflow: auto"
id="main" id="main"
class="w3-main" class="w3-main"
> >
@ -333,7 +310,7 @@ class TfTabNewsElement extends LitElement {
class="w3-button w3-hide-large" class="w3-button w3-hide-large"
@click=${this.show_sidebar} @click=${this.show_sidebar}
> >
${this.unread_status()}&#9776; &#9776;
</div> </div>
Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>! Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
${edit_profile} ${edit_profile}
@ -362,7 +339,6 @@ class TfTabNewsElement extends LitElement {
.channels_unread=${this.channels_unread} .channels_unread=${this.channels_unread}
.channels_latest=${this.channels_latest} .channels_latest=${this.channels_latest}
.private_messages=${this.private_messages} .private_messages=${this.private_messages}
.recent_reactions=${this.recent_reactions}
></tf-tab-news-feed> ></tf-tab-news-feed>
</div> </div>
</div> </div>

View File

@ -6,7 +6,6 @@ class TfUserElement extends LitElement {
static get properties() { static get properties() {
return { return {
id: {type: String}, id: {type: String},
fallback_name: {type: String},
users: {type: Object}, users: {type: Object},
}; };
} }
@ -16,7 +15,6 @@ class TfUserElement extends LitElement {
constructor() { constructor() {
super(); super();
this.id = null; this.id = null;
this.fallback_name = null;
this.users = {}; this.users = {};
} }
@ -33,16 +31,13 @@ class TfUserElement extends LitElement {
>`; >`;
let name = this.users?.[this.id]?.name; let name = this.users?.[this.id]?.name;
name = html`<a target="_top" href=${'#' + this.id} name = html`<a target="_top" href=${'#' + this.id}
>${name ?? this.fallback_name ?? this.id}</a >${name !== undefined ? name : this.id}</a
>`; >`;
if (user) { if (user) {
let image_link = user.image; let image_link = user.image;
if (typeof image_link == 'string' && !image_link.startsWith('&')) { image_link =
try { typeof image_link == 'string' ? image_link : image_link?.link;
image_link = JSON.parse(image_link)?.link;
} catch {}
}
if (image_link !== undefined) { if (image_link !== undefined) {
image = html`<img image = html`<img
class=${'w3-theme-l4 ' + shape} class=${'w3-theme-l4 ' + shape}

View File

@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "💾", "emoji": "💾",
"previous": "&tzZFIe7Y54O4sx1QtAPdemkXh+p5qHXSG/dlS7NP6OQ=.sha256" "previous": "&mvGTlWKFR5QM/3nb4fJ2WQq0n/gNKvBmhGDkAvb8ki8=.sha256"
} }

View File

@ -8,7 +8,7 @@ async function query(sql, args) {
async function get_biggest() { async function get_biggest() {
return query(` return query(`
select author, size from messages_stats group by author order by size desc limit 10; select author, sum(length(content)) as size from messages group by author order by size desc limit 10;
`); `);
} }
@ -62,14 +62,15 @@ function nice_size(bytes) {
} }
async function main() { async function main() {
await app.setDocument('<p style="color: #fff">Analyzing feeds...</p>'); await app.setDocument(
let most_follows = get_most_follows(); '<p style="color: #fff">Finding the top 10 largest feeds...</p>'
);
let most_follows = await get_most_follows();
let total = await get_total(); let total = await get_total();
let identities = await ssb.getAllIdentities(); let identities = await ssb.getAllIdentities();
let following1 = await ssb.following(identities, 1); let following1 = await ssb.following(identities, 1);
let following2 = await ssb.following(identities, 2); let following2 = await ssb.following(identities, 2);
let biggest = await get_biggest(); let biggest = await get_biggest();
most_follows = await most_follows;
let names = await get_names( let names = await get_names(
[].concat( [].concat(
biggest.map((x) => x.author), biggest.map((x) => x.author),
@ -93,7 +94,7 @@ async function main() {
} }
let html = `<body style="color: #000; background-color: #ddd">\n let html = `<body style="color: #000; background-color: #ddd">\n
<h1>Storage Summary</h1> <h1>Storage Summary</h1>
<h2>Top Accounts by Size</h2> <h2>Top 10 Accounts by Size</h2>
<ol>`; <ol>`;
for (let item of biggest) { for (let item of biggest) {
html += `<li> html += `<li>
@ -104,7 +105,7 @@ async function main() {
} }
html += ` html += `
</ol> </ol>
<h2>Top Accounts by Follows</h2> <h2>Top 10 Accounts by Follows</h2>
<ol>`; <ol>`;
for (let item of most_follows) { for (let item of most_follows) {
html += `<li> html += `<li>

View File

@ -1,5 +0,0 @@
{
"type": "tildefriends-app",
"emoji": "🕸",
"previous": "&n7hu5b8/TsfiG6FDlCRG5nPCrIdCr96+xpIJ/aQT/uM=.sha256"
}

View File

@ -1,100 +0,0 @@
let g_hash;
async function query(sql, params) {
let results = [];
await ssb.sqlAsync(sql, params, function (row) {
results.push(row);
});
return results;
}
async function resolve(id) {
try {
let blob = await ssb.blobGet(id);
if (blob) {
let json;
try {
json = JSON.parse(utf8Decode(blob));
} catch {
return {id: utf8Decode(blob)};
}
if (json?.links) {
for (let [key, value] of Object.entries(json.links)) {
json.links[key] = await resolve(value);
}
return json;
} else {
return 'huh?' + json;
}
} else {
return `missing<${id}>`;
}
} catch (e) {
return id + ': ' + e.message;
}
}
async function get_names(identities) {
return Object.fromEntries(
(
await query(
`
SELECT author, name FROM (
SELECT
messages.author,
RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank,
messages.content ->> 'name' AS name
FROM messages
JOIN json_each(?) AS identities ON identities.value = messages.author
WHERE
json_extract(messages.content, '$.type') = 'about' AND
content ->> 'about' = messages.author AND name IS NOT NULL)
WHERE author_rank = 1
`,
[JSON.stringify(identities)]
)
).map((x) => [x.author, x.name])
);
}
async function render(hash) {
g_hash = hash;
if (!hash) {
let sites = await query(
`
SELECT site.author, site.id
FROM messages site
WHERE site.content ->> 'type' = 'web-init'
`,
[]
);
let names = await get_names(sites.map((x) => x.author));
if (hash === g_hash) {
await app.setDocument(
`<ul style="background-color: #ddd">${sites.map((x) => `<li><a target="_top" href="#${encodeURIComponent(x.id)}">${names[x.author] ?? x.author} - ${x.id}</a></li>`).join('\n')}</ul>`
);
}
} else {
let site_id =
hash.charAt(0) == '#'
? decodeURIComponent(hash.substring(1))
: decodeURIComponent(hash);
await app.setDocument(`<html style="margin: 0; padding: 0; width: 100vw; height: 100vh; margin: 0; padding: 0">
<body style="display: flex; flex-direction: column; width: 100vw; height: 100vh">
<iframe src="${encodeURIComponent(site_id)}/index.html" style="flex: 1 1; border: 0; background-color: #fff"></iframe>
</body>
</html>`);
}
}
core.register('message', async function message_handler(message) {
if (message.event == 'hashChange') {
await render(message.hash);
}
});
async function main() {
render(null);
}
main();

View File

@ -1,63 +0,0 @@
async function query(sql, params) {
let results = [];
await ssb.sqlAsync(sql, params, function (row) {
results.push(row);
});
return results;
}
function guess_content_type(name) {
if (name.endsWith('.html')) {
return 'text/html; charset=UTF-8';
} else if (name.endsWith('.js') || name.endsWith('.mjs')) {
return 'text/javascript; charset=UTF-8';
} else if (name.endsWith('.css')) {
return 'text/stylesheet; charset=UTF-8';
} else {
return 'application/binary';
}
}
async function main() {
let path = request.path.replaceAll(/(%[0-9a-fA-F]{2})/g, (x) =>
String.fromCharCode(parseInt(x.substring(1), 16))
);
let match = path.match(/^(%.{44}\.sha256)(?:\/)?(.*)$/);
let content_type = guess_content_type(request.path);
let root = await query(
`
SELECT root.content ->> 'root' AS root
FROM messages site
JOIN messages root
ON site.id = ? AND root.author = site.author AND root.content ->> 'site' = site.id
ORDER BY root.sequence DESC LIMIT 1
`,
[match[1]]
);
let root_id = root[0]['root'];
let last_id = root_id;
let blob = await ssb.blobGet(root_id);
try {
for (let part of match[2]?.split('/')) {
let dir = JSON.parse(utf8Decode(blob));
last_id = dir?.links[part];
blob = await ssb.blobGet(dir?.links[part]);
content_type = guess_content_type(part);
}
} catch {}
respond({
status_code: 200,
data: blob ? utf8Decode(blob) : `${last_id} not found`,
content_type: content_type,
});
}
main().catch(function (e) {
respond({
status_code: 200,
data: `${e.message}\n${e.stack}`,
content_type: 'text/plain',
});
});

View File

@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "👋", "emoji": "👋",
"previous": "&1o8MrBHfH42NnO+ruajwCmW/DUCb+IT1qtnAZI/agyo=.sha256" "previous": "&wAb7J6E35xEXpiXsQ6t1RaWTGIvlatUnyH8ipF6pVic=.sha256"
} }

View File

@ -45,11 +45,6 @@
<i class="fa fa-mobile-screen w3-xlarge"></i> <i class="fa fa-mobile-screen w3-xlarge"></i>
<i class="fa-brands fa-windows w3-xlarge"></i> <i class="fa-brands fa-windows w3-xlarge"></i>
</p> </p>
<a
class="w3-button w3-blue w3-padding-large"
href="https://www.tildefriends.net/~core/ssb/"
>🦀 Try It</a
>
<a <a
class="w3-button w3-black w3-padding-large" class="w3-button w3-black w3-padding-large"
href="https://dev.tildefriends.net/cory/tildefriends/releases" href="https://dev.tildefriends.net/cory/tildefriends/releases"
@ -57,7 +52,12 @@
> >
<a <a
class="w3-button w3-black w3-padding-large" class="w3-button w3-black w3-padding-large"
href="https://dev.tildefriends.net/cory/tildefriends" href="https://www.tildefriends.net/~core/ssb/"
><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> Development</a ><i class="fa fa-mug-hot"></i> Development</a
> >
<a <a
@ -65,11 +65,6 @@
href="https://docs.tildefriends.net/" href="https://docs.tildefriends.net/"
><i class="fa fa-book"></i> Documentation</a ><i class="fa fa-book"></i> Documentation</a
> >
<a
class="w3-button w3-black w3-padding-large"
href="https://www.tildefriends.net/~cory/tildeblog/"
><i class="fa fa-solid fa-square-rss"></i> Blog</a
>
<p> <p>
<a <a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top" class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
@ -91,13 +86,6 @@
<img src="googleplay.svg" style="height: 2em; margin: 0" /> <img src="googleplay.svg" style="height: 2em; margin: 0" />
Get it on Google Play (Open Testing) Get it on Google Play (Open Testing)
</a> </a>
<a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
href="https://testflight.apple.com/join/tXxgtSpE"
>
<img src="ios.svg" style="height: 2em; margin: 0" />
Get it on iOS (TestFlight)
</a>
</p> </p>
</div> </div>
<div class="w3-col l4 m6"> <div class="w3-col l4 m6">

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="814" height="1000">
<path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

View File

@ -1,4 +1,4 @@
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */ /* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* 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} html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@ -108,8 +108,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.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-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-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-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size: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-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-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@ -150,7 +148,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-button:hover{color:#000!important;background-color:#ccc!important} .w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} .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-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
@ -178,19 +175,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!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-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-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!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-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-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,26 +1,53 @@
import * as core from './core.js'; import * as core from './core.js';
let g_next_id = 1;
let g_calls = {};
let gSessionIndex = 0; let gSessionIndex = 0;
/**
* TODOC
* @returns
*/
function makeSessionId() {
return 'session_' + (gSessionIndex++).toString();
}
/**
* TODOC
* @returns
*/
function App() { function App() {
this._on_output = null;
this._send_queue = []; this._send_queue = [];
this.calls = {};
this._next_call_id = 1;
return this; return this;
} }
/**
* TODOC
* @param {*} callback
*/
App.prototype.readOutput = function (callback) {
this._on_output = callback;
};
/**
* TODOC
* @param {*} api
* @returns
*/
App.prototype.makeFunction = function (api) { App.prototype.makeFunction = function (api) {
let self = this; let self = this;
let result = function () { let result = function () {
let id = self._next_call_id++; let id = g_next_id++;
while (!id || self.calls[id]) { while (!id || g_calls[id]) {
id = self._next_call_id++; id = g_next_id++;
} }
let promise = new Promise(function (resolve, reject) { let promise = new Promise(function (resolve, reject) {
self.calls[id] = {resolve: resolve, reject: reject}; g_calls[id] = {resolve: resolve, reject: reject};
}); });
let message = { let message = {
action: 'tfrpc', message: 'tfrpc',
method: api[0], method: api[0],
params: [...arguments], params: [...arguments],
id: id, id: id,
@ -32,6 +59,10 @@ App.prototype.makeFunction = function (api) {
return result; return result;
}; };
/**
* TODOC
* @param {*} message
*/
App.prototype.send = function (message) { App.prototype.send = function (message) {
if (this._send_queue) { if (this._send_queue) {
if (this._on_output) { if (this._on_output) {
@ -46,6 +77,11 @@ App.prototype.send = function (message) {
} }
}; };
/**
* TODOC
* @param {*} request
* @param {*} response
*/
exports.app_socket = async function socket(request, response) { exports.app_socket = async function socket(request, response) {
let process; let process;
let options = {}; let options = {};
@ -66,16 +102,10 @@ exports.app_socket = async function socket(request, response) {
try { try {
message = JSON.parse(event.data); message = JSON.parse(event.data);
} catch (error) { } catch (error) {
print( print('ERROR', error, event.data, event.data.length, event.opCode);
'WebSocket error:',
error,
event.data,
event.data.length,
event.opCode
);
return; return;
} }
if (!process && message.action == 'hello') { if (message.action == 'hello') {
let packageOwner; let packageOwner;
let packageName; let packageName;
let blobId; let blobId;
@ -92,7 +122,7 @@ exports.app_socket = async function socket(request, response) {
if (!blobId) { if (!blobId) {
response.send( response.send(
JSON.stringify({ JSON.stringify({
action: 'tfrpc', message: 'tfrpc',
method: 'error', method: 'error',
params: [message.path + ' not found'], params: [message.path + ' not found'],
id: -1, id: -1,
@ -133,7 +163,7 @@ exports.app_socket = async function socket(request, response) {
options.packageOwner = packageOwner; options.packageOwner = packageOwner;
options.packageName = packageName; options.packageName = packageName;
options.url = message.url; options.url = message.url;
let sessionId = 'session_' + (gSessionIndex++).toString(); let sessionId = makeSessionId();
if (blobId) { if (blobId) {
if (message.edit_only) { if (message.edit_only) {
response.send( response.send(
@ -145,24 +175,9 @@ exports.app_socket = async function socket(request, response) {
} }
} }
if (process) { if (process) {
process.client_api.tfrpc = function (message) { process.app.readOutput(function (message) {
if (message.id) {
let calls = process?.app?.calls;
if (calls) {
let call = calls[message.id];
if (call) {
if (message.error !== undefined) {
call.reject(message.error);
} else {
call.resolve(message.result);
}
delete calls[message.id];
}
}
}
};
process.app._on_output = (message) =>
response.send(JSON.stringify(message), 0x1); response.send(JSON.stringify(message), 0x1);
});
process.app.send(); process.app.send();
} }
@ -191,13 +206,26 @@ exports.app_socket = async function socket(request, response) {
if (process && process.timeout > 0) { if (process && process.timeout > 0) {
setTimeout(ping, process.timeout); setTimeout(ping, process.timeout);
} }
} else { } else if (message.action == 'resetPermission') {
if (process) { if (process) {
if (process.client_api[message.action]) { process.resetPermission(message.permission);
process.client_api[message.action](message);
} else if (process.eventHandlers['message']) {
await core.invoke(process.eventHandlers['message'], [message]);
} }
} else if (message.action == 'setActiveIdentity') {
process.setActiveIdentity(message.identity);
} else if (message.action == 'createIdentity') {
await process.createIdentity();
} else if (message.message == 'tfrpc') {
if (message.id && g_calls[message.id]) {
if (message.error !== undefined) {
g_calls[message.id].reject(message.error);
} else {
g_calls[message.id].resolve(message.result);
}
delete g_calls[message.id];
}
} else {
if (process && process.eventHandlers['message']) {
await core.invoke(process.eventHandlers['message'], [message]);
} }
} }
} else if (event.opCode == 0x8) { } else if (event.opCode == 0x8) {

View File

@ -266,7 +266,6 @@ class TfNavigationElement extends LitElement {
<button <button
@click=${() => this.reset_permission(key)} @click=${() => this.reset_permission(key)}
class="w3-button w3-red" class="w3-button w3-red"
id=${'permission_reset:' + key}
> >
Reset Reset
</button> </button>
@ -276,7 +275,6 @@ class TfNavigationElement extends LitElement {
<button <button
@click=${() => (this.show_permissions = false)} @click=${() => (this.show_permissions = false)}
class="w3-button w3-blue" class="w3-button w3-blue"
id="permissions_close"
> >
Close Close
</button> </button>
@ -411,7 +409,6 @@ class TfFilesElement extends LitElement {
current: {type: String}, current: {type: String},
files: {type: Object}, files: {type: Object},
dropping: {type: Number}, dropping: {type: Number},
drop_target: {type: String},
}; };
} }
@ -450,9 +447,6 @@ class TfFilesElement extends LitElement {
if (!this.files[file].clean) { if (!this.files[file].clean) {
classes.push('dirty'); classes.push('dirty');
} }
if (this.drop_target == file) {
classes.push('drop');
}
return html`<div return html`<div
class="${classes.join(' ')}" class="${classes.join(' ')}"
@click=${(x) => this.file_click(file)} @click=${(x) => this.file_click(file)}
@ -469,12 +463,11 @@ class TfFilesElement extends LitElement {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
this.dropping = 0; this.dropping = 0;
this.drop_target = undefined;
for (let file of event.dataTransfer.files) { for (let file of event.dataTransfer.files) {
let buffer = await file.arrayBuffer(); let buffer = await file.arrayBuffer();
let text = new TextDecoder('latin1').decode(buffer); let text = new TextDecoder('latin1').decode(buffer);
gFiles[file.name] = { gFiles[file.name] = {
doc: cm6.EditorState.create({ doc: new cm6.EditorState.create({
doc: text, doc: text,
extensions: cm6.extensions, extensions: cm6.extensions,
}), }),
@ -493,7 +486,6 @@ class TfFilesElement extends LitElement {
*/ */
drag_enter(event) { drag_enter(event) {
this.dropping++; this.dropping++;
this.drop_target = event.srcElement.innerText.trim();
event.preventDefault(); event.preventDefault();
} }
@ -503,13 +495,6 @@ class TfFilesElement extends LitElement {
*/ */
drag_leave(event) { drag_leave(event) {
this.dropping--; this.dropping--;
if (this.dropping == 0) {
this.drop_target = undefined;
}
}
drag_over(event) {
event.preventDefault();
} }
/** /**
@ -536,10 +521,6 @@ class TfFilesElement extends LitElement {
background-color: #2aa198; background-color: #2aa198;
} }
div.file.drop {
border: 4px solid red;
}
div.file.dirty::after { div.file.dirty::after {
content: '*'; content: '*';
} }
@ -548,12 +529,20 @@ class TfFilesElement extends LitElement {
@drop=${this.drop} @drop=${this.drop}
@dragenter=${this.drag_enter} @dragenter=${this.drag_enter}
@dragleave=${this.drag_leave} @dragleave=${this.drag_leave}
@dragover=${this.drag_over}
> >
${Object.keys(this.files) ${Object.keys(this.files)
.sort() .sort()
.map((x) => self.render_file(x))} .map((x) => self.render_file(x))}
</div> </div>
<div
?hidden=${this.dropping == 0}
@drop=${this.drop}
@dragenter=${this.drag_enter}
@dragleave=${this.drag_leave}
style="text-align: center; vertical-align: middle; outline: 16px solid red; margin: -8px; background-color: rgba(255, 0, 0, 0.5); position: absolute; left: 16px; top: 16px; width: calc(100% - 16px); height: calc(100% - 16px); z-index: 1000"
>
Drop File(s)
</div>
`; `;
} }
} }
@ -1336,7 +1325,7 @@ function _receive_websocket_message(message) {
line.append(key, message.stats[key]); line.append(key, message.stats[key]);
} }
} }
} else if (message && message.action === 'tfrpc' && message.method) { } else if (message && message.message === 'tfrpc' && message.method) {
let api = k_api[message.method]; let api = k_api[message.method];
let id = message.id; let id = message.id;
let params = message.params; let params = message.params;
@ -1344,14 +1333,14 @@ function _receive_websocket_message(message) {
Promise.resolve(api.func(...params)) Promise.resolve(api.func(...params))
.then(function (result) { .then(function (result) {
send({ send({
action: 'tfrpc', message: 'tfrpc',
id: id, id: id,
result: result, result: result,
}); });
}) })
.catch(function (error) { .catch(function (error) {
send({ send({
action: 'tfrpc', message: 'tfrpc',
id: id, id: id,
error: error, error: error,
}); });

View File

@ -3,22 +3,31 @@ import * as http from './http.js';
let gProcesses = {}; let gProcesses = {};
let gStatsTimer = false; let gStatsTimer = false;
let g_handler_index = 0; let kPingInterval = 60 * 1000;
const k_ping_interval = 60 * 1000; /**
* TODOC
function printError(error) { * @param {*} out
* @param {*} error
*/
function printError(out, error) {
if (error.stackTrace) { if (error.stackTrace) {
print(error.fileName + ':' + error.lineNumber + ': ' + error.message); out.print(error.fileName + ':' + error.lineNumber + ': ' + error.message);
print(error.stackTrace); out.print(error.stackTrace);
} else { } else {
for (let [k, v] of Object.entries(error)) { for (let [k, v] of Object.entries(error)) {
print(k, v); out.print(k, v);
} }
print(error.toString()); out.print(error.toString());
} }
} }
/**
* TODOC
* @param {*} handlers
* @param {*} argv
* @returns
*/
function invoke(handlers, argv) { function invoke(handlers, argv) {
let promises = []; let promises = [];
if (handlers) { if (handlers) {
@ -39,6 +48,12 @@ function invoke(handlers, argv) {
return Promise.all(promises); return Promise.all(promises);
} }
/**
* TODOC
* @param {*} eventName
* @param {*} argv
* @returns
*/
function broadcastEvent(eventName, argv) { function broadcastEvent(eventName, argv) {
let promises = []; let promises = [];
for (let process of Object.values(gProcesses)) { for (let process of Object.values(gProcesses)) {
@ -49,6 +64,11 @@ function broadcastEvent(eventName, argv) {
return Promise.all(promises); return Promise.all(promises);
} }
/**
* TODOC
* @param {*} message
* @returns
*/
function broadcast(message) { function broadcast(message) {
let sender = this; let sender = this;
let promises = []; let promises = [];
@ -65,6 +85,12 @@ function broadcast(message) {
return Promise.all(promises); return Promise.all(promises);
} }
/**
* TODOC
* @param {String} eventName
* @param {*} argv
* @returns
*/
function broadcastAppEventToUser( function broadcastAppEventToUser(
user, user,
packageOwner, packageOwner,
@ -87,6 +113,12 @@ function broadcastAppEventToUser(
return Promise.all(promises); return Promise.all(promises);
} }
/**
* TODOC
* @param {*} caller
* @param {*} process
* @returns
*/
function getUser(caller, process) { function getUser(caller, process) {
return { return {
key: process.key, key: process.key,
@ -97,6 +129,12 @@ function getUser(caller, process) {
}; };
} }
/**
* TODOC
* @param {*} user
* @param {*} process
* @returns
*/
async function getApps(user, process) { async function getApps(user, process) {
if ( if (
process.credentials && process.credentials &&
@ -123,13 +161,28 @@ async function getApps(user, process) {
return {}; return {};
} }
/**
* TODOC
* @param {*} from
* @param {*} to
* @param {*} message
* @returns
*/
function postMessageInternal(from, to, message) { function postMessageInternal(from, to, message) {
if (to.eventHandlers['message']) { if (to.eventHandlers['message']) {
return invoke(to.eventHandlers['message'], [getUser(from, from), message]); return invoke(to.eventHandlers['message'], [getUser(from, from), message]);
} }
} }
/**
* TODOC
* @param {*} blobId
* @param {*} key
* @param {*} options
* @returns
*/
async function getProcessBlob(blobId, key, options) { async function getProcessBlob(blobId, key, options) {
// TODO(tasiaiso): break this down ?
let process = gProcesses[key]; let process = gProcesses[key];
if (!process && !(options && 'create' in options && !options.create)) { if (!process && !(options && 'create' in options && !options.create)) {
let resolveReady; let resolveReady;
@ -148,7 +201,7 @@ async function getProcessBlob(blobId, key, options) {
} }
process.lastActive = Date.now(); process.lastActive = Date.now();
process.lastPing = null; process.lastPing = null;
process.timeout = k_ping_interval; process.timeout = kPingInterval;
process.ready = new Promise(function (resolve, reject) { process.ready = new Promise(function (resolve, reject) {
resolveReady = resolve; resolveReady = resolve;
rejectReady = reject; rejectReady = reject;
@ -408,10 +461,10 @@ async function getProcessBlob(blobId, key, options) {
if (process.app) { if (process.app) {
process.app.makeFunction(['error'])(error); process.app.makeFunction(['error'])(error);
} else { } else {
printError(error); printError({print: print}, error);
} }
} catch (e) { } catch (e) {
printError(error); printError({print: print}, error);
} }
}; };
imports.ssb = Object.fromEntries( imports.ssb = Object.fromEntries(
@ -596,26 +649,17 @@ async function getProcessBlob(blobId, key, options) {
permissions: await imports.core.permissionsGranted(), permissions: await imports.core.permissionsGranted(),
}); });
}; };
process.client_api = { process.resetPermission = async function resetPermission(permission) {
createIdentity: function () {
return process.createIdentity();
},
resetPermission: async function resetPermission(message) {
let user = process?.credentials?.session?.name; let user = process?.credentials?.session?.name;
await ssb.setUserPermission( await ssb.setUserPermission(
user, user,
options?.packageOwner, options?.packageOwner,
options?.packageName, options?.packageName,
message.permission, permission,
undefined undefined
); );
return process.sendPermissions(); return process.sendPermissions();
},
setActiveIdentity: function setActiveIdentity(message) {
return process.setActiveIdentity(message.identity);
},
}; };
ssb.registerImports(imports, process);
process.task.setImports(imports); process.task.setImports(imports);
process.task.activate(); process.task.activate();
let source = await ssb.blobGet(blobId); let source = await ssb.blobGet(blobId);
@ -642,7 +686,7 @@ async function getProcessBlob(blobId, key, options) {
); );
} }
} catch (e) { } catch (e) {
printError(e); printError({print: print}, e);
} }
broadcastEvent('onSessionBegin', [getUser(process, process)]); broadcastEvent('onSessionBegin', [getUser(process, process)]);
if (process.app) { if (process.app) {
@ -656,10 +700,14 @@ async function getProcessBlob(blobId, key, options) {
sendStats(); sendStats();
} }
} catch (error) { } catch (error) {
if (process?.app && process?.task?.onError) { if (process.app) {
if (process?.task?.onError) {
process.task.onError(error); process.task.onError(error);
} else { } else {
printError(error); printError({print: print}, error);
}
} else {
printError({print: print}, error);
} }
rejectReady(error); rejectReady(error);
} }
@ -679,6 +727,9 @@ ssb.addEventListener('connections', function () {
broadcastEvent('onConnectionsChanged', []); broadcastEvent('onConnectionsChanged', []);
}); });
/**
* TODOC
*/
async function loadSettings() { async function loadSettings() {
let data = {}; let data = {};
try { try {
@ -697,6 +748,9 @@ async function loadSettings() {
return data; return data;
} }
/**
* TODOC
*/
function sendStats() { function sendStats() {
let apps = Object.values(gProcesses) let apps = Object.values(gProcesses)
.filter((process) => process.app) .filter((process) => process.app)
@ -712,6 +766,8 @@ function sendStats() {
} }
} }
let g_handler_index = 0;
exports.callAppHandler = async function callAppHandler( exports.callAppHandler = async function callAppHandler(
response, response,
app_blob_id, app_blob_id,

View File

@ -1,4 +1,4 @@
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */ /* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* 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} html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@ -108,8 +108,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.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-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-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-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size: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-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-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@ -150,7 +148,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-button:hover{color:#000!important;background-color:#ccc!important} .w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} .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-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
@ -178,19 +175,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!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-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-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!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-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-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}

View File

@ -25,14 +25,14 @@
}: }:
pkgs.stdenv.mkDerivation rec { pkgs.stdenv.mkDerivation rec {
pname = "tildefriends"; pname = "tildefriends";
version = "0.0.31"; version = "0.0.28";
src = pkgs.fetchFromGitea { src = pkgs.fetchFromGitea {
domain = "dev.tildefriends.net"; domain = "dev.tildefriends.net";
owner = "cory"; owner = "cory";
repo = "tildefriends"; repo = "tildefriends";
rev = "v${version}"; rev = "f02423d0846fefd5ab21fa4542fb77ce5714547c";
hash = "sha256-c2ZKVNikI5jN5GQuvp7S53qqnRZniSrJMF1FUZdVNPI="; hash = "sha256-QyM7wmViXJc4r8uTu4oE/HO3Z9tzNbFIX2+AOTQz9ZY=";
fetchSubmodules = true; fetchSubmodules = true;
}; };

2
deps/c-ares vendored

@ -1 +1 @@
Subproject commit d3a507e920e7af18a5efb7f9f1d8044ed4750013 Subproject commit b82840329a4081a1f1b125e6e6b760d4e1237b52

File diff suppressed because one or more lines are too long

243
deps/codemirror_src/package-lock.json generated vendored
View File

@ -30,9 +30,9 @@
} }
}, },
"node_modules/@codemirror/commands": { "node_modules/@codemirror/commands": {
"version": "6.8.1", "version": "6.8.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz", "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.0.tgz",
"integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==", "integrity": "sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0", "@codemirror/state": "^6.4.0",
@ -69,9 +69,9 @@
} }
}, },
"node_modules/@codemirror/lang-javascript": { "node_modules/@codemirror/lang-javascript": {
"version": "6.2.4", "version": "6.2.3",
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.3.tgz",
"integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", "integrity": "sha512-8PR3vIWg7pSu7ur8A07pGiYHgy3hHj+mRYRCSG8q+mPIrl0F02rgpGv+DsQTHRTc30rydOsf5PZ7yjKFg2Ackw==",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.0.0", "@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.6.0", "@codemirror/language": "^6.6.0",
@ -92,9 +92,9 @@
} }
}, },
"node_modules/@codemirror/language": { "node_modules/@codemirror/language": {
"version": "6.11.0", "version": "6.10.8",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.8.tgz",
"integrity": "sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==", "integrity": "sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0", "@codemirror/view": "^6.23.0",
@ -105,9 +105,9 @@
} }
}, },
"node_modules/@codemirror/lint": { "node_modules/@codemirror/lint": {
"version": "6.8.5", "version": "6.8.4",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.4.tgz",
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", "integrity": "sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.35.0", "@codemirror/view": "^6.35.0",
@ -115,9 +115,9 @@
} }
}, },
"node_modules/@codemirror/search": { "node_modules/@codemirror/search": {
"version": "6.5.11", "version": "6.5.9",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.9.tgz",
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", "integrity": "sha512-7DdQ9aaZMMxuWB1u6IIFWWuK9NocVZwvo4nG8QjJTS6oZGvteoLSiXw3EbVZVlO08Ri2ltO89JVInMpfcJxhtg==",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0", "@codemirror/view": "^6.0.0",
@ -144,9 +144,9 @@
} }
}, },
"node_modules/@codemirror/view": { "node_modules/@codemirror/view": {
"version": "6.37.0", "version": "6.36.3",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.37.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.3.tgz",
"integrity": "sha512-ghHIeRGfWB8h9Tc3sMdr7D5zp4sZvlCzG36Xjdh2ymmfAwvSaCJAAsL3HLyLEnHcE953+5Uox1bx5OS+YCW/7Q==", "integrity": "sha512-N2bilM47QWC8Hnx0rMdDxO2x2ImJ1FvZWXubwKgjeoOrWwEiFrtpA7SFHcuZ+o2Ze2VzbkgbzWVj4+V18LVkeg==",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.5.0", "@codemirror/state": "^6.5.0",
"style-mod": "^4.1.0", "style-mod": "^4.1.0",
@ -217,13 +217,13 @@
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
}, },
"node_modules/@lezer/css": { "node_modules/@lezer/css": {
"version": "1.2.1", "version": "1.1.10",
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.10.tgz",
"integrity": "sha512-2F5tOqzKEKbCUNraIXc0f6HKeyKlmMWJnBB0i4XW6dJgssrZO/YlZ2pY5xgyqDleqqhiNJ3dQhbrV2aClZQMvg==", "integrity": "sha512-V5/89eDapjeAkWPBpWEfQjZ1Hag3aYUUJOL8213X0dFRuXJ4BXa5NKl9USzOnaLod4AOpmVCkduir2oKwZYZtg==",
"dependencies": { "dependencies": {
"@lezer/common": "^1.2.0", "@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0", "@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.3.0" "@lezer/lr": "^1.0.0"
} }
}, },
"node_modules/@lezer/highlight": { "node_modules/@lezer/highlight": {
@ -245,9 +245,9 @@
} }
}, },
"node_modules/@lezer/javascript": { "node_modules/@lezer/javascript": {
"version": "1.5.1", "version": "1.4.21",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz", "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.21.tgz",
"integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==", "integrity": "sha512-lL+1fcuxWYPURMM/oFZLEDm0XuLN128QPV+VuGtKpeaOGdcl9F2LYC3nh1S9LkPqx9M0mndZFdXCipNAZpzIkQ==",
"dependencies": { "dependencies": {
"@lezer/common": "^1.2.0", "@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.1.3", "@lezer/highlight": "^1.1.3",
@ -344,9 +344,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz",
"integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -356,9 +356,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz",
"integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -368,9 +368,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz",
"integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -380,9 +380,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz",
"integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -392,9 +392,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-arm64": { "node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz",
"integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -404,9 +404,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-x64": { "node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz",
"integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -416,9 +416,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz",
"integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -428,9 +428,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz",
"integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -440,9 +440,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz",
"integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -452,9 +452,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz",
"integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -464,9 +464,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-loongarch64-gnu": { "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz",
"integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@ -476,9 +476,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": { "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz",
"integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@ -488,21 +488,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz",
"integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==",
"cpu": [
"riscv64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz",
"integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@ -512,9 +500,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz",
"integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -524,9 +512,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz",
"integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -536,9 +524,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz",
"integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -548,9 +536,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz",
"integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -560,9 +548,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz",
"integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -572,9 +560,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz",
"integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -584,9 +572,9 @@
] ]
}, },
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.7", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
}, },
"node_modules/@types/resolve": { "node_modules/@types/resolve": {
"version": "1.20.2", "version": "1.20.2",
@ -594,9 +582,9 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==" "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.14.1", "version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true, "dev": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
@ -745,11 +733,11 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.41.1", "version": "4.34.8",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz",
"integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==",
"dependencies": { "dependencies": {
"@types/estree": "1.0.7" "@types/estree": "1.0.6"
}, },
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
@ -759,26 +747,25 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.41.1", "@rollup/rollup-android-arm-eabi": "4.34.8",
"@rollup/rollup-android-arm64": "4.41.1", "@rollup/rollup-android-arm64": "4.34.8",
"@rollup/rollup-darwin-arm64": "4.41.1", "@rollup/rollup-darwin-arm64": "4.34.8",
"@rollup/rollup-darwin-x64": "4.41.1", "@rollup/rollup-darwin-x64": "4.34.8",
"@rollup/rollup-freebsd-arm64": "4.41.1", "@rollup/rollup-freebsd-arm64": "4.34.8",
"@rollup/rollup-freebsd-x64": "4.41.1", "@rollup/rollup-freebsd-x64": "4.34.8",
"@rollup/rollup-linux-arm-gnueabihf": "4.41.1", "@rollup/rollup-linux-arm-gnueabihf": "4.34.8",
"@rollup/rollup-linux-arm-musleabihf": "4.41.1", "@rollup/rollup-linux-arm-musleabihf": "4.34.8",
"@rollup/rollup-linux-arm64-gnu": "4.41.1", "@rollup/rollup-linux-arm64-gnu": "4.34.8",
"@rollup/rollup-linux-arm64-musl": "4.41.1", "@rollup/rollup-linux-arm64-musl": "4.34.8",
"@rollup/rollup-linux-loongarch64-gnu": "4.41.1", "@rollup/rollup-linux-loongarch64-gnu": "4.34.8",
"@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8",
"@rollup/rollup-linux-riscv64-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-gnu": "4.34.8",
"@rollup/rollup-linux-riscv64-musl": "4.41.1", "@rollup/rollup-linux-s390x-gnu": "4.34.8",
"@rollup/rollup-linux-s390x-gnu": "4.41.1", "@rollup/rollup-linux-x64-gnu": "4.34.8",
"@rollup/rollup-linux-x64-gnu": "4.41.1", "@rollup/rollup-linux-x64-musl": "4.34.8",
"@rollup/rollup-linux-x64-musl": "4.41.1", "@rollup/rollup-win32-arm64-msvc": "4.34.8",
"@rollup/rollup-win32-arm64-msvc": "4.41.1", "@rollup/rollup-win32-ia32-msvc": "4.34.8",
"@rollup/rollup-win32-ia32-msvc": "4.41.1", "@rollup/rollup-win32-x64-msvc": "4.34.8",
"@rollup/rollup-win32-x64-msvc": "4.41.1",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@ -853,13 +840,13 @@
} }
}, },
"node_modules/terser": { "node_modules/terser": {
"version": "5.40.0", "version": "5.39.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.40.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
"integrity": "sha512-cfeKl/jjwSR5ar7d0FGmave9hFGJT8obyo0z+CrQOylLDbk7X81nPU6vq9VORa5jU30SkDnT2FXjLbR8HLP+xA==", "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@jridgewell/source-map": "^0.3.3", "@jridgewell/source-map": "^0.3.3",
"acorn": "^8.14.0", "acorn": "^8.8.2",
"commander": "^2.20.0", "commander": "^2.20.0",
"source-map-support": "~0.5.20" "source-map-support": "~0.5.20"
}, },

2
deps/libbacktrace vendored

@ -1 +1 @@
Subproject commit f1104f3270095831df536a2539f4cc408365105c Subproject commit 0034e33946824057b48c5e686a3aefc761b37384

2
deps/libuv vendored

@ -1 +1 @@
Subproject commit 5152db2cbfeb5582e9c27c5ea1dba2cd9e10759b Subproject commit 8fb9cb919489a48880680a56efecff6a7dfb4504

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
deps/openssl_src vendored

@ -1 +1 @@
Subproject commit 636dfadc70ce26f2473870570bfd9ec352806b1d Subproject commit a26d85337dbdcd33c971f38eb3fa5150e75cea87

2
deps/quickjs vendored

@ -1 +1 @@
Subproject commit 19abf1888db5884a5758036ff6e7fa2b340acedc Subproject commit 3f81070e573e3592728dbbbd04c84c498b20d6dc

882
deps/sqlite/shell.c vendored

File diff suppressed because it is too large Load Diff

4103
deps/sqlite/sqlite3.c vendored

File diff suppressed because it is too large Load Diff

146
deps/sqlite/sqlite3.h vendored
View File

@ -133,7 +133,7 @@ extern "C" {
** **
** Since [version 3.6.18] ([dateof:3.6.18]), ** Since [version 3.6.18] ([dateof:3.6.18]),
** SQLite source code has been stored in the ** SQLite source code has been stored in the
** <a href="http://fossil-scm.org/">Fossil configuration management ** <a href="http://www.fossil-scm.org/">Fossil configuration management
** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to ** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to
** a string which identifies a particular check-in of SQLite ** a string which identifies a particular check-in of SQLite
** within its configuration management system. ^The SQLITE_SOURCE_ID ** within its configuration management system. ^The SQLITE_SOURCE_ID
@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()]. ** [sqlite_version()] and [sqlite_source_id()].
*/ */
#define SQLITE_VERSION "3.50.0" #define SQLITE_VERSION "3.49.1"
#define SQLITE_VERSION_NUMBER 3050000 #define SQLITE_VERSION_NUMBER 3049001
#define SQLITE_SOURCE_ID "2025-05-29 14:26:00 dfc790f998f450d9c35e3ba1c8c89c17466cb559f87b0239e4aab9d34e28f742" #define SQLITE_SOURCE_ID "2025-02-18 13:38:58 873d4e274b4988d260ba8354a9718324a1c26187a4ab4c1cc0227c03d0f10e70"
/* /*
** CAPI3REF: Run-Time Library Version Numbers ** CAPI3REF: Run-Time Library Version Numbers
@ -1163,12 +1163,6 @@ struct sqlite3_io_methods {
** the value that M is to be set to. Before returning, the 32-bit signed ** the value that M is to be set to. Before returning, the 32-bit signed
** integer is overwritten with the previous value of M. ** integer is overwritten with the previous value of M.
** **
** <li>[[SQLITE_FCNTL_BLOCK_ON_CONNECT]]
** The [SQLITE_FCNTL_BLOCK_ON_CONNECT] opcode is used to configure the
** VFS to block when taking a SHARED lock to connect to a wal mode database.
** This is used to implement the functionality associated with
** SQLITE_SETLK_BLOCK_ON_CONNECT.
**
** <li>[[SQLITE_FCNTL_DATA_VERSION]] ** <li>[[SQLITE_FCNTL_DATA_VERSION]]
** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to ** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to
** a database file. The argument is a pointer to a 32-bit unsigned integer. ** a database file. The argument is a pointer to a 32-bit unsigned integer.
@ -1265,7 +1259,6 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_CKSM_FILE 41 #define SQLITE_FCNTL_CKSM_FILE 41
#define SQLITE_FCNTL_RESET_CACHE 42 #define SQLITE_FCNTL_RESET_CACHE 42
#define SQLITE_FCNTL_NULL_IO 43 #define SQLITE_FCNTL_NULL_IO 43
#define SQLITE_FCNTL_BLOCK_ON_CONNECT 44
/* deprecated names */ /* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@ -1996,16 +1989,13 @@ struct sqlite3_mem_methods {
** **
** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt> ** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt>
** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine ** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine
** the default size of [lookaside memory] on each [database connection]. ** the default size of lookaside memory on each [database connection].
** The first argument is the ** The first argument is the
** size of each lookaside buffer slot ("sz") and the second is the number of ** size of each lookaside buffer slot and the second is the number of
** slots allocated to each database connection ("cnt").)^ ** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE
** ^(SQLITE_CONFIG_LOOKASIDE sets the <i>default</i> lookaside size. ** sets the <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE]
** The [SQLITE_DBCONFIG_LOOKASIDE] option to [sqlite3_db_config()] can ** option to [sqlite3_db_config()] can be used to change the lookaside
** be used to change the lookaside configuration on individual connections.)^ ** configuration on individual connections.)^ </dd>
** The [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to change the
** default lookaside configuration at compile-time.
** </dd>
** **
** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt> ** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt>
** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is ** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is
@ -2242,50 +2232,31 @@ struct sqlite3_mem_methods {
** [[SQLITE_DBCONFIG_LOOKASIDE]] ** [[SQLITE_DBCONFIG_LOOKASIDE]]
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt> ** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
** <dd> The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the ** <dd> The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the
** configuration of the [lookaside memory allocator] within a database ** configuration of the lookaside memory allocator within a database
** connection. ** connection.
** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are <i>not</i> ** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are <i>not</i>
** in the [DBCONFIG arguments|usual format]. ** in the [DBCONFIG arguments|usual format].
** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two, ** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two,
** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE ** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE
** should have a total of five parameters. ** should have a total of five parameters.
** <ol> ** ^The first argument (the third parameter to [sqlite3_db_config()] is a
** <li><p>The first argument ("buf") is a
** pointer to a memory buffer to use for lookaside memory. ** pointer to a memory buffer to use for lookaside memory.
** The first argument may be NULL in which case SQLite will allocate the ** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb
** lookaside buffer itself using [sqlite3_malloc()]. ** may be NULL in which case SQLite will allocate the
** <li><P>The second argument ("sz") is the ** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the
** size of each lookaside buffer slot. Lookaside is disabled if "sz" ** size of each lookaside buffer slot. ^The third argument is the number of
** is less than 8. The "sz" argument should be a multiple of 8 less than ** slots. The size of the buffer in the first argument must be greater than
** 65536. If "sz" does not meet this constraint, it is reduced in size until ** or equal to the product of the second and third arguments. The buffer
** it does. ** must be aligned to an 8-byte boundary. ^If the second argument to
** <li><p>The third argument ("cnt") is the number of slots. Lookaside is disabled ** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally
** if "cnt"is less than 1. The "cnt" value will be reduced, if necessary, so ** rounded down to the next smaller multiple of 8. ^(The lookaside memory
** that the product of "sz" and "cnt" does not exceed 2,147,418,112. The "cnt"
** parameter is usually chosen so that the product of "sz" and "cnt" is less
** than 1,000,000.
** </ol>
** <p>If the "buf" argument is not NULL, then it must
** point to a memory buffer with a size that is greater than
** or equal to the product of "sz" and "cnt".
** The buffer must be aligned to an 8-byte boundary.
** The lookaside memory
** configuration for a database connection can only be changed when that ** configuration for a database connection can only be changed when that
** connection is not currently using lookaside memory, or in other words ** connection is not currently using lookaside memory, or in other words
** when the value returned by [SQLITE_DBSTATUS_LOOKASIDE_USED] is zero. ** when the "current value" returned by
** [sqlite3_db_status](D,[SQLITE_DBSTATUS_LOOKASIDE_USED],...) is zero.
** Any attempt to change the lookaside memory configuration when lookaside ** Any attempt to change the lookaside memory configuration when lookaside
** memory is in use leaves the configuration unchanged and returns ** memory is in use leaves the configuration unchanged and returns
** [SQLITE_BUSY]. ** [SQLITE_BUSY].)^</dd>
** If the "buf" argument is NULL and an attempt
** to allocate memory based on "sz" and "cnt" fails, then
** lookaside is silently disabled.
** <p>
** The [SQLITE_CONFIG_LOOKASIDE] configuration option can be used to set the
** default lookaside configuration at initialization. The
** [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to set the default lookaside
** configuration at compile-time. Typical values for lookaside are 1200 for
** "sz" and 40 to 100 for "cnt".
** </dd>
** **
** [[SQLITE_DBCONFIG_ENABLE_FKEY]] ** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt> ** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
@ -3022,44 +2993,6 @@ SQLITE_API int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);
*/ */
SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
/*
** CAPI3REF: Set the Setlk Timeout
** METHOD: sqlite3
**
** This routine is only useful in SQLITE_ENABLE_SETLK_TIMEOUT builds. If
** the VFS supports blocking locks, it sets the timeout in ms used by
** eligible locks taken on wal mode databases by the specified database
** handle. In non-SQLITE_ENABLE_SETLK_TIMEOUT builds, or if the VFS does
** not support blocking locks, this function is a no-op.
**
** Passing 0 to this function disables blocking locks altogether. Passing
** -1 to this function requests that the VFS blocks for a long time -
** indefinitely if possible. The results of passing any other negative value
** are undefined.
**
** Internally, each SQLite database handle store two timeout values - the
** busy-timeout (used for rollback mode databases, or if the VFS does not
** support blocking locks) and the setlk-timeout (used for blocking locks
** on wal-mode databases). The sqlite3_busy_timeout() method sets both
** values, this function sets only the setlk-timeout value. Therefore,
** to configure separate busy-timeout and setlk-timeout values for a single
** database handle, call sqlite3_busy_timeout() followed by this function.
**
** Whenever the number of connections to a wal mode database falls from
** 1 to 0, the last connection takes an exclusive lock on the database,
** then checkpoints and deletes the wal file. While it is doing this, any
** new connection that tries to read from the database fails with an
** SQLITE_BUSY error. Or, if the SQLITE_SETLK_BLOCK_ON_CONNECT flag is
** passed to this API, the new connection blocks until the exclusive lock
** has been released.
*/
SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags);
/*
** CAPI3REF: Flags for sqlite3_setlk_timeout()
*/
#define SQLITE_SETLK_BLOCK_ON_CONNECT 0x01
/* /*
** CAPI3REF: Convenience Routines For Running Queries ** CAPI3REF: Convenience Routines For Running Queries
** METHOD: sqlite3 ** METHOD: sqlite3
@ -5175,7 +5108,7 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
** other than [SQLITE_ROW] before any subsequent invocation of ** other than [SQLITE_ROW] before any subsequent invocation of
** sqlite3_step(). Failure to reset the prepared statement using ** sqlite3_step(). Failure to reset the prepared statement using
** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from ** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from
** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1]), ** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1],
** sqlite3_step() began ** sqlite3_step() began
** calling [sqlite3_reset()] automatically in this circumstance rather ** calling [sqlite3_reset()] automatically in this circumstance rather
** than returning [SQLITE_MISUSE]. This is not considered a compatibility ** than returning [SQLITE_MISUSE]. This is not considered a compatibility
@ -7071,8 +7004,6 @@ SQLITE_API int sqlite3_autovacuum_pages(
** **
** ^The second argument is a pointer to the function to invoke when a ** ^The second argument is a pointer to the function to invoke when a
** row is updated, inserted or deleted in a rowid table. ** row is updated, inserted or deleted in a rowid table.
** ^The update hook is disabled by invoking sqlite3_update_hook()
** with a NULL pointer as the second parameter.
** ^The first argument to the callback is a copy of the third argument ** ^The first argument to the callback is a copy of the third argument
** to sqlite3_update_hook(). ** to sqlite3_update_hook().
** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE], ** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE],
@ -11555,10 +11486,9 @@ SQLITE_API void sqlite3session_table_filter(
** is inserted while a session object is enabled, then later deleted while ** is inserted while a session object is enabled, then later deleted while
** the same session object is disabled, no INSERT record will appear in the ** the same session object is disabled, no INSERT record will appear in the
** changeset, even though the delete took place while the session was disabled. ** changeset, even though the delete took place while the session was disabled.
** Or, if one field of a row is updated while a session is enabled, and ** Or, if one field of a row is updated while a session is disabled, and
** then another field of the same row is updated while the session is disabled, ** another field of the same row is updated while the session is enabled, the
** the resulting changeset will contain an UPDATE change that updates both ** resulting changeset will contain an UPDATE change that updates both fields.
** fields.
*/ */
SQLITE_API int sqlite3session_changeset( SQLITE_API int sqlite3session_changeset(
sqlite3_session *pSession, /* Session object */ sqlite3_session *pSession, /* Session object */
@ -11630,9 +11560,8 @@ SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession
** database zFrom the contents of the two compatible tables would be ** database zFrom the contents of the two compatible tables would be
** identical. ** identical.
** **
** Unless the call to this function is a no-op as described above, it is an ** It an error if database zFrom does not exist or does not contain the
** error if database zFrom does not exist or does not contain the required ** required compatible table.
** compatible table.
** **
** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
@ -11767,7 +11696,7 @@ SQLITE_API int sqlite3changeset_start_v2(
** The following flags may passed via the 4th parameter to ** The following flags may passed via the 4th parameter to
** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]: ** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]:
** **
** <dt>SQLITE_CHANGESETSTART_INVERT <dd> ** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
** Invert the changeset while iterating through it. This is equivalent to ** Invert the changeset while iterating through it. This is equivalent to
** inverting a changeset using sqlite3changeset_invert() before applying it. ** inverting a changeset using sqlite3changeset_invert() before applying it.
** It is an error to specify this flag with a patchset. ** It is an error to specify this flag with a patchset.
@ -12082,6 +12011,19 @@ SQLITE_API int sqlite3changeset_concat(
void **ppOut /* OUT: Buffer containing output changeset */ void **ppOut /* OUT: Buffer containing output changeset */
); );
/*
** CAPI3REF: Upgrade the Schema of a Changeset/Patchset
*/
SQLITE_API int sqlite3changeset_upgrade(
sqlite3 *db,
const char *zDb,
int nIn, const void *pIn, /* Input changeset */
int *pnOut, void **ppOut /* OUT: Inverse of input */
);
/* /*
** CAPI3REF: Changegroup Handle ** CAPI3REF: Changegroup Handle
** **

View File

@ -366,8 +366,6 @@ struct sqlite3_api_routines {
/* Version 3.44.0 and later */ /* Version 3.44.0 and later */
void *(*get_clientdata)(sqlite3*,const char*); void *(*get_clientdata)(sqlite3*,const char*);
int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*)); int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
/* Version 3.50.0 and later */
int (*setlk_timeout)(sqlite3*,int,int);
}; };
/* /*
@ -701,8 +699,6 @@ typedef int (*sqlite3_loadext_entry)(
/* Version 3.44.0 and later */ /* Version 3.44.0 and later */
#define sqlite3_get_clientdata sqlite3_api->get_clientdata #define sqlite3_get_clientdata sqlite3_api->get_clientdata
#define sqlite3_set_clientdata sqlite3_api->set_clientdata #define sqlite3_set_clientdata sqlite3_api->set_clientdata
/* Version 3.50.0 and later */
#define sqlite3_setlk_timeout sqlite3_api->setlk_timeout
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)

View File

@ -1,40 +0,0 @@
# Connecting with Manyverse
Communication with [Manyverse](https://www.manyver.se/) should Just Work (tm).
This document is intended as a cheat sheet for the instances where it doesn't.
If your experience differs, please share so we can make things better.
## Connecting Manyverse to the tildefriends.net room
Open the `Connections` tab. This is from the desktop app, but mobile is similar.
![Manyverse connections tab](manyverse_connections_tab.png)
Open the `Connections Panel`.
![Manyverse connections panel](manyverse_connections_panel.png)
Use the `Add Connection` button at the bottom right to open the dialog to enter
a connections string to add a new connection.
![Manyverse connections panel](manyverse_paste_invite_code.png)
Copy the tildefriends.net room code from https://www.tildefriends.net/~cory/room/.
![Tilde Friends room code](tildefriends_room_app.png)
Paste.
On mobile especially, make sure the full text is pasted without modification.
![Manyverse invite code](manyverse_code.png)
Click `Done`, and you should be connected successfully. tildefriends.net is
all things: a room, a pub, and a client, so you should be able to start replicating
immediately as well as find other similarly connected people with whom to establish
further connections.
When logged into tildefriends.net, active connections it sees can be found on
the `Connections` tab: https://www.tildefriends.net/~core/ssb/#connections,
which may indicate errors if you find yourself disconnecting.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

View File

@ -4,8 +4,7 @@
- run the tests - run the tests
- format + prettier - format + prettier
- update metadata/en-US/changelogs - update metadata/en-US/changelogs
- git tag v1.2.3 - git tag
- git tag -f latest_release
- push - push
- make a release on gitea - make a release on gitea
- upload the artifacts - upload the artifacts
@ -14,7 +13,7 @@
- upload to Apple with dist-ios on macos - upload to Apple with dist-ios on macos
- nix - nix
- june and december: update release version - june and december: update release version
- run `nix --extra-experimental-features nix-command --extra-experimental-features flakes flake update` - run `nix flake update`
- comment out the hash in default.nix - comment out the hash in default.nix
- update the version - update the version
- run `nix-build` - run `nix-build`

View File

@ -1,18 +0,0 @@
# Upgrading
Tilde Friends can be upgraded simply by running a new executable against an
existing database.
Tilde Friends writes all data to a `db.sqlite` file, either in
`~/.local/share/tildefriends/` or in the working directory where it is run,
depending on the platform and whether each one already exists. Run with
`tildefriends run -d DB_PATH` to specify the path to the database explicitly.
This file can be copied and moved across machines as needed like any [sqlite3
database](https://www.sqlite.org/onefile.html).
Schema changes and compatibility breaks have been rare, by design. In general,
upgrading is not expected to require any manual intervention and likely does
not involve any automatic migration, either. Downgrading is not well-supported
but will probably just work excepting rare changes that will be called out in
the changelog.

6
flake.lock generated
View File

@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1748037224, "lastModified": 1739758141,
"narHash": "sha256-92vihpZr6dwEMV6g98M5kHZIttrWahb9iRPBm1atcPk=", "narHash": "sha256-uq6A2L7o1/tR6VfmYhZWoVAwb3gTy7j4Jx30MIrH0rE=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "f09dede81861f3a83f7f06641ead34f02f37597f", "rev": "c618e28f70257593de75a7044438efc1c1fc0791",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -35,5 +35,27 @@
graphviz graphviz
]; ];
}; };
nixosModules.default = {
config,
lib,
...
}: let
# Shorter name to access final settings a
# user of hello.nix module HAS ACTUALLY SET.
# cfg is a typical convention.
cfg = config.services.tildefriends;
in {
options.services.tildefriends = {
enable = lib.mkEnableOption "Enable Tilde Friends";
};
config = lib.mkIf cfg.enable {
systemd.services.tildefriends = {
wantedBy = ["multi-user.target"];
serviceConfig.ExecStart = "${pkgs.tildefriends}/bin/tildefriends";
};
};
};
}); });
} }

View File

@ -1,5 +1,3 @@
* Allow specifying all global settings from the command-line (CLI usage changed).
* Replication improvements.
* An iOS build is on TestFlight. * An iOS build is on TestFlight.
* macOS targets are debug and release like everywhere else. * macOS targets are debug and release like everywhere else.
* Running from a subdirectory is fine. * Running from a subdirectory is fine.
@ -7,9 +5,10 @@
* Invite fixes. * Invite fixes.
* Follow/block UI fixes. * Follow/block UI fixes.
* Mobile automatically logs in. * Mobile automatically logs in.
* Updates: * Allow specifying all global settings from the command-line.
* UpdateS:
* CodeMirror * CodeMirror
* OpenSSL 3.4.1 * OpenSSL 3.4.1
* libbacktrace * libbacktrace
* speedscope 1.22.2
* sqlite 3.49.1 * sqlite 3.49.1
* speedscope 1.22.2

View File

@ -1,11 +0,0 @@
* The connections tab now shows replication progress.
* Replication performance and thoroughness improvements.
* Bind only to localhost on mobile, configurable.
* Request blobs referenced by referenced blobs, and improve performance of that
query.
* Fix file upload on iOS.
* Add rough back/forward/refresh buttons on iOS.
* Other crash fixes and performance improvements.
* Updates:
* CodeMirror
* w3.css

View File

@ -1,12 +0,0 @@
* Faster loads.
* Replication fixes.
* Shutdown fixes.
* Consolidated message actions into a % menu.
* Fixed and tested handling of user permissions.
* Add a very work in progress "web" app.
* Updates:
* CodeMirror
* Lit 3.3.0
* OpenSSL 3.5.0
* c-ares 1.34.5
* libbacktrace

View File

@ -1,13 +0,0 @@
* Faster loads.
* Minor UI tweaks.
* Added an intro app as part of the initial flow for first-time users.
* Fixed more shutdown issues.
* Fixed a longstanding potential database issue.
* Added a blob export command.
* Refresh blob wants for blobs that are requested over the web.
* Updates:
* CodeMirror
* QuickJS 2025-04-26
* libuv 1.51.0
* sqlite 3.49.2
* w3.css 5.02

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

@ -1 +1 @@
A Secure Scuttlebutt decentralized social network client A tool for making and sharing

6
package-lock.json generated
View File

@ -11,9 +11,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "3.5.3", "version": "3.5.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==",
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
}, },

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unprompted.tildefriends" package="com.unprompted.tildefriends"
android:versionCode="38" android:versionCode="33"
android:versionName="0.0.32-wip"> android:versionName="0.0.28-wip">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<application <application

View File

@ -347,6 +347,7 @@ public class TildeFriendsActivity extends Activity {
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
e.printStackTrace(); e.printStackTrace();
} catch (java.io.FileNotFoundException e) { } catch (java.io.FileNotFoundException e) {
Log.w("tildefriends", "Port file does not yet exist.");
} catch (java.io.IOException e) { } catch (java.io.IOException e) {
e.printStackTrace(); e.printStackTrace();
} }

View File

@ -1,19 +0,0 @@
#include "api.js.h"
#include "log.h"
#include <quickjs.h>
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
return JS_UNDEFINED;
}
void tf_api_register(JSContext* context)
{
JSValue global = JS_GetGlobalObject(context);
JSValue ssb = JS_GetPropertyStr(context, global, "ssb");
JS_SetPropertyStr(context, ssb, "registerImports", JS_NewCFunction(context, _tf_api_register_imports, "registerImports", 2));
JS_FreeValue(context, ssb);
JS_FreeValue(context, global);
}

View File

@ -1,18 +0,0 @@
#pragma once
/**
** \defgroup api_js JS API
** Functions that are ultimately exposed to apps.
** @{
*/
/** A JS context. */
typedef struct JSContext JSContext;
/**
** Register JS API functions.
** @param context The JS context.
*/
void tf_api_register(JSContext* context);
/** @} */

View File

@ -107,7 +107,7 @@ static void _database_get_work(tf_ssb_t* ssb, void* user_data)
database_get_t* work = user_data; database_get_t* work = user_data;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare_v2(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW) sqlite3_step(statement) == SQLITE_ROW)
@ -185,7 +185,7 @@ static void _database_set_work(tf_ssb_t* ssb, void* user_data)
database_set_t* work = user_data; database_set_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, ?2, ?3)", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, ?2, ?3)", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE) sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE)
@ -265,7 +265,7 @@ static void _database_exchange_work(tf_ssb_t* ssb, void* user_data)
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (!work->expected) if (!work->expected)
{ {
if (sqlite3_prepare_v2(db, "INSERT INTO properties (id, key, value) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare(db, "INSERT INTO properties (id, key, value) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE) sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE)
@ -275,7 +275,7 @@ static void _database_exchange_work(tf_ssb_t* ssb, void* user_data)
sqlite3_finalize(statement); sqlite3_finalize(statement);
} }
} }
else if (sqlite3_prepare_v2(db, "UPDATE properties SET value = ?1 WHERE id = ?2 AND key = ?3 AND value = ?4", -1, &statement, NULL) == SQLITE_OK) else if (sqlite3_prepare(db, "UPDATE properties SET value = ?1 WHERE id = ?2 AND key = ?3 AND value = ?4", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->id, -1, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->id, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->key, work->key_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 3, work->key, work->key_length, NULL) == SQLITE_OK &&
@ -339,7 +339,7 @@ static void _database_remove_work(tf_ssb_t* ssb, void* user_data)
database_remove_t* work = user_data; database_remove_t* work = user_data;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (sqlite3_prepare_v2(db, "DELETE FROM properties WHERE id = ?1 AND key = ?2", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare(db, "DELETE FROM properties WHERE id = ?1 AND key = ?2", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_OK) sqlite3_step(statement) == SQLITE_OK)
@ -401,7 +401,7 @@ static void _database_get_all_work(tf_ssb_t* ssb, void* user_data)
database_get_all_t* work = user_data; database_get_all_t* work = user_data;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare_v2(db, "SELECT key FROM properties WHERE id = ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare(db, "SELECT key FROM properties WHERE id = ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK)
{ {
@ -487,7 +487,7 @@ static void _database_get_like_work(tf_ssb_t* ssb, void* user_data)
database_get_like_t* work = user_data; database_get_like_t* work = user_data;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare_v2(db, "SELECT key, value FROM properties WHERE id = ? AND KEY LIKE ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare(db, "SELECT key, value FROM properties WHERE id = ? AND KEY LIKE ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->pattern, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->pattern, -1, NULL) == SQLITE_OK)
{ {
@ -566,7 +566,7 @@ static void _databases_list_work(tf_ssb_t* ssb, void* user_data)
databases_list_t* work = user_data; databases_list_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare_v2(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->pattern, -1, NULL) == SQLITE_OK) if (sqlite3_bind_text(statement, 1, work->pattern, -1, NULL) == SQLITE_OK)
{ {

View File

@ -84,7 +84,6 @@ typedef struct _tf_http_listener_t
typedef struct _tf_http_t typedef struct _tf_http_t
{ {
bool is_shutting_down; bool is_shutting_down;
bool is_in_destroy;
tf_http_listener_t** listeners; tf_http_listener_t** listeners;
int listeners_count; int listeners_count;
@ -396,7 +395,7 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d
if (fin) if (fin)
{ {
if (connection->request && connection->request->on_message) if (connection->request->on_message)
{ {
tf_trace_begin(connection->http->trace, connection->trace_name ? connection->trace_name : "websocket"); tf_trace_begin(connection->http->trace, connection->trace_name ? connection->trace_name : "websocket");
connection->request->on_message(connection->request, connection->fragment_length ? connection->fragment_op_code : op_code, connection->request->on_message(connection->request, connection->fragment_length ? connection->fragment_op_code : op_code,
@ -698,7 +697,7 @@ static void _http_on_connection(uv_stream_t* stream, int status)
http->connections[http->connections_count++] = connection; http->connections[http->connections_count++] = connection;
} }
int tf_http_listen(tf_http_t* http, int port, bool local_only, tf_tls_context_t* tls, tf_http_cleanup_t* cleanup, void* user_data) int tf_http_listen(tf_http_t* http, int port, tf_tls_context_t* tls, tf_http_cleanup_t* cleanup, void* user_data)
{ {
tf_http_listener_t* listener = tf_malloc(sizeof(tf_http_listener_t)); tf_http_listener_t* listener = tf_malloc(sizeof(tf_http_listener_t));
*listener = (tf_http_listener_t) { *listener = (tf_http_listener_t) {
@ -716,29 +715,25 @@ int tf_http_listen(tf_http_t* http, int port, bool local_only, tf_tls_context_t*
if (r == 0) if (r == 0)
{ {
bool use_ipv6 = !tf_util_is_mobile();
#if defined(__HAIKU__) #if defined(__HAIKU__)
/* /*
** Binding to IPv6 here fails with an odd error, and the socket ** Binding to IPv6 here fails with an odd error, and the socket
** becomes unusable. Since we probably want localhost only ** becomes unusable. Since we probably want localhost only
** on this single-user OS, let's just assume IPv4. ** on this single-user OS, let's just assume IPv4.
*/ */
use_ipv6 = false; struct sockaddr_in addr = {
#endif
struct sockaddr_in addr4 = {
.sin_family = AF_INET, .sin_family = AF_INET,
.sin_addr = { .s_addr = local_only ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY) }, .sin_addr = { .s_addr = INADDR_ANY },
.sin_port = ntohs(port), .sin_port = ntohs(port),
}; };
struct sockaddr_in6 addr6 = { #else
struct sockaddr_in6 addr = {
.sin6_family = AF_INET6, .sin6_family = AF_INET6,
.sin6_addr = local_only ? (struct in6_addr)IN6ADDR_LOOPBACK_INIT : (struct in6_addr)IN6ADDR_ANY_INIT, .sin6_addr = IN6ADDR_ANY_INIT,
.sin6_port = ntohs(port), .sin6_port = ntohs(port),
}; };
struct sockaddr* addr = use_ipv6 ? (struct sockaddr*)&addr6 : (struct sockaddr*)&addr4; #endif
r = uv_tcp_bind(&listener->tcp, addr, 0); r = uv_tcp_bind(&listener->tcp, (struct sockaddr*)&addr, 0);
if (r) if (r)
{ {
tf_printf("%s:%d: uv_tcp_bind: %s\n", __FILE__, __LINE__, uv_strerror(r)); tf_printf("%s:%d: uv_tcp_bind: %s\n", __FILE__, __LINE__, uv_strerror(r));
@ -785,41 +780,13 @@ void tf_http_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_
static void _http_free_listener_on_close(uv_handle_t* handle) static void _http_free_listener_on_close(uv_handle_t* handle)
{ {
tf_http_listener_t* listener = handle->data; tf_http_listener_t* listener = handle->data;
tf_http_t* http = listener->http;
for (int i = 0; i < http->listeners_count; i++)
{
if (http->listeners[i] == listener)
{
http->listeners[i] = http->listeners[http->listeners_count - 1];
http->listeners_count--;
break;
}
}
handle->data = NULL; handle->data = NULL;
tf_free(listener); tf_free(listener);
if (!http->listeners_count)
{
tf_free(http->listeners);
http->listeners = NULL;
tf_http_destroy(http);
}
} }
void tf_http_destroy(tf_http_t* http) void tf_http_destroy(tf_http_t* http)
{ {
if (http->is_in_destroy)
{
return;
}
if (!http->is_shutting_down)
{
tf_printf("tf_http_destroy\n");
}
http->is_shutting_down = true; http->is_shutting_down = true;
http->is_in_destroy = true;
for (int i = 0; i < http->connections_count; i++) for (int i = 0; i < http->connections_count; i++)
{ {
@ -834,10 +801,6 @@ void tf_http_destroy(tf_http_t* http)
listener->cleanup(listener->user_data); listener->cleanup(listener->user_data);
listener->cleanup = NULL; listener->cleanup = NULL;
} }
if (listener->tcp.data && !uv_is_closing((uv_handle_t*)&listener->tcp))
{
uv_close((uv_handle_t*)&listener->tcp, _http_free_listener_on_close);
}
} }
for (int i = 0; i < http->handlers_count; i++) for (int i = 0; i < http->handlers_count; i++)
@ -855,11 +818,20 @@ void tf_http_destroy(tf_http_t* http)
http->user_data = NULL; http->user_data = NULL;
} }
if (http->connections_count == 0 && http->listeners_count == 0) if (http->connections_count == 0)
{ {
tf_free(http->connections); tf_free(http->connections);
http->connections = NULL; http->connections = NULL;
for (int i = 0; i < http->listeners_count; i++)
{
tf_http_listener_t* listener = http->listeners[i];
uv_close((uv_handle_t*)&listener->tcp, _http_free_listener_on_close);
}
tf_free(http->listeners);
http->listeners = NULL;
http->listeners_count = 0;
for (int i = 0; i < http->handlers_count; i++) for (int i = 0; i < http->handlers_count; i++)
{ {
if (http->handlers[i].pattern) if (http->handlers[i].pattern)
@ -873,10 +845,6 @@ void tf_http_destroy(tf_http_t* http)
tf_free(http); tf_free(http);
} }
else
{
http->is_in_destroy = false;
}
} }
const char* tf_http_status_text(int status) const char* tf_http_status_text(int status)
@ -995,42 +963,9 @@ static void _http_write(tf_http_connection_t* connection, const void* data, size
} }
} }
void tf_http_request_websocket_send(tf_http_request_t* request, int op_code, const void* data, size_t size) void tf_http_request_send(tf_http_request_t* request, const void* data, size_t size)
{ {
uint8_t* copy = tf_malloc(size + 16); _http_write(request->connection, data, size);
bool fin = true;
size_t header = 1;
copy[0] = (fin ? (1 << 7) : 0) | (op_code & 0xf);
if (size < 126)
{
copy[1] = size;
header += 1;
}
else if (size < (1 << 16))
{
copy[1] = 126;
copy[2] = (size >> 8) & 0xff;
copy[3] = (size >> 0) & 0xff;
header += 3;
}
else
{
uint32_t high = ((uint64_t)size >> 32) & 0xffffffff;
uint32_t low = (size >> 0) & 0xffffffff;
copy[1] = 127;
copy[2] = (high >> 24) & 0xff;
copy[3] = (high >> 16) & 0xff;
copy[4] = (high >> 8) & 0xff;
copy[5] = (high >> 0) & 0xff;
copy[6] = (low >> 24) & 0xff;
copy[7] = (low >> 16) & 0xff;
copy[8] = (low >> 8) & 0xff;
copy[9] = (low >> 0) & 0xff;
header += 9;
}
memcpy(copy + header, data, size);
_http_write(request->connection, copy, header + size);
tf_free(copy);
} }
void tf_http_respond(tf_http_request_t* request, int status, const char** headers, int headers_count, const void* body, size_t content_length) void tf_http_respond(tf_http_request_t* request, int status, const char** headers, int headers_count, const void* body, size_t content_length)

View File

@ -116,13 +116,12 @@ void tf_http_set_trace(tf_http_t* http, tf_trace_t* trace);
** times to listen on multiple ports. ** times to listen on multiple ports.
** @param http The HTTP instance. ** @param http The HTTP instance.
** @param port The port on which to listen, or 0 to assign a free port. ** @param port The port on which to listen, or 0 to assign a free port.
** @param local_only Only access connections on localhost, otherwise any address.
** @param tls An optional TLS context to use for HTTPS requests. ** @param tls An optional TLS context to use for HTTPS requests.
** @param cleanup A function called when the HTTP instance is being cleaned up. ** @param cleanup A function called when the HTTP instance is being cleaned up.
** @param user_data User data passed to the cleanup callback. ** @param user_data User data passed to the cleanup callback.
** @return The port number on which the HTTP instance is now listening. ** @return The port number on which the HTTP instance is now listening.
*/ */
int tf_http_listen(tf_http_t* http, int port, bool local_only, tf_tls_context_t* tls, tf_http_cleanup_t* cleanup, void* user_data); int tf_http_listen(tf_http_t* http, int port, tf_tls_context_t* tls, tf_http_cleanup_t* cleanup, void* user_data);
/** /**
** Add an HTTP request handler. ** Add an HTTP request handler.
@ -210,11 +209,10 @@ const char* tf_http_get_cookie(const char* cookie_header, const char* name);
** Send a websocket message. ** Send a websocket message.
** @param request The HTTP request which was previously updated to a websocket ** @param request The HTTP request which was previously updated to a websocket
** session with tf_http_request_websocket_upgrade(). ** session with tf_http_request_websocket_upgrade().
** @param op_code Websocket op code.
** @param data The message data. ** @param data The message data.
** @param size The size of data. ** @param size The size of data.
*/ */
void tf_http_request_websocket_send(tf_http_request_t* request, int op_code, const void* data, size_t size); void tf_http_request_send(tf_http_request_t* request, const void* data, size_t size);
/** /**
** Upgrade an HTTP request to a websocket session. ** Upgrade an HTTP request to a websocket session.

View File

@ -5,7 +5,6 @@
#include "log.h" #include "log.h"
#include "mem.h" #include "mem.h"
#include "ssb.db.h" #include "ssb.db.h"
#include "ssb.ebt.h"
#include "ssb.h" #include "ssb.h"
#include "task.h" #include "task.h"
#include "tls.h" #include "tls.h"
@ -47,6 +46,7 @@ static const char* _make_set_session_cookie_header(tf_http_request_t* request, c
const char** _form_data_decode(const char* data, int length); const char** _form_data_decode(const char* data, int length);
const char* _form_data_get(const char** form_data, const char* key); const char* _form_data_get(const char** form_data, const char* key);
static JSClassID _httpd_class_id;
static JSClassID _httpd_request_class_id; static JSClassID _httpd_request_class_id;
typedef struct _http_user_data_t typedef struct _http_user_data_t
@ -152,9 +152,44 @@ static JSValue _httpd_response_send(JSContext* context, JSValueConst this_val, i
tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id); tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id);
int opcode = 0x1; int opcode = 0x1;
JS_ToInt32(context, &opcode, argv[1]); JS_ToInt32(context, &opcode, argv[1]);
size_t length = 0; uint64_t length = 0;
const char* message = JS_ToCStringLen(context, &length, argv[0]); size_t length_size = 0;
tf_http_request_websocket_send(request, opcode, message, length); const char* message = JS_ToCStringLen(context, &length_size, argv[0]);
length = length_size;
uint8_t* copy = tf_malloc(length + 16);
bool fin = true;
size_t header = 1;
copy[0] = (fin ? (1 << 7) : 0) | (opcode & 0xf);
if (length < 126)
{
copy[1] = length;
header += 1;
}
else if (length < (1 << 16))
{
copy[1] = 126;
copy[2] = (length >> 8) & 0xff;
copy[3] = (length >> 0) & 0xff;
header += 3;
}
else
{
uint32_t high = (length >> 32) & 0xffffffff;
uint32_t low = (length >> 0) & 0xffffffff;
copy[1] = 127;
copy[2] = (high >> 24) & 0xff;
copy[3] = (high >> 16) & 0xff;
copy[4] = (high >> 8) & 0xff;
copy[5] = (high >> 0) & 0xff;
copy[6] = (low >> 24) & 0xff;
copy[7] = (low >> 16) & 0xff;
copy[8] = (low >> 8) & 0xff;
copy[9] = (low >> 0) & 0xff;
header += 9;
}
memcpy(copy + header, message, length);
tf_http_request_send(request, copy, header + length);
tf_free(copy);
JS_FreeCString(context, message); JS_FreeCString(context, message);
return JS_UNDEFINED; return JS_UNDEFINED;
} }
@ -537,6 +572,12 @@ static const char* _ext_to_content_type(const char* ext, bool use_fallback)
return use_fallback ? "application/binary" : NULL; return use_fallback ? "application/binary" : NULL;
} }
static void _httpd_finalizer(JSRuntime* runtime, JSValue value)
{
tf_http_t* http = JS_GetOpaque(value, _httpd_class_id);
tf_http_destroy(http);
}
static void _httpd_request_finalizer(JSRuntime* runtime, JSValue value) static void _httpd_request_finalizer(JSRuntime* runtime, JSValue value)
{ {
tf_http_request_t* request = JS_GetOpaque(value, _httpd_request_class_id); tf_http_request_t* request = JS_GetOpaque(value, _httpd_request_class_id);
@ -563,50 +604,6 @@ static void _httpd_endpoint_trace(tf_http_request_t* request)
tf_free(json); tf_free(json);
} }
static void _httpd_endpoint_ebt(tf_http_request_t* request)
{
if (_httpd_redirect(request))
{
return;
}
tf_task_t* task = request->user_data;
tf_ssb_t* ssb = tf_task_get_ssb(task);
JSContext* context = tf_ssb_get_context(ssb);
JSValue object = JS_NewObject(context);
tf_ssb_connection_t* connections[256];
int connection_count = tf_ssb_get_connections(ssb, connections, tf_countof(connections));
for (int i = 0; i < connection_count; i++)
{
char id[k_id_base64_len];
tf_ssb_connection_get_id(connections[i], id, sizeof(id));
char key[256];
JSValue clock = JS_NewObject(context);
snprintf(key, sizeof(key), "%d:%s", i, id);
tf_ssb_ebt_t* ebt = tf_ssb_connection_get_ebt(connections[i]);
tf_ssb_ebt_debug_clock(ebt, context, clock);
JS_SetPropertyStr(context, object, key, clock);
}
JSValue json_value = JS_JSONStringify(context, object, JS_NULL, JS_NewInt32(context, 2));
const char* json = JS_ToCString(context, json_value);
JS_FreeValue(context, json_value);
const char* headers[] = {
"Content-Type",
"application/json; charset=utf-8",
"Access-Control-Allow-Origin",
"*",
};
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, json, json ? strlen(json) : 0);
JS_FreeCString(context, json);
JS_FreeValue(context, object);
}
static void _httpd_endpoint_mem(tf_http_request_t* request) static void _httpd_endpoint_mem(tf_http_request_t* request)
{ {
if (_httpd_redirect(request)) if (_httpd_redirect(request))
@ -1116,7 +1113,6 @@ typedef struct _view_t
void* data; void* data;
size_t size; size_t size;
char etag[256]; char etag[256];
char notify_want_blob_id[k_blob_id_len];
bool not_modified; bool not_modified;
} view_t; } view_t;
@ -1171,13 +1167,7 @@ static void _httpd_endpoint_view_work(tf_ssb_t* ssb, void* user_data)
} }
else else
{ {
if (!tf_ssb_db_blob_get(ssb, blob_id, (uint8_t**)&view->data, &view->size)) tf_ssb_db_blob_get(ssb, blob_id, (uint8_t**)&view->data, &view->size);
{
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
tf_ssb_db_add_blob_wants(db, blob_id);
tf_ssb_release_db_writer(ssb, db);
snprintf(view->notify_want_blob_id, sizeof(view->notify_want_blob_id), "%s", blob_id);
}
} }
} }
} }
@ -1220,12 +1210,6 @@ static void _httpd_endpoint_view_after_work(tf_ssb_t* ssb, int status, void* use
const char* k_payload = tf_http_status_text(404); const char* k_payload = tf_http_status_text(404);
tf_http_respond(view->request, 404, NULL, 0, k_payload, strlen(k_payload)); tf_http_respond(view->request, 404, NULL, 0, k_payload, strlen(k_payload));
} }
if (*view->notify_want_blob_id)
{
tf_ssb_notify_blob_want_added(ssb, view->notify_want_blob_id);
}
tf_free(view->form_data); tf_free(view->form_data);
tf_http_request_unref(view->request); tf_http_request_unref(view->request);
tf_free(view); tf_free(view);
@ -1330,11 +1314,6 @@ static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
snprintf(save->blob_id, sizeof(save->blob_id), "%s", blob_id); snprintf(save->blob_id, sizeof(save->blob_id), "%s", blob_id);
save->response = 200; save->response = 200;
} }
else
{
tf_printf("Blob store or property set failed.\n");
save->response = 500;
}
JS_FreeCString(context, new_app_str); JS_FreeCString(context, new_app_str);
JS_FreeValue(context, new_app_json); JS_FreeValue(context, new_app_json);
@ -1351,7 +1330,7 @@ static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
} }
else else
{ {
save->response = 403; save->response = 401;
} }
tf_free(user_app); tf_free(user_app);
} }
@ -1363,21 +1342,12 @@ static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
snprintf(save->blob_id, sizeof(save->blob_id), "%s", blob_id); snprintf(save->blob_id, sizeof(save->blob_id), "%s", blob_id);
save->response = 200; save->response = 200;
} }
else
{
tf_printf("Blob store failed.\n");
save->response = 500;
}
} }
else else
{ {
save->response = 400; save->response = 400;
} }
} }
else
{
save->response = 401;
}
tf_free((void*)session); tf_free((void*)session);
JS_FreeCString(context, user_string); JS_FreeCString(context, user_string);
@ -1499,9 +1469,17 @@ static void _httpd_endpoint_delete(tf_http_request_t* request)
static void _httpd_endpoint_root_callback(const char* path, void* user_data) static void _httpd_endpoint_root_callback(const char* path, void* user_data)
{ {
tf_http_request_t* request = user_data; tf_http_request_t* request = user_data;
const char* host = tf_http_request_get_header(request, "x-forwarded-host");
if (!host)
{
host = tf_http_request_get_header(request, "host");
}
char url[1024];
snprintf(url, sizeof(url), "%s%s%s", request->is_tls ? "https://" : "http://", host, path ? path : "/~core/apps/");
const char* headers[] = { const char* headers[] = {
"Location", "Location",
path ? path : "/~core/apps/", url,
}; };
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0); tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
tf_http_request_unref(request); tf_http_request_unref(request);
@ -2360,7 +2338,16 @@ static const char* _httpd_read_file(tf_task_t* task, const char* path)
void tf_httpd_register(JSContext* context) void tf_httpd_register(JSContext* context)
{ {
JS_NewClassID(&_httpd_class_id);
JS_NewClassID(&_httpd_request_class_id); JS_NewClassID(&_httpd_request_class_id);
JSClassDef httpd_def = {
.class_name = "Httpd",
.finalizer = &_httpd_finalizer,
};
if (JS_NewClass(JS_GetRuntime(context), _httpd_class_id, &httpd_def) != 0)
{
fprintf(stderr, "Failed to register Httpd.\n");
}
JSClassDef request_def = { JSClassDef request_def = {
.class_name = "Request", .class_name = "Request",
.finalizer = &_httpd_request_finalizer, .finalizer = &_httpd_request_finalizer,
@ -2371,29 +2358,22 @@ void tf_httpd_register(JSContext* context)
} }
JSValue global = JS_GetGlobalObject(context); JSValue global = JS_GetGlobalObject(context);
JSValue httpd = JS_NewObject(context); JSValue httpd = JS_NewObjectClass(context, _httpd_class_id);
JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1));
JS_SetPropertyStr(context, global, "httpd", httpd);
JS_FreeValue(context, global);
}
tf_http_t* tf_httpd_create(JSContext* context)
{
tf_task_t* task = tf_task_get(context); tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task); tf_ssb_t* ssb = tf_task_get_ssb(task);
uv_loop_t* loop = tf_task_get_loop(task); uv_loop_t* loop = tf_task_get_loop(task);
tf_http_t* http = tf_http_create(loop); tf_http_t* http = tf_http_create(loop);
tf_http_set_trace(http, tf_task_get_trace(task)); tf_http_set_trace(http, tf_task_get_trace(task));
JS_SetOpaque(httpd, http);
int64_t http_port = 0; int64_t http_port = 0;
int64_t https_port = 0; int64_t https_port = 0;
char out_http_port_file[512] = ""; char out_http_port_file[512] = "";
bool local_only = false;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
tf_ssb_db_get_global_setting_int64(db, "http_port", &http_port); tf_ssb_db_get_global_setting_int64(db, "http_port", &http_port);
tf_ssb_db_get_global_setting_int64(db, "https_port", &https_port); tf_ssb_db_get_global_setting_int64(db, "https_port", &https_port);
tf_ssb_db_get_global_setting_string(db, "out_http_port_file", out_http_port_file, sizeof(out_http_port_file)); tf_ssb_db_get_global_setting_string(db, "out_http_port_file", out_http_port_file, sizeof(out_http_port_file));
tf_ssb_db_get_global_setting_bool(db, "http_local_only", &local_only);
tf_ssb_release_db_reader(ssb, db); tf_ssb_release_db_reader(ssb, db);
if (https_port) if (https_port)
@ -2440,7 +2420,6 @@ tf_http_t* tf_httpd_create(JSContext* context)
tf_http_add_handler(http, "/hitches", _httpd_endpoint_hitches, NULL, task); tf_http_add_handler(http, "/hitches", _httpd_endpoint_hitches, NULL, task);
tf_http_add_handler(http, "/mem", _httpd_endpoint_mem, NULL, task); tf_http_add_handler(http, "/mem", _httpd_endpoint_mem, NULL, task);
tf_http_add_handler(http, "/trace", _httpd_endpoint_trace, NULL, task); tf_http_add_handler(http, "/trace", _httpd_endpoint_trace, NULL, task);
tf_http_add_handler(http, "/ebt", _httpd_endpoint_ebt, NULL, task);
tf_http_add_handler(http, "/login/logout", _httpd_endpoint_logout, NULL, task); tf_http_add_handler(http, "/login/logout", _httpd_endpoint_logout, NULL, task);
tf_http_add_handler(http, "/login/auto", _httpd_endpoint_login_auto, NULL, task); tf_http_add_handler(http, "/login/auto", _httpd_endpoint_login_auto, NULL, task);
@ -2448,11 +2427,15 @@ tf_http_t* tf_httpd_create(JSContext* context)
tf_http_add_handler(http, "/app/socket", _httpd_endpoint_app_socket, NULL, task); tf_http_add_handler(http, "/app/socket", _httpd_endpoint_app_socket, NULL, task);
JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1));
JS_SetPropertyStr(context, global, "httpd", httpd);
JS_FreeValue(context, global);
if (http_port > 0 || *out_http_port_file) if (http_port > 0 || *out_http_port_file)
{ {
httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t)); httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t));
*listener = (httpd_listener_t) { 0 }; *listener = (httpd_listener_t) { 0 };
int assigned_port = tf_http_listen(http, http_port, local_only, NULL, _httpd_listener_cleanup, listener); int assigned_port = tf_http_listen(http, http_port, NULL, _httpd_listener_cleanup, listener);
tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "http://127.0.0.1:%d/" RESET ".\n", assigned_port); tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "http://127.0.0.1:%d/" RESET ".\n", assigned_port);
if (*out_http_port_file) if (*out_http_port_file)
@ -2485,17 +2468,11 @@ tf_http_t* tf_httpd_create(JSContext* context)
tf_tls_context_set_private_key(tls, private_key); tf_tls_context_set_private_key(tls, private_key);
httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t)); httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t));
*listener = (httpd_listener_t) { .tls = tls }; *listener = (httpd_listener_t) { .tls = tls };
int assigned_port = tf_http_listen(http, https_port, local_only, tls, _httpd_listener_cleanup, listener); int assigned_port = tf_http_listen(http, https_port, tls, _httpd_listener_cleanup, listener);
tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "https://127.0.0.1:%d/" RESET ".\n", assigned_port); tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "https://127.0.0.1:%d/" RESET ".\n", assigned_port);
} }
tf_free((char*)certificate); tf_free((char*)certificate);
tf_free((char*)private_key); tf_free((char*)private_key);
} }
} }
return http;
}
void tf_httpd_destroy(tf_http_t* http)
{
tf_http_destroy(http);
} }

View File

@ -13,11 +13,6 @@
#include "quickjs.h" #include "quickjs.h"
/**
** An HTTP server instance.
*/
typedef struct _tf_http_t tf_http_t;
/** /**
** Register the HTTP script interface. Also registers a number of built-in ** Register the HTTP script interface. Also registers a number of built-in
** request handlers. An ongoing project is to move the JS request handlers ** request handlers. An ongoing project is to move the JS request handlers
@ -26,16 +21,4 @@ typedef struct _tf_http_t tf_http_t;
*/ */
void tf_httpd_register(JSContext* context); void tf_httpd_register(JSContext* context);
/**
** Create the HTTP server instance.
** @param context The JS context.
*/
tf_http_t* tf_httpd_create(JSContext* context);
/**
** Destroy the HTTP server instance.
** @param http The HTTP server instance.
*/
void tf_httpd_destroy(tf_http_t* http);
/** @} */ /** @} */

View File

@ -4,14 +4,12 @@
#import <WebKit/WKWebView.h> #import <WebKit/WKWebView.h>
#import <WebKit/WKWebViewConfiguration.h> #import <WebKit/WKWebViewConfiguration.h>
#include "log.h"
#include <libgen.h> #include <libgen.h>
#include <string.h> #include <string.h>
void tf_run_thread_start(const char* zip_path); void tf_run_thread_start(const char* zip_path);
@interface ViewController : UINavigationController <WKUIDelegate, WKNavigationDelegate> @interface ViewController : UIViewController <WKUIDelegate, WKNavigationDelegate>
@property (strong, nonatomic) WKWebView* web_view; @property (strong, nonatomic) WKWebView* web_view;
@property bool initial_load_complete; @property bool initial_load_complete;
@end @end
@ -21,67 +19,19 @@ static void _start_initial_load(WKWebView* web_view)
[web_view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:12345/login/auto"]]]; [web_view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:12345/login/auto"]]];
} }
@implementation ViewController : UINavigationController @implementation ViewController : UIViewController
- (void)viewDidLoad - (void)viewDidLoad
{ {
[super viewDidLoad]; [super viewDidLoad];
[self setToolbarHidden:false animated:false];
self.toolbar.items = @[
[[UIBarButtonItem alloc] initWithTitle:@"Back" style:UIBarButtonItemStylePlain target:self action:@selector(goBack)],
[[UIBarButtonItem alloc] initWithTitle:@"Forward" style:UIBarButtonItemStylePlain target:self action:@selector(goForward)],
[[UIBarButtonItem alloc] initWithTitle:@"Refresh" style:UIBarButtonItemStylePlain target:self action:@selector(reload)]
];
WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init]; WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];
self.web_view = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration]; self.web_view = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
self.web_view.UIDelegate = self; self.web_view.UIDelegate = self;
self.web_view.navigationDelegate = self; self.web_view.navigationDelegate = self;
self.web_view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; self.web_view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.web_view.translatesAutoresizingMaskIntoConstraints = false;
[self.view addSubview:self.web_view]; [self.view addSubview:self.web_view];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-75]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0]];
_start_initial_load(self.web_view); _start_initial_load(self.web_view);
} }
- (void)goBack
{
if (self.web_view.canGoBack)
{
[self.web_view goBack];
}
}
- (void)goForward
{
if (self.web_view.canGoForward)
{
[self.web_view goForward];
}
}
- (void)reload
{
[self.web_view reload];
}
- (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation - (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation
{ {
self.initial_load_complete = true; self.initial_load_complete = true;

View File

@ -13,13 +13,13 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.0.32</string> <string>0.0.28</string>
<key>CFBundleSupportedPlatforms</key> <key>CFBundleSupportedPlatforms</key>
<array> <array>
<string>iPhoneOS</string> <string>iPhoneOS</string>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>14</string> <string>8</string>
<key>DTPlatformName</key> <key>DTPlatformName</key>
<string>iphoneos</string> <string>iphoneos</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>

BIN
src/ios/icons/Assets.car Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Some files were not shown because too many files have changed in this diff Show More