Compare commits

..

15 Commits

Author SHA1 Message Date
3bd827a9f7 remove some of the preinstalled dependencies (code_mirror, prettier) 2024-03-22 01:57:17 +01:00
474e39c9c3 add picohttpparser as a submodule 2024-03-22 01:37:45 +01:00
0272382e0e remove picohttpparser 2024-03-22 01:35:48 +01:00
b1c8b51377 add libuv as a submodule 2024-03-22 01:24:30 +01:00
1a5acca5cf remove libuv 2024-03-22 01:22:14 +01:00
2d5417f7dc add libbacktrace as a submodule 2024-03-22 01:14:27 +01:00
2a10d26215 remove libbacktrace 2024-03-22 01:08:23 +01:00
b8e5caba0d add crypt_blowfish as a submodule 2024-03-22 01:06:13 +01:00
a4b324127a remove crypt_blowfish 2024-03-22 01:05:29 +01:00
acae3e9562 add quickjs as a submodule 2024-03-22 00:55:37 +01:00
4aa7424977 remove quickjs 2024-03-22 00:51:31 +01:00
758f177617 add libsodium as a submodule 2024-03-22 00:45:21 +01:00
9291de41d8 remove libsodium 2024-03-22 00:43:03 +01:00
3603ce5ba6 add zlib as a submodule 2024-03-22 00:32:52 +01:00
bff231751e remove zlib 2024-03-22 00:32:01 +01:00
131 changed files with 4048 additions and 6179 deletions

View File

@@ -1,5 +1,4 @@
.svn .svn
db.* db.sqlite
out/**/*.o out/**/*.o
out/**/*.d out/**/*.d
NOTES.md

View File

@@ -1,3 +0,0 @@
---
name: 'Bug Report'
---

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: Documentation
url: https://dev.tildefriends.net/cory/tildefriends/src/branch/main/docs/index.md
about: Read the documentation

View File

@@ -1,3 +0,0 @@
---
name: 'Feature Request'
---

View File

@@ -1,9 +0,0 @@
To Do List
- [ ] My changes are documented in the [documentation](https://dev.tildefriends.net/cory/tildefriends/src/branch/main/docs/index.md)
- [ ] I have tested my changes
- [ ] I agree to the contribution guidelines
- [ ] [C](https://dev.tildefriends.net/cory/tildefriends/src/branch/main/docs/guidelines/c-guidelines.md)
- [ ] [JavaScript](https://dev.tildefriends.net/cory/tildefriends/src/branch/main/docs/guidelines/javascript-guidelines.md)
- [ ] [documentation](https://dev.tildefriends.net/cory/tildefriends/src/branch/main/docs/guidelines/documentation-guidelines.md)
<!-- - [ ] I agree to the [Code of Conduct]() -->

5
.gitignore vendored
View File

@@ -8,5 +8,6 @@ out
*.swo *.swo
*.swp *.swp
.zsign_cache/ .zsign_cache/
result
NOTES.md deps/codemirror/cm6.js
deps/prettier/standalone.mjs

1
.gitmodules vendored
View File

@@ -1,6 +1,7 @@
[submodule "deps/zlib"] [submodule "deps/zlib"]
path = deps/zlib path = deps/zlib
url = https://github.com/madler/zlib.git url = https://github.com/madler/zlib.git
branch = master
[submodule "deps/libsodium"] [submodule "deps/libsodium"]
path = deps/libsodium path = deps/libsodium
url = https://github.com/jedisct1/libsodium.git url = https://github.com/jedisct1/libsodium.git

View File

@@ -1,5 +0,0 @@
default: true
MD010: false # Ignore tabs in code blocks
MD013: false # Don't wrap lines by default
MD046:
style: 'fenced' # Force fenced code blocks

View File

@@ -2,7 +2,6 @@ node_modules
src src
deps deps
.clang-format .clang-format
flake.lock
# Minified files # Minified files
**/*.min.css **/*.min.css
@@ -13,8 +12,3 @@ flake.lock
apps/ssb/tribute.esm.js apps/ssb/tribute.esm.js
apps/api/app.js apps/api/app.js
**/emojis.json **/emojis.json
# only markdownlint should deal with the documentation
docs/**/*.md
NOTES.md

View File

@@ -3,11 +3,11 @@
MAKEFLAGS += --warn-undefined-variables MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules MAKEFLAGS += --no-builtin-rules
VERSION_CODE := 19 VERSION_CODE := 17
VERSION_NUMBER := 0.0.19-wip VERSION_NUMBER := 0.0.17-wip
VERSION_NAME := Don't let your loyalty become a burden. VERSION_NAME := Please enjoy responsibly.
SQLITE_URL := https://www.sqlite.org/2024/sqlite-amalgamation-3450300.zip SQLITE_URL := https://www.sqlite.org/2024/sqlite-amalgamation-3450200.zip
LIBUV_URL := https://dist.libuv.org/dist/v1.48.0/libuv-v1.48.0.tar.gz LIBUV_URL := https://dist.libuv.org/dist/v1.48.0/libuv-v1.48.0.tar.gz
PROJECT = tildefriends PROJECT = tildefriends
@@ -17,7 +17,17 @@ UNAME_M := $(shell uname -m)
ANDROID_SDK ?= ~/Android/Sdk ANDROID_SDK ?= ~/Android/Sdk
HAVE_WIN := 0 ifeq ($(UNAME_M),x86_64)
ifneq ($(UNAME_S),Haiku)
debug: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
debug: LDFLAGS += -fsanitize=address -fsanitize=undefined
endif
endif
ifeq ($(UNAME_M),aarch64)
debug: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
debug: LDFLAGS += -fsanitize=address -fsanitize=undefined
endif
ifeq ($(UNAME_S),Darwin) ifeq ($(UNAME_S),Darwin)
BUILD_TYPES := macosdebug macosrelease iosdebug iosrelease iossimdebug iossimrelease BUILD_TYPES := macosdebug macosrelease iosdebug iosrelease iossimdebug iossimrelease
@@ -41,6 +51,7 @@ LDFLAGS += \
-lc++abi -lc++abi
HAVE_ANDROID := 0 HAVE_ANDROID := 0
HAVE_LINUX_IOS := 0 HAVE_LINUX_IOS := 0
HAVE_WIN := 0
else else
$(error Unexpected host platform $(UNAME_S).) $(error Unexpected host platform $(UNAME_S).)
endif endif
@@ -57,11 +68,11 @@ CFLAGS += \
-fno-exceptions \ -fno-exceptions \
-g -g
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-34
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.2.11394342
ANDROID_MIN_SDK_VERSION := 24 ANDROID_MIN_SDK_VERSION := 24
ANDROID_TARGET_SDK_VERSION := 34 ANDROID_TARGET_SDK_VERSION := 34
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-$(ANDROID_TARGET_SDK_VERSION)
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.2.11394342
ANDROID_ARMV7A_TARGETS := \ ANDROID_ARMV7A_TARGETS := \
out/androiddebug-armv7a/tildefriends \ out/androiddebug-armv7a/tildefriends \
@@ -211,18 +222,6 @@ $(IOS_TARGETS): LDFLAGS += -Ldeps/openssl/ios/ios64-xcrun/usr/local/lib
$(IOSSIM_TARGETS): CFLAGS += -Ideps/openssl/ios/iossimulator-xcrun/usr/local/include $(IOSSIM_TARGETS): CFLAGS += -Ideps/openssl/ios/iossimulator-xcrun/usr/local/include
$(IOSSIM_TARGETS): LDFLAGS += -Ldeps/openssl/ios/iossimulator-xcrun/usr/local/lib $(IOSSIM_TARGETS): LDFLAGS += -Ldeps/openssl/ios/iossimulator-xcrun/usr/local/lib
ifeq ($(UNAME_M),x86_64)
ifneq ($(UNAME_S),Haiku)
out/debug/tildefriends: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
out/debug/tildefriends: LDFLAGS += -fsanitize=address -fsanitize=undefined
endif
endif
ifeq ($(UNAME_M),aarch64)
out/debug/tildefriends: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
out/debug/tildefriends: LDFLAGS += -fsanitize=address -fsanitize=undefined
endif
get_objs = \ get_objs = \
$(foreach build_type,$(BUILD_TYPES),$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)))))) \ $(foreach build_type,$(BUILD_TYPES),$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)))))) \
$(foreach build_type,debug release,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_unix))))) \ $(foreach build_type,debug release,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_unix))))) \
@@ -579,7 +578,7 @@ $(MINIUNZIP_OBJS): CFLAGS += \
LDFLAGS += \ LDFLAGS += \
-pthread \ -pthread \
-lm -lm
$(LINUX_TARGETS) $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \ debug release $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
-lssl \ -lssl \
-lcrypto -lcrypto
ifneq ($(UNAME_S),Haiku) ifneq ($(UNAME_S),Haiku)
@@ -616,7 +615,7 @@ $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
unix: debug release unix: debug release
win: windebug winrelease win: windebug winrelease
all: $(BUILD_TYPES) default.nix all: $(BUILD_TYPES)
.PHONY: all win unix .PHONY: all win unix
ALL_APP_OBJS := \ ALL_APP_OBJS := \
@@ -673,10 +672,6 @@ src/android/AndroidManifest.xml : $(firstword $(MAKEFILE_LIST))
-e 's/android:targetSdkVersion="[[:digit:]]*"/android:targetSdkVersion="$(ANDROID_TARGET_SDK_VERSION)"/' \ -e 's/android:targetSdkVersion="[[:digit:]]*"/android:targetSdkVersion="$(ANDROID_TARGET_SDK_VERSION)"/' \
$@ $@
default.nix : $(firstword $(MAKEFILE_LIST))
@echo "[version] $@"
@sed -i -e 's/version = ".*";/version = "$(VERSION_NUMBER)";/' $@
# Android support. # Android support.
out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
@@ -697,7 +692,7 @@ CLASS_FILES := $(foreach src,$(JAVA_FILES),out/classes/com/unprompted/tildefrien
$(CLASS_FILES) &: $(JAVA_FILES) $(CLASS_FILES) &: $(JAVA_FILES)
@echo "[javac] $(CLASS_FILES)" @echo "[javac] $(CLASS_FILES)"
@javac --release 8 -encoding UTF-8 -Xlint:deprecation -XDuseUnsharedTable=true -classpath $(ANDROID_PLATFORM)/android.jar:$(ANDROID_BUILD_TOOLS)/core-lambda-stubs.jar -d out/classes $(JAVA_FILES) @javac --release 8 -Xlint:deprecation -classpath $(ANDROID_PLATFORM)/android.jar -d out/classes $(JAVA_FILES)
out/apk/classes.dex: $(CLASS_FILES) out/apk/classes.dex: $(CLASS_FILES)
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
@@ -733,7 +728,7 @@ out/apk/TildeFriends-arm-%.unsigned.apk:
@cp out/apk/res.apk $@.zip @cp out/apk/res.apk $@.zip
@cp out/apk/classes.dex out/apk-arm-$(BUILD_TYPE)/ @cp out/apk/classes.dex out/apk-arm-$(BUILD_TYPE)/
@cd out/apk-arm-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../ @cd out/apk-arm-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../
@zip -u $@.zip -q -9 $(RAW_FILES) @zip -u $@.zip -q $(RAW_FILES)
@$(ANDROID_BUILD_TOOLS)/zipalign -f 4 $@.zip $@ @$(ANDROID_BUILD_TOOLS)/zipalign -f 4 $@.zip $@
out/apk/TildeFriends-x86-%.unsigned.apk: out/apk/TildeFriends-x86-%.unsigned.apk:
@@ -746,7 +741,7 @@ out/apk/TildeFriends-x86-%.unsigned.apk:
@cp out/apk/res.apk $@.zip @cp out/apk/res.apk $@.zip
@cp out/apk/classes.dex out/apk-x86-$(BUILD_TYPE)/ @cp out/apk/classes.dex out/apk-x86-$(BUILD_TYPE)/
@cd out/apk-x86-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../ @cd out/apk-x86-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../
@zip -u $@.zip -q -9 $(RAW_FILES) @zip -u $@.zip -q $(RAW_FILES)
@$(ANDROID_BUILD_TOOLS)/zipalign -f 4 $@.zip $@ @$(ANDROID_BUILD_TOOLS)/zipalign -f 4 $@.zip $@
out/%.apk: out/apk/%.unsigned.apk out/%.apk: out/apk/%.unsigned.apk
@@ -763,7 +758,7 @@ release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-releas
releaseapkgo: out/TildeFriends-arm-release.apk releaseapkgo: out/TildeFriends-arm-release.apk
@adb install -r $< @adb install -r $<
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity @adb shell am start com.unprompted.tildefriends/.MainActivity
.PHONY: releaseapkgo .PHONY: releaseapkgo
# iOS Support # iOS Support
@@ -774,10 +769,10 @@ out/%.app/tildefriends.png: src/ios/tildefriends.png
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
@cp -v $< $@ @cp -v $< $@
out/data.zip: $(RAW_FILES) out/%/data.zip: $(RAW_FILES)
@zip -u $@ -q -9 $(RAW_FILES) @zip -u $@ -q -9 $(RAW_FILES)
out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/data.zip out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/tildefriends-%.app/data.zip
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
@cp -v $< $@ @cp -v $< $@
ifeq ($(HAVE_LINUX_IOS),1) ifeq ($(HAVE_LINUX_IOS),1)
@@ -792,16 +787,6 @@ out/tildefriends-%.ipa: out/tildefriends-ios%.app/tildefriends
@cd $@.tmp/ && zip -u ../../$@ -q -9 -r ./ @cd $@.tmp/ && zip -u ../../$@ -q -9 -r ./
@rm -rf $@.tmp/ @rm -rf $@.tmp/
out/%/tildefriends.standalone: out/%/tildefriends out/data.zip
@echo "[standalone] $@"
@cat $< out/data.zip > $@
@chmod +x $@
out/%/tildefriends.standalone.exe: out/%/tildefriends.exe out/data.zip
@echo "[standalone] $@"
@cat $< out/data.zip > $@
@chmod +x $@
iossimdebug-app: out/tildefriends-iossimdebug.app/tildefriends iossimdebug-app: out/tildefriends-iossimdebug.app/tildefriends
iossimrelease-app: out/tildefriends-iossimrelease.app/tildefriends iossimrelease-app: out/tildefriends-iossimrelease.app/tildefriends
iosdebug-app: out/tildefriends-iosdebug.app/tildefriends iosdebug-app: out/tildefriends-iosdebug.app/tildefriends
@@ -862,11 +847,11 @@ clean:
rm -rf $(BUILD_DIR) rm -rf $(BUILD_DIR)
.PHONY: clean .PHONY: clean
dist: release-apk iosrelease-ipa $(if $(HAVE_WIN), out/winrelease/tildefriends.standalone.exe) default.nix dist: release-apk iosrelease-ipa
@echo [archive] dist/tildefriends-$(VERSION_NUMBER).tar.xz @echo [archive] dist/tildefriends-$(VERSION_NUMBER).tar.xz
@rm -rf out/tildefriends-$(VERSION_NUMBER) @rm -rf out/tildefriends-$(VERSION_NUMBER)
@mkdir -p dist/ out/tildefriends-$(VERSION_NUMBER) @mkdir -p dist/ out/tildefriends-$(VERSION_NUMBER)
@git ls-files --recurse-submodules | tar -c -T- | tar -x -C out/tildefriends-$(VERSION_NUMBER) @git archive main | tar -x -C out/tildefriends-$(VERSION_NUMBER)
@tar \ @tar \
--exclude=apps/welcome* \ --exclude=apps/welcome* \
--exclude=deps/libbacktrace/Isaac.Newton-Opticks.txt \ --exclude=deps/libbacktrace/Isaac.Newton-Opticks.txt \
@@ -882,17 +867,13 @@ dist: release-apk iosrelease-ipa $(if $(HAVE_WIN), out/winrelease/tildefriends.s
--exclude=deps/sqlite/shell.c \ --exclude=deps/sqlite/shell.c \
--exclude=deps/zlib/contrib/vstudio \ --exclude=deps/zlib/contrib/vstudio \
--exclude=deps/zlib/doc \ --exclude=deps/zlib/doc \
-caf dist/tildefriends-$(VERSION_NUMBER).tar.xz \ -caf dist/tildefriends-$(VERSION_NUMBER).tar.xz out/tildefriends-$(VERSION_NUMBER)
-C out/ \
tildefriends-$(VERSION_NUMBER)
@echo "[cp] TildeFriends-x86-$(VERSION_NUMBER).apk" @echo "[cp] TildeFriends-x86-$(VERSION_NUMBER).apk"
@cp out/TildeFriends-x86-release.zopfli.apk dist/TildeFriends-x86-$(VERSION_NUMBER).apk @cp out/TildeFriends-x86-release.zopfli.apk dist/TildeFriends-x86-$(VERSION_NUMBER).apk
@echo "[cp] TildeFriends-arm-$(VERSION_NUMBER).apk" @echo "[cp] TildeFriends-arm-$(VERSION_NUMBER).apk"
@cp out/TildeFriends-arm-release.zopfli.apk dist/TildeFriends-arm-$(VERSION_NUMBER).apk @cp out/TildeFriends-arm-release.zopfli.apk dist/TildeFriends-arm-$(VERSION_NUMBER).apk
@echo "[cp] TildeFriends-$(VERSION_NUMBER).ipa" @echo "[cp] TildeFriends-$(VERSION_NUMBER).ipa"
@cp out/tildefriends-release.ipa dist/TildeFriends-$(VERSION_NUMBER).ipa @cp out/tildefriends-release.ipa dist/TildeFriends-$(VERSION_NUMBER).ipa
@test $(HAVE_WIN) && echo "[cp] tildefriends-$(VERSION_NUMBER).exe"
@test $(HAVE_WIN) && cp out/winrelease/tildefriends.standalone.exe dist/tildefriends-$(VERSION_NUMBER).exe
.PHONY: dist .PHONY: dist
dist-test: dist dist-test: dist

View File

@@ -1,4 +1,4 @@
Copyright 2014-2024 Cory McWilliams Copyright 2014 Cory McWilliams
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in this software and associated documentation files (the "Software"), to deal in

View File

@@ -4,19 +4,46 @@ Tilde Friends is a tool for making and sharing.
A public instance lives at https://www.tildefriends.net/. A public instance lives at https://www.tildefriends.net/.
It is both a peer-to-peer social network client, participating in Secure Scuttlebutt, as well as a platform for writing and running web applications. It is both a peer-to-peer social network client, participating in Secure
Scuttlebutt, as well as a platform for writing and running web applications.
## Goals ## Goals
1. Make it easy and fun to run all sorts of web applications. 1. Make it easy and fun to run all sorts of web applications.
2. Provide security that is easy to understand and protects your data. 2. Provide security that is easy to understand and protects your data.
3. Make creating and sharing web applications accessible to anyone with a browser. 3. Make creating and sharing web applications accessible to anyone with a
browser.
## Building
Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. Builds for
all of those host platforms plus mingw64, iOS, and android.
1. Requires openssl (`libssl-dev`, in debian-speak). All other dependencies
are kept up to date in the tree.
2. To build, run `make debug` or `make release`. An executable will be
generated in a subdirectory of `out/`.
3. It's possible to build for Android, iOS, and Windows on Linux, if you have
the right dependencies in the right places. `make windebug winrelease
iosdebug-ipa iosrelease-ipa release-apk`.
4. To build in docker, `docker build .`.
5. `make format` will normalize formatting to the coding standard.
## Running
By default, running the built `tildefriends` executable will start a web server
at <http://localhost:12345/>. `tildefriends -h` lists further options.
The first user to create an account and log in will be granted administrative
privileges. Further administration can be done at
<http://localhost:12345/~core/admin/>.
## Documentation ## Documentation
Docs are a work in progress: [documentation](https://dev.tildefriends.net/cory/tildefriends/src/branch/main/docs/index.md), or alternatively in Tilde Friends: <https://www.tildefriends.net/~cory/wiki/#test-wiki/tf-app-quick-reference>. Docs are a work in progress:
<https://www.tildefriends.net/~cory/wiki/#test-wiki/tf-app-quick-reference>.
## License ## License
All code, documentation and assets unless otherwise noted in is provided under the All code unless otherwise noted in is provided under the
[MIT](https://opensource.org/licenses/MIT/) license. [MIT](https://opensource.org/licenses/MIT) license.

View File

@@ -1,5 +1,4 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🎛", "emoji": "🎛"
"previous": "&vrpS/vE7n588iYv1p8HafDxHB+YDHTrtUbJiu9nGA9I=.sha256"
} }

View File

@@ -4,38 +4,9 @@
<script> <script>
const g_data = $data; const g_data = $data;
</script> </script>
<link rel="stylesheet" href="w3.css" />
<!-- prettier-ignore -->
<style>
/* 2018 Valiant Poppy */
.w3-theme-l5 {color:#000 !important; background-color:#fbf3f3 !important}
.w3-theme-l4 {color:#000 !important; background-color:#f3d7d6 !important}
.w3-theme-l3 {color:#000 !important; background-color:#e6afae !important}
.w3-theme-l2 {color:#fff !important; background-color:#da8785 !important}
.w3-theme-l1 {color:#fff !important; background-color:#cd5f5d !important}
.w3-theme-d1 {color:#fff !important; background-color:#a93634 !important}
.w3-theme-d2 {color:#fff !important; background-color:#96302e !important}
.w3-theme-d3 {color:#fff !important; background-color:#832a28 !important}
.w3-theme-d4 {color:#fff !important; background-color:#702423 !important}
.w3-theme-d5 {color:#fff !important; background-color:#5e1e1d !important}
.w3-theme-light {color:#000 !important; background-color:#fbf3f3 !important}
.w3-theme-dark {color:#fff !important; background-color:#5e1e1d !important}
.w3-theme-action {color:#fff !important; background-color:#5e1e1d !important}
.w3-theme {color:#fff !important; background-color:#bd3d3a !important}
.w3-text-theme {color:#bd3d3a !important}
.w3-border-theme {border-color:#bd3d3a !important}
.w3-hover-theme:hover {color:#fff !important; background-color:#bd3d3a !important}
.w3-hover-text-theme:hover {color:#bd3d3a !important}
.w3-hover-border-theme:hover {border-color:#bd3d3a !important}
</style>
</head> </head>
<body class="w3-theme-l4"> <body style="color: #fff; width: 100%">
<header class="w3-row w3-padding w3-header w3-theme-l1"> <h1>Tilde Friends Administration</h1>
<h1>Tilde Friends Administration</h1>
</header>
</body> </body>
<script type="module" src="script.js"></script> <script type="module" src="script.js"></script>
</html> </html>

View File

@@ -32,75 +32,59 @@ window.addEventListener('load', function () {
function input_template(key, description) { function input_template(key, description) {
if (description.type === 'boolean') { if (description.type === 'boolean') {
return html` return html`
<li class="w3-row"> <div style="margin-top: 1em">
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${key}</label> <label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
<div class="w3-quarter w3-padding">${description.description}</div> <div>
<input class="w3-quarter w3-check" type="checkbox" ?checked=${description.value} id=${'gs_' + key}></input> <input type="checkbox" ?checked=${description.value} id=${'gs_' + key}></input>
<button class="w3-quarter w3-button w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.checked)}>Set</button> <button @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.checked)}>Set</button>
</li> <div>${description.description}</div>
</div>
</div>
`; `;
} else if (description.type === 'textarea') { } else if (description.type === 'textarea') {
return html` return html`
<li class="w3-row"> <div style="margin-top: 1em"">
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold" <label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
>${key}</label <div style="width: 100%; padding: 0; margin: 0">
> <div style="width: 90%; padding: 0 margin: 0">
<div class="w3-rest w3-padding">${description.description}</div> <textarea style="vertical-align: top; width: 100%" rows=20 cols=80 id=${'gs_' + key}>${description.value}</textarea>
<textarea </div>
class="w3-input" <button @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.firstElementChild.value)}>Set</button>
style="vertical-align: top; resize: vertical" <div>${description.description}</div>
id=${'gs_' + key} </div>
> </div>
${description.value}</textarea
>
<button
class="w3-button w3-right w3-quarter w3-theme-action"
@click=${(e) =>
global_settings_set(
key,
e.srcElement.previousElementSibling.value
)}
>
Set
</button>
</li>
`; `;
} else { } else {
return html` return html`
<li class="w3-row"> <div style="margin-top: 1em">
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${key}</label> <label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
<div class="w3-quarter w3-padding">${description.description}</div> <div>
<input class="w3-input w3-quarter" type="text" value="${description.value}" id=${'gs_' + key}></input> <input type="text" value="${description.value}" id=${'gs_' + key}></input>
<button class="w3-button w3-quarter w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.value)}>Set</button> <button @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.value)}>Set</button>
</li> <div>${description.description}</div>
</div>
</div>
`; `;
} }
} }
const user_template = (user, permissions) => html` const user_template = (user, permissions) => html`
<li class="w3-card w3-margin"> <li>
<button <button @click=${(e) => delete_user(user)}>Delete</button>
class="w3-button w3-theme-action"
@click=${(e) => delete_user(user)}
>
Delete
</button>
${user}: ${permissions.map((x) => permission_template(x))} ${user}: ${permissions.map((x) => permission_template(x))}
</li> </li>
`; `;
const users_template = (users) => const users_template = (users) =>
html` <header class="w3-container w3-theme-l2"><h2>Users</h2></header> html`<h2>Users</h2>
<ul class="w3-ul"> <ul>
${Object.entries(users).map((u) => user_template(u[0], u[1]))} ${Object.entries(users).map((u) => user_template(u[0], u[1]))}
</ul>`; </ul>`;
const page_template = (data) => const page_template = (data) =>
html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%"> html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%">
<header class="w3-container w3-theme-l2"><h2>Global Settings</h2></header> <h2>Global Settings</h2>
<div class="w3-container"> <div>
<ul class="w3-ul"> ${Object.keys(data.settings)
${Object.keys(data.settings) .sort()
.sort() .map((x) => html`${input_template(x, data.settings[x])}`)}
.map((x) => html`${input_template(x, data.settings[x])}`)}
</ul>
</div> </div>
${users_template(data.users)} ${users_template(data.users)}
</div> `; </div> `;

View File

@@ -1,235 +0,0 @@
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
a{background-color:transparent}a:active,a:hover{outline-width:0}
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
button,input{overflow:visible}button,select{text-transform:none}
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
[type=checkbox],[type=radio]{padding:0}
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
/* End extract */
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
.w3-main,#main{transition:margin-left .4s}
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
.w3-bar .w3-button{white-space:normal}
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
.w3-responsive{display:block;overflow-x:auto}
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
@media (max-width:1205px){.w3-auto{max-width:95%}}
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
.w3-display-position{position:absolute}
.w3-circle{border-radius:50%}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
.w3-left{float:left!important}.w3-right{float:right!important}
.w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important}
/* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}

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": "&de7q4A59auHP/34bXgeNH05JZoxsGr5TjwXPvehWH30=.sha256" "previous": "&kgukkyDk1RxgfzgMH6H/0QeDPIuwPZypLuAFax21ljk=.sha256"
} }

View File

@@ -19,36 +19,7 @@ tfrpc.register(async function reload() {
async function main() { async function main() {
let ids = await ssb.getIdentities(); let ids = await ssb.getIdentities();
await app.setDocument( await app.setDocument(
` `<body style="color: #fff">
<head>
<link rel="stylesheet" href="w3.css"></link>
<style>
/* "2018 Sargasso Sea" */
.w3-theme-l5 {color:#000 !important; background-color:#f3f4f7 !important}
.w3-theme-l4 {color:#000 !important; background-color:#d7dbe3 !important}
.w3-theme-l3 {color:#000 !important; background-color:#b0b6c8 !important}
.w3-theme-l2 {color:#fff !important; background-color:#8892ac !important}
.w3-theme-l1 {color:#fff !important; background-color:#636f8e !important}
.w3-theme-d1 {color:#fff !important; background-color:#40485c !important}
.w3-theme-d2 {color:#fff !important; background-color:#394052 !important}
.w3-theme-d3 {color:#fff !important; background-color:#323848 !important}
.w3-theme-d4 {color:#fff !important; background-color:#2b303d !important}
.w3-theme-d5 {color:#fff !important; background-color:#242833 !important}
.w3-theme-light {color:#000 !important; background-color:#f3f4f7 !important}
.w3-theme-dark {color:#fff !important; background-color:#242833 !important}
.w3-theme-action {color:#fff !important; background-color:#242833 !important}
.w3-theme {color:#fff !important; background-color:#485167 !important}
.w3-text-theme {color:#485167 !important}
.w3-border-theme {border-color:#485167 !important}
.w3-hover-theme:hover {color:#fff !important; background-color:#485167 !important}
.w3-hover-text-theme:hover {color:#485167 !important}
.w3-hover-border-theme:hover {border-color:#485167 !important}
</style>
</head>
<body class="w3-theme-l3">
<script>const handler = {};</script> <script>const handler = {};</script>
<script type="module"> <script type="module">
import * as tfrpc from '/static/tfrpc.js'; import * as tfrpc from '/static/tfrpc.js';
@@ -56,8 +27,7 @@ async function main() {
let id = event.srcElement.dataset.id; let id = event.srcElement.dataset.id;
let element = document.createElement('textarea'); let element = document.createElement('textarea');
element.value = await tfrpc.rpc.get_private_key(id); element.value = await tfrpc.rpc.get_private_key(id);
element.style = 'width: 100%; height: auto; read-only: true; resize: none'; element.style = 'width: 100%; read-only: true';
element.classList.add('w3-input');
element.readOnly = true; element.readOnly = true;
event.srcElement.parentElement.appendChild(element); event.srcElement.parentElement.appendChild(element);
event.srcElement.onclick = event => handler.hide_id(event, element); event.srcElement.onclick = event => handler.hide_id(event, element);
@@ -78,7 +48,7 @@ async function main() {
alert('Successfully created: ' + id); alert('Successfully created: ' + id);
await tfrpc.rpc.reload(); await tfrpc.rpc.reload();
} catch (e) { } catch (e) {
alert('Error creating identity: ' + e.message); alert('Error creating identity: ' + e);
} }
} }
handler.hide_id = function hide_id(event, element) { handler.hide_id = function hide_id(event, element) {
@@ -99,36 +69,23 @@ async function main() {
} }
} }
</script> </script>
<header class="w3-theme w3-padding"><h1>SSB Identity Management</h1></header> <h1>SSB Identity Management</h1>
<div class="w3-card-4 w3-margin"> <h2>Create a new identity</h2>
<header class="w3-container w3-theme-l2"><h2>Create a new identity</h2></header> <button id="create_id" onclick="handler.create_id()">Create Identity</button>
<footer class="w3-padding"> <h2>Import an SSB Identity from 12 BIP39 English Words</h2>
<button id="create_id" onclick="handler.create_id()" class="w3-button w3-theme">Create Identity</button> <textarea id="add_id" style="width: 100%" rows="4"></textarea><button id="add" onclick="handler.add_id(event)">Import Identity</button>
</footer> <h2>Identities</h2>
</div> <ul>` +
<div class="w3-card-4 w3-margin">
<header class="w3-container w3-theme-l2"><h2>Import an SSB Identity from 12 BIP39 English Words</h2></header>
<textarea id="add_id" style="width: 100%" rows="4" class="w3-input"></textarea>
<footer class="w3-padding">
<button id="add" onclick="handler.add_id(event)" class="w3-button w3-theme">Import Identity</button>
</footer>
</div>
<div class="w3-card-4 w3-margin">
<header class="w3-container w3-theme-l2"><h2>Identities</h2></header>
<ul class="w3-ul">` +
ids ids
.map( .map(
( (id) => `<li>
id <button onclick="handler.export_id(event)" data-id="${id}">Export Identity</button>
) => `<li style="overflow: hidden; text-wrap: nowrap; text-overflow: ellipsis"> <button onclick="handler.delete_id(event)" data-id="${id}">Delete Identity</button>
<button onclick="handler.export_id(event)" data-id="${id}" class="w3-button w3-theme">Export Identity</button> ${id}
<button onclick="handler.delete_id(event)" data-id="${id}" class="w3-button w3-theme">Delete Identity</button> </li>`
${id}
</li>`
) )
.join('\n') + .join('\n') +
` </ul> ` </ul>
</div>
</body>` </body>`
); );
} }

View File

@@ -1,235 +0,0 @@
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
a{background-color:transparent}a:active,a:hover{outline-width:0}
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
button,input{overflow:visible}button,select{text-transform:none}
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
[type=checkbox],[type=radio]{padding:0}
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
/* End extract */
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
.w3-main,#main{transition:margin-left .4s}
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
.w3-bar .w3-button{white-space:normal}
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
.w3-responsive{display:block;overflow-x:auto}
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
@media (max-width:1205px){.w3-auto{max-width:95%}}
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
.w3-display-position{position:absolute}
.w3-circle{border-radius:50%}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
.w3-left{float:left!important}.w3-right{float:right!important}
.w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important}
/* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}

View File

@@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🦟", "emoji": "🦟",
"previous": "&cUqvSDUls3jn0haD85LPFAGdkc8wFuy347TtATNcJgg=.sha256" "previous": "&TegdzvFE+im94shygaHkgDYSaSrwY2h0OKUXSRPBQDM=.sha256"
} }

View File

@@ -67,6 +67,9 @@ tfrpc.register(function getHash(id, message) {
tfrpc.register(function setHash(hash) { tfrpc.register(function setHash(hash) {
return app.setHash(hash); return app.setHash(hash);
}); });
ssb.addEventListener('message', async function (id) {
await tfrpc.rpc.notifyNewMessage(id);
});
tfrpc.register(async function store_blob(blob) { tfrpc.register(async function store_blob(blob) {
if (Array.isArray(blob)) { if (Array.isArray(blob)) {
blob = Uint8Array.from(blob); blob = Uint8Array.from(blob);
@@ -82,18 +85,13 @@ tfrpc.register(async function store_message(message) {
tfrpc.register(function apps() { tfrpc.register(function apps() {
return core.apps(); return core.apps();
}); });
tfrpc.register(function getActiveIdentity() {
return ssb.getActiveIdentity();
});
tfrpc.register(async function try_decrypt(id, content) { tfrpc.register(async function try_decrypt(id, content) {
return await ssb.privateMessageDecrypt(id, content); return await ssb.privateMessageDecrypt(id, content);
}); });
core.register('onMessage', async function (id) { ssb.addEventListener('broadcasts', async function () {
await tfrpc.rpc.notifyNewMessage(id);
});
core.register('onBroadcastsChanged', async function () {
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts()); await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
}); });
core.register('onConnectionsChanged', async function () { core.register('onConnectionsChanged', async function () {
await tfrpc.rpc.set('connections', await ssb.connections()); await tfrpc.rpc.set('connections', await ssb.connections());
}); });

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4,6 +4,48 @@ import * as tfutils from './tf-utils.js';
const k_project = '%Hr+4xEVtjplidSKBlRWi4Aw/0Tfw7B+1OR9BzlDKmOI=.sha256'; const k_project = '%Hr+4xEVtjplidSKBlRWi4Aw/0Tfw7B+1OR9BzlDKmOI=.sha256';
class TfIdPickerElement extends LitElement {
static get properties() {
return {
ids: {type: Array},
selected: {type: String},
};
}
constructor() {
super();
this.load();
}
async load() {
this.selected = await tfrpc.rpc.localStorageGet('whoami');
this.ids = (await tfrpc.rpc.getIdentities()) || [];
}
changed(event) {
this.selected = event.srcElement.value;
tfrpc.rpc.localStorageSet('whoami', this.selected);
}
render() {
if (this.ids) {
return html`
<select @change=${this.changed} style="max-width: 100%">
${this.ids.map(
(id) =>
html`<option ?selected=${id == this.selected} value=${id}>
${id}
</option>`
)}
</select>
`;
} else {
return html`<div>Loading...</div>`;
}
}
}
customElements.define('tf-id-picker', TfIdPickerElement);
class TfComposeElement extends LitElement { class TfComposeElement extends LitElement {
static get properties() { static get properties() {
return { return {
@@ -63,10 +105,10 @@ class TfIssuesAppElement extends LitElement {
let issues = {}; let issues = {};
let messages = await tfrpc.rpc.query( let messages = await tfrpc.rpc.query(
` `
WITH issues AS (SELECT messages.id, json(messages.content) AS content, messages.author, messages.timestamp FROM messages_refs JOIN messages ON WITH issues AS (SELECT messages.* FROM messages_refs JOIN messages ON
messages.id = messages_refs.message messages.id = messages_refs.message
WHERE messages_refs.ref = ? AND json_extract(messages.content, '$.type') = 'issue'), WHERE messages_refs.ref = ? AND json_extract(messages.content, '$.type') = 'issue'),
edits AS (SELECT messages.id, json(messages.content) AS content, messages.author, messages.timestamp FROM issues JOIN messages_refs ON edits AS (SELECT messages.* FROM issues JOIN messages_refs ON
issues.id = messages_refs.ref JOIN messages ON issues.id = messages_refs.ref JOIN messages ON
messages.id = messages_refs.message messages.id = messages_refs.message
WHERE json_extract(messages.content, '$.type') IN ('issue-edit', 'post')) WHERE json_extract(messages.content, '$.type') IN ('issue-edit', 'post'))
@@ -164,7 +206,7 @@ class TfIssuesAppElement extends LitElement {
if ( if (
confirm(`Are you sure you want to ${open ? 'open' : 'close'} this issue?`) confirm(`Are you sure you want to ${open ? 'open' : 'close'} this issue?`)
) { ) {
let whoami = await tfrpc.rpc.getActiveIdentity(); let whoami = this.shadowRoot.getElementById('picker').selected;
await tfrpc.rpc.appendMessage(whoami, { await tfrpc.rpc.appendMessage(whoami, {
type: 'issue-edit', type: 'issue-edit',
issues: [ issues: [
@@ -179,7 +221,7 @@ class TfIssuesAppElement extends LitElement {
} }
async create_issue(event) { async create_issue(event) {
let whoami = await tfrpc.rpc.getActiveIdentity(); let whoami = this.shadowRoot.getElementById('picker').selected;
await tfrpc.rpc.appendMessage(whoami, { await tfrpc.rpc.appendMessage(whoami, {
type: 'issue', type: 'issue',
project: k_project, project: k_project,
@@ -189,7 +231,7 @@ class TfIssuesAppElement extends LitElement {
} }
async reply_to_issue(event) { async reply_to_issue(event) {
let whoami = await tfrpc.rpc.getActiveIdentity(); let whoami = this.shadowRoot.getElementById('picker').selected;
await tfrpc.rpc.appendMessage(whoami, { await tfrpc.rpc.appendMessage(whoami, {
type: 'post', type: 'post',
text: event.detail.value, text: event.detail.value,
@@ -207,7 +249,10 @@ class TfIssuesAppElement extends LitElement {
} }
render() { render() {
let header = html` <h1>Tilde Friends Issues</h1> `; let header = html`
<h1>Tilde Friends Issues</h1>
<tf-id-picker id="picker"></tf-id-picker>
`;
if (this.selected) { if (this.selected) {
return html` return html`
${header} ${header}

View File

@@ -55,7 +55,7 @@ function new_message() {
return g_new_message_promise; return g_new_message_promise;
} }
core.register('onMessage', function (id) { ssb.addEventListener('message', function (id) {
let resolve = g_new_message_resolve; let resolve = g_new_message_resolve;
g_new_message_promise = new Promise(function (resolve, reject) { g_new_message_promise = new Promise(function (resolve, reject) {
g_new_message_resolve = resolve; g_new_message_resolve = resolve;

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": "&HXCdDG8gGYXElTyEFbg85jqa6lDXNL2ENPIA9UoJNbI=.sha256" "previous": "&IU+TwyM7TznD8NBfnw7tgW2zxVlMqTVxSqWFjuosLwo=.sha256"
} }

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": "&h0sTvkhc3zEJw/sH612fy5i554Gr1AKzCBbLkm0KH28=.sha256" "previous": "&Xs1X5TzLCk6KVr+5IDc80JAHYxJyoD10cXKBUYpFqWQ=.sha256"
} }

View File

@@ -76,7 +76,7 @@ tfrpc.register(function getHash(id, message) {
tfrpc.register(function setHash(hash) { tfrpc.register(function setHash(hash) {
return app.setHash(hash); return app.setHash(hash);
}); });
core.register('onMessage', async function (id) { ssb.addEventListener('message', async function (id) {
await tfrpc.rpc.notifyNewMessage(id); await tfrpc.rpc.notifyNewMessage(id);
}); });
tfrpc.register(async function store_blob(blob) { tfrpc.register(async function store_blob(blob) {
@@ -100,19 +100,13 @@ tfrpc.register(async function try_decrypt(id, content) {
tfrpc.register(async function encrypt(id, recipients, content) { tfrpc.register(async function encrypt(id, recipients, content) {
return await ssb.privateMessageEncrypt(id, recipients, content); return await ssb.privateMessageEncrypt(id, recipients, content);
}); });
tfrpc.register(async function getActiveIdentity() { ssb.addEventListener('broadcasts', async function () {
return await ssb.getActiveIdentity();
});
core.register('onBroadcastsChanged', async function () {
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts()); await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
}); });
core.register('onConnectionsChanged', async function () { core.register('onConnectionsChanged', async function () {
await tfrpc.rpc.set('connections', await ssb.connections()); await tfrpc.rpc.set('connections', await ssb.connections());
}); });
core.register('setActiveIdentity', async function (id) {
await tfrpc.rpc.set('identity', id);
});
async function main() { async function main() {
if (typeof database !== 'undefined') { if (typeof database !== 'undefined') {

View File

@@ -1,94 +1,90 @@
function textNode(text) { function textNode(text) {
const node = new commonmark.Node('text', undefined); const node = new commonmark.Node("text", undefined);
node.literal = text; node.literal = text;
return node; return node;
} }
function linkNode(text, link) { function linkNode(text, link) {
const linkNode = new commonmark.Node('link', undefined); const linkNode = new commonmark.Node("link", undefined);
if (link.startsWith('#')) { linkNode.destination = `#q=${encodeURIComponent(link)}`;
linkNode.destination = `#q=${encodeURIComponent(link)}`; linkNode.appendChild(textNode(text));
} else { return linkNode;
linkNode.destination = link;
}
linkNode.appendChild(textNode(text));
return linkNode;
} }
function splitMatches(text, regexp) { function splitMatches(text, regexp) {
// Regexp must be sticky. // Regexp must be sticky.
regexp = new RegExp(regexp, 'gm'); regexp = new RegExp(regexp, "gm");
let i = 0; let i = 0;
const result = []; const result = [];
let match = regexp.exec(text); let match = regexp.exec(text);
while (match) { while (match) {
const matchText = match[0]; const matchText = match[0];
if (match.index > i) { if (match.index > i) {
result.push([text.substring(i, match.index), false]); result.push([text.substring(i, match.index), false]);
} }
result.push([matchText, true]); result.push([matchText, true]);
i = match.index + matchText.length; i = match.index + matchText.length;
match = regexp.exec(text); match = regexp.exec(text);
} }
if (i < text.length) { if (i < text.length) {
result.push([text.substring(i, text.length), false]); result.push([text.substring(i, text.length), false]);
} }
return result; return result;
} }
const regex = new RegExp('(?:https?://[^ ]+[^ .,])|(?:(?<!\\w)#[\\w-]+)|(?:@[A-Za-z0-9+/]+=.ed25519)|(?:[%&][A-Za-z0-9+/]+=.sha256)'); const regex = new RegExp("(?<!\\w)#[\\w-]+");
function split(textNodes) { function split(textNodes) {
const text = textNodes.map((n) => n.literal).join(''); const text = textNodes.map(n => n.literal).join("");
const parts = splitMatches(text, regex); const parts = splitMatches(text, regex);
return parts.map((part) => { return parts.map(part => {
if (part[1]) { if (part[1]) {
return linkNode(part[0], part[0]); return linkNode(part[0], part[0]);
} else { } else {
return textNode(part[0]); return textNode(part[0]);
} }
}); });
} }
export function transform(parsed) { export function transform(parsed) {
const walker = parsed.walker(); const walker = parsed.walker();
let event; let event;
let nodes = []; let nodes = [];
while ((event = walker.next())) { while ((event = walker.next())) {
const node = event.node; const node = event.node;
if (event.entering && node.type === 'text') { if (event.entering && node.type === "text") {
nodes.push(node); nodes.push(node);
} else { } else {
if (nodes.length > 0) { if (nodes.length > 0) {
split(nodes) split(nodes)
.reverse() .reverse()
.forEach((newNode) => { .forEach(newNode => {
nodes[0].insertAfter(newNode); nodes[0].insertAfter(newNode);
}); });
nodes.forEach((n) => n.unlink()); nodes.forEach(n => n.unlink());
nodes = []; nodes = [];
} }
} }
} }
if (nodes.length > 0) { if (nodes.length > 0) {
split(nodes) split(nodes)
.reverse() .reverse()
.forEach((newNode) => { .forEach(newNode => {
nodes[0].insertAfter(newNode); nodes[0].insertAfter(newNode);
}); });
nodes.forEach((n) => n.unlink()); nodes.forEach(n => n.unlink());
} }
return parsed; return parsed;
} }

View File

@@ -0,0 +1,91 @@
function textNode(text) {
const node = new commonmark.Node("text", undefined);
node.literal = text;
return node;
}
function linkNode(text, url) {
const urlNode = new commonmark.Node("link", undefined);
urlNode.destination = url;
urlNode.appendChild(textNode(text));
return urlNode;
}
function splitMatches(text, regexp) {
// Regexp must be sticky.
regexp = new RegExp(regexp, "gm");
let i = 0;
const result = [];
let match = regexp.exec(text);
while (match) {
const matchText = match[0];
if (match.index > i) {
result.push([text.substring(i, match.index), false]);
}
result.push([matchText, true]);
i = match.index + matchText.length;
match = regexp.exec(text);
}
if (i < text.length) {
result.push([text.substring(i, text.length), false]);
}
return result;
}
const urlRegexp = new RegExp("https?://[^ ]+[^ .,]");
function splitURLs(textNodes) {
const text = textNodes.map(n => n.literal).join("");
const parts = splitMatches(text, urlRegexp);
return parts.map(part => {
if (part[1]) {
return linkNode(part[0], part[0]);
} else {
return textNode(part[0]);
}
});
}
export function transform(parsed) {
const walker = parsed.walker();
let event;
let nodes = [];
while ((event = walker.next())) {
const node = event.node;
if (event.entering && node.type === "text") {
nodes.push(node);
} else {
if (nodes.length > 0) {
splitURLs(nodes)
.reverse()
.forEach(newNode => {
nodes[0].insertAfter(newNode);
});
nodes.forEach(n => n.unlink());
nodes = [];
}
}
}
if (nodes.length > 0) {
splitURLs(nodes)
.reverse()
.forEach(newNode => {
nodes[0].insertAfter(newNode);
});
nodes.forEach(n => n.unlink());
}
return parsed;
}

View File

@@ -1,5 +1,3 @@
import * as tfrpc from '/static/tfrpc.js';
let g_emojis; let g_emojis;
function get_emojis() { function get_emojis() {
@@ -12,154 +10,105 @@ function get_emojis() {
}); });
} }
async function get_recent(author) { export function picker(callback, anchor) {
let recent = await tfrpc.rpc.query( get_emojis().then(function (json) {
` let div = document.createElement('div');
SELECT DISTINCT content ->> '$.vote.expression' AS value div.id = 'emoji_picker';
FROM messages div.style.color = '#000';
WHERE author = ? AND div.style.background = '#fff';
content ->> '$.type' = 'vote' div.style.border = '1px solid #000';
ORDER BY timestamp DESC LIMIT 10 div.style.display = 'block';
`, div.style.position = 'absolute';
[author] div.style.minWidth = 'min(16em, 90vw)';
); div.style.width = 'min(16em, 90vw)';
return recent.map((x) => x.value); div.style.maxWidth = 'min(16em, 90vw)';
} div.style.maxHeight = '16em';
div.style.overflow = 'scroll';
div.style.fontWeight = 'bold';
div.style.fontSize = 'xx-large';
let input = document.createElement('input');
input.type = 'text';
input.style.display = 'block';
input.style.boxSizing = 'border-box';
input.style.width = '100%';
input.style.margin = '0';
input.style.position = 'relative';
div.appendChild(input);
let list = document.createElement('div');
div.appendChild(list);
div.addEventListener('mousedown', function (event) {
event.stopPropagation();
});
export async function picker(callback, anchor, author) { function cleanup() {
let json = await get_emojis(); console.log('emoji cleanup');
let recent = await get_recent(author); div.parentElement.removeChild(div);
window.removeEventListener('keydown', key_down);
console.log('removing click');
document.body.removeEventListener('mousedown', cleanup);
}
let div = document.createElement('div'); function key_down(event) {
div.id = 'emoji_picker'; if (event.key == 'Escape') {
div.style.color = '#000'; cleanup();
div.style.background = '#fff'; }
div.style.border = '1px solid #000'; }
div.style.display = 'block';
div.style.position = 'absolute';
div.style.minWidth = 'min(16em, 90vw)';
div.style.width = 'min(16em, 90vw)';
div.style.maxWidth = 'min(16em, 90vw)';
div.style.maxHeight = '16em';
div.style.overflow = 'scroll';
div.style.fontWeight = 'bold';
div.style.fontSize = 'xx-large';
let input = document.createElement('input');
input.type = 'text';
input.style.display = 'block';
input.style.boxSizing = 'border-box';
input.style.width = '100%';
input.style.margin = '0';
input.style.position = 'relative';
div.appendChild(input);
let list = document.createElement('div');
div.appendChild(list);
div.addEventListener('mousedown', function (event) {
event.stopPropagation();
});
function cleanup() { function chosen(event) {
console.log('emoji cleanup'); console.log(event.srcElement.innerText);
div.parentElement.removeChild(div); callback(event.srcElement.innerText);
window.removeEventListener('keydown', key_down);
console.log('removing click');
document.body.removeEventListener('mousedown', cleanup);
}
function key_down(event) {
if (event.key == 'Escape') {
cleanup(); cleanup();
} }
}
function chosen(event) { function refresh() {
console.log(event.srcElement.innerText); while (list.firstChild) {
callback(event.srcElement.innerText); list.removeChild(list.firstChild);
cleanup(); }
} let search = input.value.toLowerCase();
let any_at_all = false;
function refresh() { for (let row of Object.entries(json)) {
while (list.firstChild) { let header = document.createElement('div');
list.removeChild(list.firstChild); header.appendChild(document.createTextNode(row[0]));
} list.appendChild(header);
let search = input.value.toLowerCase(); let any = false;
let any_at_all = false; for (let entry of Object.entries(row[1])) {
if (recent) { if (
let emoji_to_name = {}; search &&
for (let row of Object.values(json)) { search.length &&
for (let entry of Object.entries(row)) { entry[0].toLowerCase().indexOf(search) == -1
emoji_to_name[entry[1]] = entry[0]; ) {
continue;
}
let emoji = document.createElement('span');
const k_size = '1.25em';
emoji.style.display = 'inline-block';
emoji.style.overflow = 'hidden';
emoji.style.cursor = 'pointer';
emoji.onclick = chosen;
emoji.title = entry[0];
emoji.appendChild(document.createTextNode(entry[1]));
list.appendChild(emoji);
any = true;
any_at_all = true;
}
if (!any) {
list.removeChild(header);
} }
} }
let header = document.createElement('div'); if (!any_at_all) {
header.appendChild(document.createTextNode('Recent')); list.appendChild(document.createTextNode('No matches found.'));
list.appendChild(header);
let any = false;
for (let entry of recent) {
if (
search &&
search.length &&
(emoji_to_name[entry] || '').toLowerCase().indexOf(search) == -1
) {
continue;
}
let emoji = document.createElement('span');
const k_size = '1.25em';
emoji.style.display = 'inline-block';
emoji.style.overflow = 'hidden';
emoji.style.cursor = 'pointer';
emoji.onclick = chosen;
emoji.title = emoji_to_name[entry] || entry;
emoji.appendChild(document.createTextNode(entry));
list.appendChild(emoji);
any = true;
}
if (!any) {
list.removeChild(header);
} }
} }
for (let row of Object.entries(json)) { refresh();
let header = document.createElement('div'); input.oninput = refresh;
header.appendChild(document.createTextNode(row[0])); document.body.appendChild(div);
list.appendChild(header); div.style.position = 'fixed';
let any = false; div.style.top = '50%';
for (let entry of Object.entries(row[1])) { div.style.left = '50%';
if ( div.style.transform = 'translate(-50%, -50%)';
search && input.focus();
search.length && console.log('adding click');
entry[0].toLowerCase().indexOf(search) == -1 document.body.addEventListener('mousedown', cleanup);
) { window.addEventListener('keydown', key_down);
continue; });
}
let emoji = document.createElement('span');
const k_size = '1.25em';
emoji.style.display = 'inline-block';
emoji.style.overflow = 'hidden';
emoji.style.cursor = 'pointer';
emoji.onclick = chosen;
emoji.title = entry[0];
emoji.appendChild(document.createTextNode(entry[1]));
list.appendChild(emoji);
any = true;
any_at_all = true;
}
if (!any) {
list.removeChild(header);
}
}
if (!any_at_all) {
list.appendChild(document.createTextNode('No matches found.'));
}
}
refresh();
input.oninput = refresh;
document.body.appendChild(div);
div.style.position = 'fixed';
div.style.top = '50%';
div.style.left = '50%';
div.style.transform = 'translate(-50%, -50%)';
input.focus();
console.log('adding click');
document.body.addEventListener('mousedown', cleanup);
window.addEventListener('keydown', key_down);
} }

View File

@@ -1,5 +1,5 @@
<!doctype html> <!doctype html>
<html> <html style="color: #fff">
<head> <head>
<title>Tilde Friends</title> <title>Tilde Friends</title>
<base target="_top" /> <base target="_top" />
@@ -10,14 +10,14 @@
} }
</style> </style>
</head> </head>
<body style="margin: 0; padding: 0"> <body style="background-color: #223a5e">
<tf-app></tf-app> <tf-app class="w3-deep-purple" />
<tf-reactions-modal id="reactions_modal"></tf-reactions-modal>
<script> <script>
window.litDisableBundleWarning = true; window.litDisableBundleWarning = true;
</script> </script>
<script src="filesaver.min.js"></script> <script src="filesaver.min.js"></script>
<script src="commonmark.min.js"></script> <script src="commonmark.min.js"></script>
<script src="commonmark-linkify.js" type="module"></script>
<script src="commonmark-hashtag.js" type="module"></script> <script src="commonmark-hashtag.js" type="module"></script>
<script src="script.js" type="module"></script> <script src="script.js" type="module"></script>
</body> </body>

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,13 +1,13 @@
import {LitElement, html} from './lit-all.min.js'; import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js'; import * as tfrpc from '/static/tfrpc.js';
import * as tf_id_picker from './tf-id-picker.js';
import * as tf_app from './tf-app.js'; import * as tf_app from './tf-app.js';
import * as tf_message from './tf-message.js'; import * as tf_message from './tf-message.js';
import * as tf_user from './tf-user.js'; import * as tf_user from './tf-user.js';
import * as tf_compose from './tf-compose.js'; import * as tf_compose from './tf-compose.js';
import * as tf_news from './tf-news.js'; import * as tf_news from './tf-news.js';
import * as tf_profile from './tf-profile.js'; import * as tf_profile from './tf-profile.js';
import * as tf_reactions_modal from './tf-reactions-modal.js';
import * as tf_tab_mentions from './tf-tab-mentions.js'; import * as tf_tab_mentions from './tf-tab-mentions.js';
import * as tf_tab_news from './tf-tab-news.js'; import * as tf_tab_news from './tf-tab-news.js';
import * as tf_tab_news_feed from './tf-tab-news-feed.js'; import * as tf_tab_news_feed from './tf-tab-news-feed.js';

View File

@@ -52,15 +52,13 @@ class TfElement extends LitElement {
self.broadcasts = value; self.broadcasts = value;
} else if (name === 'connections') { } else if (name === 'connections') {
self.connections = value; self.connections = value;
} else if (name === 'identity') {
self.whoami = value;
} }
}); });
this.initial_load(); this.initial_load();
} }
async initial_load() { async initial_load() {
let whoami = await tfrpc.rpc.getActiveIdentity(); let whoami = await tfrpc.rpc.localStorageGet('whoami');
let ids = (await tfrpc.rpc.getIdentities()) || []; let ids = (await tfrpc.rpc.getIdentities()) || [];
this.whoami = whoami ?? (ids.length ? ids[0] : undefined); this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
this.ids = ids; this.ids = ids;
@@ -195,6 +193,29 @@ class TfElement extends LitElement {
} }
} }
render_id_picker() {
return html`
<div style="display: flex; gap: 8px">
<tf-id-picker
id="picker"
style="flex: 1 1 auto"
selected=${this.whoami}
.ids=${this.ids}
.users=${this.users}
@change=${this._handle_whoami_changed}
></tf-id-picker>
<button
class="w3-button w3-dark-grey w3-border"
style="flex: 0 0 auto"
@click=${this.create_identity}
id="create_identity"
>
Create Identity
</button>
</div>
`;
}
async load_recent_tags() { async load_recent_tags() {
let start = new Date(); let start = new Date();
this.tags = await tfrpc.rpc.query( this.tags = await tfrpc.rpc.query(
@@ -234,15 +255,7 @@ class TfElement extends LitElement {
by_count.push({count: v.of, id: id}); by_count.push({count: v.of, id: id});
} }
console.log(by_count.sort((x, y) => y.count - x.count).slice(0, 20)); console.log(by_count.sort((x, y) => y.count - x.count).slice(0, 20));
let start_time = new Date();
users = await this.fetch_about(Object.keys(following).sort(), users); users = await this.fetch_about(Object.keys(following).sort(), users);
console.log(
'about took',
(new Date() - start_time) / 1000.0,
'seconds for',
Object.keys(users).length,
'users'
);
this.following = Object.keys(following); this.following = Object.keys(following);
this.users = users; this.users = users;
await tags; await tags;
@@ -264,7 +277,6 @@ class TfElement extends LitElement {
hash=${this.hash} hash=${this.hash}
.unread=${this.unread} .unread=${this.unread}
@refresh=${() => (this.unread = [])} @refresh=${() => (this.unread = [])}
?loading=${this.loading}
></tf-tab-news> ></tf-tab-news>
`; `;
} else if (this.tab === 'connections') { } else if (this.tab === 'connections') {
@@ -340,20 +352,18 @@ class TfElement extends LitElement {
}; };
let tabs = html` let tabs = html`
<div class="w3-bar w3-theme-l1"> <div class="w3-bar w3-black">
${Object.entries(k_tabs).map( ${Object.entries(k_tabs).map(
([k, v]) => html` ([k, v]) => html`
<button <button
title=${v} title=${v}
class="w3-bar-item w3-padding w3-hover-theme tab ${self.tab == v class="w3-bar-item w3-padding-large w3-hover-gray tab ${self.tab ==
? 'w3-theme-l2' v
: 'w3-theme-l1'}" ? 'w3-red'
: 'w3-black'}"
@click=${() => self.set_tab(v)} @click=${() => self.set_tab(v)}
> >
${k} ${k}
<span class=${self.tab == v ? '' : 'w3-hide-small'}
>${v.charAt(0).toUpperCase() + v.substring(1)}</span
>
</button> </button>
` `
)} )}
@@ -361,27 +371,15 @@ class TfElement extends LitElement {
`; `;
let contents = !this.loaded let contents = !this.loaded
? this.loading ? this.loading
? html`<div ? html`<div>Loading...</div>`
class="w3-panel w3-theme-l5 w3-card-4 w3-padding-large w3-round-xlarge"
>
Loading...
</div>
${this.render_tab()}`
: html`<div>Select or create an identity.</div>` : html`<div>Select or create an identity.</div>`
: this.render_tab(); : this.render_tab();
return html` return html`
<div ${this.render_id_picker()} ${tabs}
style="width: 100vw; min-height: 100vh; height: 100%" ${this.tags.map(
class="w3-theme-dark" (x) => html`<tf-tag tag=${x.tag} count=${x.count}></tf-tag>`
> )}
${tabs} ${contents}
<div style="padding: 8px">
${this.tags.map(
(x) => html`<tf-tag tag=${x.tag} count=${x.count}></tf-tag>`
)}
${contents}
</div>
</div>
`; `;
} }
} }

View File

@@ -1,4 +1,4 @@
import {LitElement, html, unsafeHTML, live} from './lit-all.min.js'; import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import * as tfutils from './tf-utils.js'; import * as tfutils from './tf-utils.js';
import * as tfrpc from '/static/tfrpc.js'; import * as tfrpc from '/static/tfrpc.js';
import {styles} from './tf-styles.js'; import {styles} from './tf-styles.js';
@@ -13,7 +13,6 @@ class TfComposeElement extends LitElement {
branch: {type: String}, branch: {type: String},
apps: {type: Object}, apps: {type: Object},
drafts: {type: Object}, drafts: {type: Object},
author: {type: String},
}; };
} }
@@ -26,7 +25,6 @@ class TfComposeElement extends LitElement {
this.branch = undefined; this.branch = undefined;
this.apps = undefined; this.apps = undefined;
this.drafts = {}; this.drafts = {};
this.author = undefined;
} }
process_text(text) { process_text(text) {
@@ -66,7 +64,7 @@ class TfComposeElement extends LitElement {
updated = true; updated = true;
} }
if (updated) { if (updated) {
setTimeout(() => this.notify(draft), 0); this.requestUpdate();
} }
return tfutils.markdown(text); return tfutils.markdown(text);
} }
@@ -74,7 +72,7 @@ class TfComposeElement extends LitElement {
input(event) { input(event) {
let edit = this.renderRoot.getElementById('edit'); let edit = this.renderRoot.getElementById('edit');
let preview = this.renderRoot.getElementById('preview'); let preview = this.renderRoot.getElementById('preview');
preview.innerHTML = this.process_text(edit.innerText); preview.innerHTML = this.process_text(edit.value);
let content_warning = this.renderRoot.getElementById('content_warning'); let content_warning = this.renderRoot.getElementById('content_warning');
let content_warning_preview = this.renderRoot.getElementById( let content_warning_preview = this.renderRoot.getElementById(
'content_warning_preview' 'content_warning_preview'
@@ -82,10 +80,6 @@ class TfComposeElement extends LitElement {
if (content_warning && content_warning_preview) { if (content_warning && content_warning_preview) {
content_warning_preview.innerText = content_warning.value; content_warning_preview.innerText = content_warning.value;
} }
let draft = this.get_draft();
draft.text = edit.innerText;
draft.content_warning = content_warning?.innerText;
setTimeout(() => this.notify(draft), 0);
} }
notify(draft) { notify(draft) {
@@ -101,6 +95,14 @@ class TfComposeElement extends LitElement {
); );
} }
change() {
let draft = this.get_draft();
draft.text = this.renderRoot.getElementById('edit')?.value;
draft.content_warning =
this.renderRoot.getElementById('content_warning')?.value;
this.notify(draft);
}
convert_to_format(buffer, type, mime_type) { convert_to_format(buffer, type, mime_type) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
let img = new Image(); let img = new Image();
@@ -167,7 +169,8 @@ class TfComposeElement extends LitElement {
size: buffer.length ?? buffer.byteLength, size: buffer.length ?? buffer.byteLength,
}; };
let edit = self.renderRoot.getElementById('edit'); let edit = self.renderRoot.getElementById('edit');
edit.innerText += `\n![${name}](${id})`; edit.value += `\n![${name}](${id})`;
self.change();
self.input(); self.input();
} catch (e) { } catch (e) {
alert(e?.message); alert(e?.message);
@@ -194,7 +197,7 @@ class TfComposeElement extends LitElement {
let edit = this.renderRoot.getElementById('edit'); let edit = this.renderRoot.getElementById('edit');
let message = { let message = {
type: 'post', type: 'post',
text: edit.innerText, text: edit.value,
}; };
if (this.root || this.branch) { if (this.root || this.branch) {
message.root = this.root; message.root = this.root;
@@ -222,8 +225,8 @@ class TfComposeElement extends LitElement {
} }
try { try {
await tfrpc.rpc.appendMessage(this.whoami, message).then(function () { await tfrpc.rpc.appendMessage(this.whoami, message).then(function () {
edit.innerText = ''; edit.value = '';
self.input(); self.change();
self.notify(undefined); self.notify(undefined);
self.requestUpdate(); self.requestUpdate();
}); });
@@ -233,11 +236,17 @@ class TfComposeElement extends LitElement {
} }
discard() { discard() {
let edit = this.renderRoot.getElementById('edit');
edit.value = '';
this.change();
let preview = this.renderRoot.getElementById('preview');
preview.innerHTML = '';
this.notify(undefined); this.notify(undefined);
} }
attach() { attach() {
let self = this; let self = this;
let edit = this.renderRoot.getElementById('edit');
let input = document.createElement('input'); let input = document.createElement('input');
input.type = 'file'; input.type = 'file';
input.onchange = function (event) { input.onchange = function (event) {
@@ -275,38 +284,22 @@ class TfComposeElement extends LitElement {
} }
firstUpdated() { firstUpdated() {
let values = Object.entries(this.users).map((x) => ({
key: x[1].name ?? x[0],
value: x[0],
}));
if (this.author) {
values = [].concat(
[
{
key: this.users[this.author]?.name,
value: this.author,
},
],
values
);
}
let tribute = new Tribute({ let tribute = new Tribute({
collection: [ collection: [
{ {
values: values, values: Object.entries(this.users).map((x) => ({
key: x[1].name,
value: x[0],
})),
selectTemplate: function (item) { selectTemplate: function (item) {
return item return `[@${item.original.key}](${item.original.value})`;
? `[@${item.original.key}](${item.original.value})`
: undefined;
}, },
}, },
{ {
trigger: '&', trigger: '&',
values: this.autocomplete, values: this.autocomplete,
selectTemplate: function (item) { selectTemplate: function (item) {
return item return `![${item.original.key}](${item.original.value})`;
? `![${item.original.key}](${item.original.value})`
: undefined;
}, },
}, },
], ],
@@ -317,10 +310,10 @@ class TfComposeElement extends LitElement {
updated() { updated() {
super.updated(); super.updated();
let edit = this.renderRoot.getElementById('edit'); let edit = this.renderRoot.getElementById('edit');
if (this.last_updated_text !== edit.innerText) { if (this.last_updated_text !== edit.value) {
let preview = this.renderRoot.getElementById('preview'); let preview = this.renderRoot.getElementById('preview');
preview.innerHTML = this.process_text(edit.innerText); preview.innerHTML = this.process_text(edit.value);
this.last_updated_text = edit.innerText; this.last_updated_text = edit.value;
} }
let encrypt = this.renderRoot.getElementById('encrypt_to'); let encrypt = this.renderRoot.getElementById('encrypt_to');
if (encrypt) { if (encrypt) {
@@ -340,7 +333,8 @@ 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(), 0); this.notify(draft);
this.requestUpdate();
} }
render_mention(mention) { render_mention(mention) {
@@ -348,7 +342,7 @@ class TfComposeElement extends LitElement {
return html` <div style="display: flex; flex-direction: row"> return html` <div style="display: flex; flex-direction: row">
<div style="align-self: center; margin: 0.5em"> <div style="align-self: center; margin: 0.5em">
<button <button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
title="Remove ${mention.name} mention" title="Remove ${mention.name} mention"
@click=${() => self.remove_mention(mention.link)} @click=${() => self.remove_mention(mention.link)}
> >
@@ -402,16 +396,16 @@ class TfComposeElement extends LitElement {
if (this.apps) { if (this.apps) {
return html` return html`
<div class="w3-card-4 w3-margin w3-padding"> <div class="w3-card-4 w3-margin w3-padding">
<select id="select" class="w3-select w3-theme-d1"> <select id="select" class="w3-select w3-dark-grey">
${Object.keys(self.apps).map( ${Object.keys(self.apps).map(
(app) => html`<option value=${app}>${app}</option>` (app) => html`<option value=${app}>${app}</option>`
)} )}
</select> </select>
<button class="w3-button w3-theme-d1" @click=${attach_selected_app}> <button class="w3-button w3-dark-grey" @click=${attach_selected_app}>
Attach Attach
</button> </button>
<button <button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => (this.apps = null)} @click=${() => (this.apps = null)}
> >
Cancel Cancel
@@ -427,12 +421,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 class="w3-button w3-theme-d1" @click=${attach_app}> return html`<button class="w3-button w3-dark-grey" @click=${attach_app}>
Attach App Attach App
</button>`; </button>`;
} else { } else {
return html`<button return html`<button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => (this.apps = null)} @click=${() => (this.apps = null)}
> >
Discard App Discard App
@@ -454,15 +448,15 @@ class TfComposeElement extends LitElement {
return html` return html`
<div class="w3-container w3-padding"> <div class="w3-container w3-padding">
<p> <p>
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning(undefined)} checked="checked"></input> <input type="checkbox" class="w3-check w3-dark-grey" id="cw" @change=${() => self.set_content_warning(undefined)} checked="checked"></input>
<label for="cw">CW</label> <label for="cw">CW</label>
</p> </p>
<input type="text" class="w3-input w3-border w3-theme-d1" id="content_warning" placeholder="Enter a content warning here." @input=${this.input} @change=${this.change} value=${draft.content_warning}></input> <input type="text" class="w3-input w3-border w3-dark-grey" id="content_warning" placeholder="Enter a content warning here." @input=${this.input} @change=${this.change} value=${draft.content_warning}></input>
</div> </div>
`; `;
} else { } else {
return html` return html`
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning('')}></input> <input type="checkbox" class="w3-check w3-dark-grey" id="cw" @change=${() => self.set_content_warning('')}></input>
<label for="cw">CW</label> <label for="cw">CW</label>
`; `;
} }
@@ -492,14 +486,14 @@ class TfComposeElement extends LitElement {
<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" 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-dark-grey" @click=${() => this.set_encrypt(undefined)}>🚮</button>
</div> </div>
<ul> <ul>
${draft.encrypt_to.map( ${draft.encrypt_to.map(
(x) => html` (x) => html`
<li> <li>
<tf-user id=${x} .users=${this.users}></tf-user> <tf-user id=${x} .users=${this.users}></tf-user>
<input type="button" class="w3-button w3-theme-d1" value="🚮" @click=${() => this.set_encrypt(draft.encrypt_to.filter((id) => id != x))}></input> <input type="button" class="w3-button w3-dark-grey" value="🚮" @click=${() => this.set_encrypt(draft.encrypt_to.filter((id) => id != x))}></input>
</li>` </li>`
)} )}
</ul> </ul>
@@ -518,7 +512,7 @@ class TfComposeElement extends LitElement {
let draft = self.get_draft(); let draft = self.get_draft();
let content_warning = let content_warning =
draft.content_warning !== undefined draft.content_warning !== undefined
? html`<div class="w3-panel w3-round-xlarge w3-theme-d2"> ? html`<div class="w3-panel w3-round-xlarge w3-blue">
<p id="content_warning_preview">${draft.content_warning}</p> <p id="content_warning_preview">${draft.content_warning}</p>
</div>` </div>`
: undefined; : undefined;
@@ -526,31 +520,34 @@ class TfComposeElement extends LitElement {
draft.encrypt_to !== undefined draft.encrypt_to !== undefined
? undefined ? undefined
: html`<button : html`<button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => this.set_encrypt([])} @click=${() => this.set_encrypt([])}
> >
🔐 🔐
</button>`; </button>`;
let result = html` let result = html`
<div <div
class="w3-card-4 w3-theme-d4 w3-padding-small" class="w3-card-4 w3-blue-grey w3-padding"
style="box-sizing: border-box" style="box-sizing: border-box"
> >
${this.render_encrypt()} ${this.render_encrypt()}
<div class="w3-container w3-padding-small"> <div style="display: flex; flex-direction: row; width: 100%; gap: 4px">
<div class="w3-half"> <div style="flex: 1 0 50%">
<span <p>
class="w3-input w3-theme-d1 w3-border" <textarea
style="resize: vertical; width: 100%; overflow: hidden; white-space: pre-wrap" class="w3-input w3-dark-grey w3-border"
placeholder="Write a post here." style="resize: vertical"
id="edit" placeholder="Write a post here."
@input=${this.input} id="edit"
@paste=${this.paste} @input=${this.input}
contenteditable @change=${this.change}
.innerText=${live(draft.text ?? '')} @paste=${this.paste}
></span> >
${draft.text}</textarea
>
</p>
</div> </div>
<div class="w3-half w3-padding"> <div style="flex: 1 0 50%">
${content_warning} ${content_warning}
<div id="preview"></div> <div id="preview"></div>
</div> </div>
@@ -559,14 +556,18 @@ class TfComposeElement extends LitElement {
self.render_mention(x) self.render_mention(x)
)} )}
${this.render_attach_app()} ${this.render_content_warning()} ${this.render_attach_app()} ${this.render_content_warning()}
<button class="w3-button w3-theme-d1" id="submit" @click=${this.submit}> <button
class="w3-button w3-dark-grey"
id="submit"
@click=${this.submit}
>
Submit Submit
</button> </button>
<button class="w3-button w3-theme-d1" @click=${this.attach}> <button class="w3-button w3-dark-grey" @click=${this.attach}>
Attach Attach
</button> </button>
${this.render_attach_app_button()} ${encrypt} ${this.render_attach_app_button()} ${encrypt}
<button class="w3-button w3-theme-d1" @click=${this.discard}> <button class="w3-button w3-dark-grey" @click=${this.discard}>
Discard Discard
</button> </button>
</div> </div>

54
apps/ssb/tf-id-picker.js Normal file
View File

@@ -0,0 +1,54 @@
import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import {styles} from './tf-styles.js';
/*
** Provide a list of IDs, and this lets the user pick one.
*/
class TfIdentityPickerElement extends LitElement {
static get properties() {
return {
ids: {type: Array},
selected: {type: String},
users: {type: Object},
};
}
static styles = styles;
constructor() {
super();
this.ids = [];
this.users = {};
}
changed(event) {
this.selected = event.srcElement.value;
this.dispatchEvent(
new Event('change', {
srcElement: this,
})
);
}
render() {
return html`
<select
class="w3-select w3-dark-grey w3-padding w3-border"
@change=${this.changed}
style="max-width: 100%; overflow: hidden"
>
${(this.ids ?? []).map(
(id) =>
html`<option ?selected=${id == this.selected} value=${id}>
${this.users[id]?.name
? this.users[id]?.name + ' - '
: undefined}<small>${id}</small>
</option>`
)}
</select>
`;
}
}
customElements.define('tf-id-picker', TfIdentityPickerElement);

View File

@@ -1,4 +1,4 @@
import {LitElement, html, render, unsafeHTML} from './lit-all.min.js'; import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js'; import * as 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';
@@ -54,12 +54,6 @@ class TfMessageElement extends LitElement {
); );
} }
show_reactions() {
let modal = document.getElementById('reactions_modal');
modal.users = this.users;
modal.votes = this.message?.votes || [];
}
render_votes() { render_votes() {
function normalize_expression(expression) { function normalize_expression(expression) {
if (expression === 'Like' || !expression) { if (expression === 'Like' || !expression) {
@@ -72,21 +66,19 @@ class TfMessageElement extends LitElement {
return expression; return expression;
} }
} }
if (this.message?.votes?.length) { return html`<div>
return html`<div class="w3-button" @click=${this.show_reactions}> ${(this.message.votes || []).map(
${(this.message.votes || []).map( (vote) => html`
(vote) => html` <span
<span title="${this.users[vote.author]?.name ?? vote.author} ${new Date(
title="${this.users[vote.author]?.name ?? vote.author} ${new Date( vote.timestamp
vote.timestamp )}"
)}" >
> ${normalize_expression(vote.content.vote.expression)}
${normalize_expression(vote.content.vote.expression)} </span>
</span> `
` )}
)} </div>`;
</div>`;
}
} }
render_raw() { render_raw() {
@@ -133,7 +125,7 @@ class TfMessageElement extends LitElement {
} }
react(event) { react(event) {
emojis.picker((x) => this.vote(x), null, this.whoami); emojis.picker((x) => this.vote(x));
} }
show_image(link) { show_image(link) {
@@ -247,7 +239,9 @@ ${JSON.stringify(mention, null, 2)}</pre
if (mentions.length) { if (mentions.length) {
let self = this; let self = this;
return html` return html`
<fieldset style="padding: 0.5em; border: 1px solid black"> <fieldset
style="background-color: rgba(0, 0, 0, 0.1); padding: 0.5em; border: 1px solid black"
>
<legend>Mentions</legend> <legend>Mentions</legend>
${mentions.map((x) => self.render_mention(x))} ${mentions.map((x) => self.render_mention(x))}
</fieldset> </fieldset>
@@ -288,14 +282,14 @@ ${JSON.stringify(mention, null, 2)}</pre
if (this.message.child_messages?.length) { if (this.message.child_messages?.length) {
if (!this.expanded[this.message.id]) { if (!this.expanded[this.message.id]) {
return html`<button return html`<button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@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`<button return html`<button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => self.set_expanded(false)} @click=${() => self.set_expanded(false)}
> >
Collapse</button Collapse</button
@@ -337,23 +331,20 @@ ${JSON.stringify(mention, null, 2)}</pre
if (this.message?.decrypted?.type == 'post') { if (this.message?.decrypted?.type == 'post') {
content = this.message.decrypted; content = this.message.decrypted;
} }
let class_background = this.message?.decrypted
? 'w3-pale-red'
: 'w3-theme-d4';
let self = this; let self = this;
let raw_button; let raw_button;
switch (this.format) { switch (this.format) {
case 'raw': case 'raw':
if (content?.type == 'post' || content?.type == 'blog') { if (content?.type == 'post' || content?.type == 'blog') {
raw_button = html`<button raw_button = html`<button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => (self.format = 'md')} @click=${() => (self.format = 'md')}
> >
Markdown Markdown
</button>`; </button>`;
} else { } else {
raw_button = html`<button raw_button = html`<button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => (self.format = 'message')} @click=${() => (self.format = 'message')}
> >
Message Message
@@ -362,7 +353,7 @@ ${JSON.stringify(mention, null, 2)}</pre
break; break;
case 'md': case 'md':
raw_button = html`<button raw_button = html`<button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => (self.format = 'message')} @click=${() => (self.format = 'message')}
> >
Message Message
@@ -370,7 +361,7 @@ ${JSON.stringify(mention, null, 2)}</pre
break; break;
case 'decrypted': case 'decrypted':
raw_button = html`<button raw_button = html`<button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => (self.format = 'raw')} @click=${() => (self.format = 'raw')}
> >
Raw Raw
@@ -379,14 +370,14 @@ ${JSON.stringify(mention, null, 2)}</pre
default: default:
if (this.message.decrypted) { if (this.message.decrypted) {
raw_button = html`<button raw_button = html`<button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => (self.format = 'decrypted')} @click=${() => (self.format = 'decrypted')}
> >
Decrypted Decrypted
</button>`; </button>`;
} else { } else {
raw_button = html`<button raw_button = html`<button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => (self.format = 'raw')} @click=${() => (self.format = 'raw')}
> >
Raw Raw
@@ -398,8 +389,8 @@ ${JSON.stringify(mention, null, 2)}</pre
let body; let body;
return html` return html`
<div <div
class="w3-card-4 w3-theme-d4 w3-border-theme" class="w3-card-4"
style="margin-top: 8px; padding: 16px; display: inline-block; overflow-wrap: anywhere" style="background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; display: inline-block; overflow-wrap: anywhere"
> >
<tf-user id=${self.message.author} .users=${self.users}></tf-user> <tf-user id=${self.message.author} .users=${self.users}></tf-user>
<span style="padding-right: 8px" <span style="padding-right: 8px"
@@ -409,24 +400,13 @@ ${JSON.stringify(mention, null, 2)}</pre
> >
${raw_button} ${self.format == 'raw' ? self.render_raw() : inner} ${raw_button} ${self.format == 'raw' ? self.render_raw() : inner}
${self.render_votes()} ${self.render_votes()}
${(self.message.child_messages || []).map(
(x) => html`
<tf-message
.message=${x}
whoami=${self.whoami}
.users=${self.users}
.drafts=${self.drafts}
.expanded=${self.expanded}
></tf-message>
`
)}
</div> </div>
`; `;
} }
if (this.message?.type === 'contact_group') { if (this.message?.type === 'contact_group') {
return html` <div return html` <div
class="w3-card-4 w3-theme-d4 w3-border-theme" class="w3-card-4"
style="margin-top: 8px; padding: 16px; overflow-wrap: anywhere" style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
> >
${this.message.messages.map( ${this.message.messages.map(
(x) => (x) =>
@@ -441,8 +421,8 @@ ${JSON.stringify(mention, null, 2)}</pre
</div>`; </div>`;
} else if (this.message.placeholder) { } else if (this.message.placeholder) {
return html` <div return html` <div
class="w3-card-4 w3-theme-d4 w3-border-theme" class="w3-card-4"
style="margin-top: 8px; padding: 16px; overflow-wrap: anywhere" style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
> >
<a target="_top" href=${'#' + this.message.id}>${this.message.id}</a> <a target="_top" href=${'#' + this.message.id}>${this.message.id}</a>
(placeholder) (placeholder)
@@ -518,11 +498,13 @@ ${JSON.stringify(mention, null, 2)}</pre
branch=${this.message.id} branch=${this.message.id}
.drafts=${this.drafts} .drafts=${this.drafts}
@tf-discard=${this.discard_reply} @tf-discard=${this.discard_reply}
author=${this.message.author}
></tf-compose> ></tf-compose>
` `
: html` : html`
<button class="w3-button w3-theme-d1" @click=${this.show_reply}> <button
class="w3-button w3-dark-grey"
@click=${this.show_reply}
>
Reply Reply
</button> </button>
`; `;
@@ -551,7 +533,7 @@ ${JSON.stringify(content, null, 2)}</pre
} }
let content_warning = html` let content_warning = html`
<div <div
class="w3-panel w3-round-xlarge w3-theme-l4" class="w3-panel w3-round-xlarge w3-blue"
style="cursor: pointer" style="cursor: pointer"
@click=${(x) => this.toggle_expanded(':cw')} @click=${(x) => this.toggle_expanded(':cw')}
> >
@@ -571,6 +553,9 @@ ${JSON.stringify(content, null, 2)}</pre
let is_encrypted = this.message?.decrypted let is_encrypted = this.message?.decrypted
? html`<span style="align-self: center">🔓</span>` ? html`<span style="align-self: center">🔓</span>`
: undefined; : undefined;
let style_background = this.message?.decrypted
? 'rgba(255, 0, 0, 0.2)'
: 'rgba(255, 255, 255, 0.1)';
return html` return html`
<style> <style>
code { code {
@@ -587,8 +572,8 @@ ${JSON.stringify(content, null, 2)}</pre
} }
</style> </style>
<div <div
class="w3-card-4 ${class_background} w3-border-theme" class="w3-card-4"
style="margin-top: 8px; padding: 16px" style="border: 1px solid black; background-color: ${style_background}; margin-top: 8px; padding: 16px"
> >
<div style="display: flex; flex-direction: row"> <div style="display: flex; flex-direction: row">
<tf-user id=${this.message.author} .users=${this.users}></tf-user> <tf-user id=${this.message.author} .users=${this.users}></tf-user>
@@ -603,7 +588,7 @@ ${JSON.stringify(content, null, 2)}</pre
${payload} ${this.render_votes()} ${payload} ${this.render_votes()}
<p> <p>
${reply} ${reply}
<button class="w3-button w3-theme-d1" @click=${this.react}> <button class="w3-button w3-dark-grey" @click=${this.react}>
React React
</button> </button>
</p> </p>
@@ -614,6 +599,9 @@ ${JSON.stringify(content, null, 2)}</pre
let is_encrypted = this.message?.decrypted let is_encrypted = this.message?.decrypted
? html`<span style="align-self: center">🔓</span>` ? html`<span style="align-self: center">🔓</span>`
: undefined; : undefined;
let style_background = this.message?.decrypted
? 'rgba(255, 0, 0, 0.2)'
: 'rgba(255, 255, 255, 0.1)';
return html` return html`
<style> <style>
code { code {
@@ -630,8 +618,8 @@ ${JSON.stringify(content, null, 2)}</pre
} }
</style> </style>
<div <div
class="w3-card-4 ${class_background} w3-border-theme" class="w3-card-4"
style="margin-top: 8px; padding: 16px" style="border: 1px solid black; background-color: ${style_background}; margin-top: 8px; padding: 16px"
> >
<div style="display: flex; flex-direction: row"> <div style="display: flex; flex-direction: row">
<tf-user id=${this.message.author} .users=${this.users}></tf-user> <tf-user id=${this.message.author} .users=${this.users}></tf-user>
@@ -645,7 +633,7 @@ ${JSON.stringify(content, null, 2)}</pre
</div> </div>
${content.text} ${this.render_votes()} ${content.text} ${this.render_votes()}
<p> <p>
<button class="w3-button w3-theme-d1" @click=${this.react}> <button class="w3-button w3-dark-grey" @click=${this.react}>
React React
</button> </button>
</p> </p>
@@ -697,11 +685,13 @@ ${JSON.stringify(content, null, 2)}</pre
branch=${this.message.id} branch=${this.message.id}
.drafts=${this.drafts} .drafts=${this.drafts}
@tf-discard=${this.discard_reply} @tf-discard=${this.discard_reply}
author=${this.message.author}
></tf-compose> ></tf-compose>
` `
: html` : html`
<button class="w3-button w3-theme-d1" @click=${this.show_reply}> <button
class="w3-button w3-dark-grey"
@click=${this.show_reply}
>
Reply Reply
</button> </button>
`; `;
@@ -721,8 +711,8 @@ ${JSON.stringify(content, null, 2)}</pre
} }
</style> </style>
<div <div
class="w3-card-4 w3-theme-d4 w3-border-theme" class="w3-card-4"
style="margin-top: 8px; padding: 16px" style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px"
> >
<div style="display: flex; flex-direction: row"> <div style="display: flex; flex-direction: row">
<tf-user id=${this.message.author} .users=${this.users}></tf-user> <tf-user id=${this.message.author} .users=${this.users}></tf-user>
@@ -738,7 +728,7 @@ ${JSON.stringify(content, null, 2)}</pre
${this.render_mentions()} ${this.render_mentions()}
<div> <div>
${reply} ${reply}
<button class="w3-button w3-theme-d1" @click=${this.react}> <button class="w3-button w3-dark-grey" @click=${this.react}>
React React
</button> </button>
</div> </div>

View File

@@ -215,49 +215,49 @@ class TfProfileElement extends LitElement {
let server_follow; let server_follow;
if (this.server_follows_me === true) { if (this.server_follows_me === true) {
server_follow = html`<button server_follow = html`<button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => this.server_follow_me(false)} @click=${() => this.server_follow_me(false)}
> >
Server, Stop Following Me Server, Stop Following Me
</button>`; </button>`;
} else if (this.server_follows_me === false) { } else if (this.server_follows_me === false) {
server_follow = html`<button server_follow = html`<button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => this.server_follow_me(true)} @click=${() => this.server_follow_me(true)}
> >
Server, Follow Me Server, Follow Me
</button>`; </button>`;
} }
edit = html` edit = html`
<button class="w3-button w3-theme-d1" @click=${this.save_edits}> <button class="w3-button w3-dark-grey" @click=${this.save_edits}>
Save Profile Save Profile
</button> </button>
<button class="w3-button w3-theme-d1" @click=${this.discard_edits}> <button class="w3-button w3-dark-grey" @click=${this.discard_edits}>
Discard Discard
</button> </button>
${server_follow} ${server_follow}
`; `;
} else { } else {
edit = html`<button class="w3-button w3-theme-d1" @click=${this.edit}> edit = html`<button class="w3-button w3-dark-grey" @click=${this.edit}>
Edit Profile Edit Profile
</button>`; </button>`;
} }
} }
if (this.id !== this.whoami && this.following !== undefined) { if (this.id !== this.whoami && this.following !== undefined) {
follow = this.following follow = this.following
? html`<button class="w3-button w3-theme-d1" @click=${this.unfollow}> ? html`<button class="w3-button w3-dark-grey" @click=${this.unfollow}>
Unfollow Unfollow
</button>` </button>`
: html`<button class="w3-button w3-theme-d1" @click=${this.follow}> : html`<button class="w3-button w3-dark-grey" @click=${this.follow}>
Follow Follow
</button>`; </button>`;
} }
if (this.id !== this.whoami && this.blocking !== undefined) { if (this.id !== this.whoami && this.blocking !== undefined) {
block = this.blocking block = this.blocking
? html`<button class="w3-button w3-theme-d1" @click=${this.unblock}> ? html`<button class="w3-button w3-dark-grey" @click=${this.unblock}>
Unblock Unblock
</button>` </button>`
: html`<button class="w3-button w3-theme-d1" @click=${this.block}> : html`<button class="w3-button w3-dark-grey" @click=${this.block}>
Block Block
</button>`; </button>`;
} }
@@ -267,16 +267,16 @@ class TfProfileElement extends LitElement {
<div class="w3-container"> <div class="w3-container">
<div> <div>
<label for="name">Name:</label> <label for="name">Name:</label>
<input class="w3-input w3-theme-d1" type="text" id="name" value=${this.editing.name} @input=${(event) => (this.editing = Object.assign({}, this.editing, {name: event.srcElement.value}))}></input> <input class="w3-input w3-dark-grey" type="text" id="name" value=${this.editing.name} @input=${(event) => (this.editing = Object.assign({}, this.editing, {name: event.srcElement.value}))}></input>
</div> </div>
<div><label for="description">Description:</label></div> <div><label for="description">Description:</label></div>
<textarea class="w3-input w3-theme-d1" style="resize: vertical" rows="8" id="description" @input=${(event) => (this.editing = Object.assign({}, this.editing, {description: event.srcElement.value}))}>${this.editing.description}</textarea> <textarea class="w3-input w3-dark-grey" style="resize: vertical" rows="8" id="description" @input=${(event) => (this.editing = Object.assign({}, this.editing, {description: event.srcElement.value}))}>${this.editing.description}</textarea>
<div> <div>
<label for="public_web_hosting">Public Web Hosting:</label> <label for="public_web_hosting">Public Web Hosting:</label>
<input class="w3-check w3-theme-d1" type="checkbox" id="public_web_hosting" ?checked=${this.editing.publicWebHosting} @input=${(event) => (self.editing = Object.assign({}, self.editing, {publicWebHosting: event.srcElement.checked}))}></input> <input class="w3-check w3-dark-grey" type="checkbox" id="public_web_hosting" ?checked=${this.editing.publicWebHosting} @input=${(event) => (self.editing = Object.assign({}, self.editing, {publicWebHosting: event.srcElement.checked}))}></input>
</div> </div>
<div> <div>
<button class="w3-button w3-theme-d1" @click=${this.attach_image}>Attach Image</button> <button class="w3-button w3-dark-grey" @click=${this.attach_image}>Attach Image</button>
</div> </div>
</div> </div>
</div>` </div>`

View File

@@ -1,68 +0,0 @@
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import {styles} from './tf-styles.js';
class TfReactionsModalElement extends LitElement {
static get properties() {
return {
users: {type: Object},
votes: {type: Array},
};
}
static styles = styles;
constructor() {
super();
this.votes = [];
this.users = {};
}
clear() {
this.votes = [];
}
render() {
let self = this;
return this.votes?.length
? html` <div
class="w3-modal w3-animate-opacity"
style="display: block; box-sizing: border-box"
>
<div class="w3-modal-content w3-card-4 w3-theme-d1">
<div class="w3-container w3-padding">
<header class="w3-container">
<h2>Reactions</h2>
<span class="w3-button w3-display-topright" @click=${this.clear}
>&times;</span
>
</header>
<ul class="w3-theme-dark w3-container w3-ul">
${this.votes.map(
(x) => html`
<li class="w3-bar">
<span class="w3-bar-item"
>${x?.content?.vote?.expression}</span
>
<tf-user
class="w3-bar-item"
id=${x.author}
.users=${this.users}
></tf-user>
<span class="w3-bar-item w3-right"
>${new Date(x?.timestamp).toLocaleString()}</span
>
</li>
`
)}
</ul>
<footer class="w3-container w3-padding">
<button class="w3-button" @click=${this.clear}>Close</button>
</footer>
</div>
</div>
</div>`
: undefined;
}
}
customElements.define('tf-reactions-modal', TfReactionsModalElement);

File diff suppressed because it is too large Load Diff

View File

@@ -7,11 +7,9 @@ class TfTabConnectionsElement extends LitElement {
return { return {
broadcasts: {type: Array}, broadcasts: {type: Array},
identities: {type: Array}, identities: {type: Array},
my_identities: {type: Array},
connections: {type: Array}, connections: {type: Array},
stored_connections: {type: Array}, stored_connections: {type: Array},
users: {type: Object}, users: {type: Object},
server_identity: {type: String},
}; };
} }
@@ -22,31 +20,22 @@ class TfTabConnectionsElement extends LitElement {
let self = this; let self = this;
this.broadcasts = []; this.broadcasts = [];
this.identities = []; this.identities = [];
this.my_identities = [];
this.connections = []; this.connections = [];
this.stored_connections = []; this.stored_connections = [];
this.users = {}; this.users = {};
tfrpc.rpc.getIdentities().then(function (identities) {
self.my_identities = identities || [];
});
tfrpc.rpc.getAllIdentities().then(function (identities) { tfrpc.rpc.getAllIdentities().then(function (identities) {
self.identities = identities || []; self.identities = identities || [];
}); });
tfrpc.rpc.getStoredConnections().then(function (connections) { tfrpc.rpc.getStoredConnections().then(function (connections) {
self.stored_connections = connections || []; self.stored_connections = connections || [];
}); });
tfrpc.rpc.getServerIdentity().then(function (identity) {
self.server_identity = identity;
});
} }
render_connection_summary(connection) { render_connection_summary(connection) {
if (connection.address && connection.port) { if (connection.address && connection.port) {
return html`<div> return html`(<small>${connection.address}:${connection.port}</small>)`;
<small>${connection.address}:${connection.port}</small>
</div>`;
} else if (connection.tunnel) { } else if (connection.tunnel) {
return html`<div>room peer</div>`; return html`(room peer)`;
} else { } else {
return JSON.stringify(connection); return JSON.stringify(connection);
} }
@@ -72,7 +61,7 @@ class TfTabConnectionsElement extends LitElement {
return html` return html`
<li> <li>
<button <button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => self._tunnel(connection.tunnel.id, connection.pubkey)} @click=${() => self._tunnel(connection.tunnel.id, connection.pubkey)}
> >
Connect Connect
@@ -84,17 +73,15 @@ class TfTabConnectionsElement extends LitElement {
render_broadcast(connection) { render_broadcast(connection) {
return html` return html`
<li class="w3-bar" style="overflow: hidden; overflow-wrap: nowrap"> <li>
<button <button
class="w3-bar-item w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => tfrpc.rpc.connect(connection)} @click=${() => tfrpc.rpc.connect(connection)}
> >
Connect Connect
</button> </button>
<div class="w3-bar-item"> <tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user> ${this.render_connection_summary(connection)}
${this.render_connection_summary(connection)}
</div>
</li> </li>
`; `;
} }
@@ -105,19 +92,9 @@ class TfTabConnectionsElement extends LitElement {
} }
render_connection(connection) { render_connection(connection) {
let requests = Object.values(
connection.requests.reduce(function (accumulator, value) {
let key = `${value.name}:${Math.sign(value.request_number)}`;
if (!accumulator[key]) {
accumulator[key] = Object.assign({count: 0}, value);
}
accumulator[key].count++;
return accumulator;
}, {})
);
return html` return html`
<button <button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => tfrpc.rpc.closeConnection(connection.id)} @click=${() => tfrpc.rpc.closeConnection(connection.id)}
> >
Close Close
@@ -126,20 +103,6 @@ class TfTabConnectionsElement extends LitElement {
${connection.tunnel !== undefined ${connection.tunnel !== undefined
? '🚇' ? '🚇'
: html`(${connection.host}:${connection.port})`} : html`(${connection.host}:${connection.port})`}
<div>
${requests.map(
(x) => html`
<span class="w3-tag w3-small"
>${x.request_number > 0 ? '🟩' : '🟥'} ${x.name}
<span
class="w3-badge w3-white"
style=${x.count > 1 ? undefined : 'display: none'}
>${x.count}</span
></span
>
`
)}
</div>
<ul> <ul>
${this.connections ${this.connections
.filter((x) => x.tunnel === this.connections.indexOf(connection)) .filter((x) => x.tunnel === this.connections.indexOf(connection))
@@ -152,74 +115,56 @@ class TfTabConnectionsElement extends LitElement {
render() { render() {
let self = this; let self = this;
return html` return html`
<div class="w3-container" style="box-sizing: border-box"> <div class="w3-container">
<h2>New Connection</h2> <h2>New Connection</h2>
<textarea class="w3-input w3-theme-d1" id="code"></textarea> <textarea class="w3-input w3-dark-grey" id="code"></textarea>
<button <button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => @click=${() =>
tfrpc.rpc.connect(self.renderRoot.getElementById('code').value)} tfrpc.rpc.connect(self.renderRoot.getElementById('code').value)}
> >
Connect Connect
</button> </button>
<h2>Broadcasts</h2> <h2>Broadcasts</h2>
<ul class="w3-ul w3-border"> <ul>
${this.broadcasts ${this.broadcasts
.filter((x) => x.address) .filter((x) => x.address)
.map((x) => self.render_broadcast(x))} .map((x) => self.render_broadcast(x))}
</ul> </ul>
<h2>Connections</h2> <h2>Connections</h2>
<ul class="w3-ul w3-border"> <ul>
${this.connections ${this.connections
.filter((x) => x.tunnel === undefined) .filter((x) => x.tunnel === undefined)
.map( .map((x) => html` <li>${this.render_connection(x)}</li> `)}
(x) => html`
<li class="w3-bar">${this.render_connection(x)}</li>
`
)}
</ul> </ul>
<h2>Stored Connections</h2> <h2>Stored Connections (WIP)</h2>
<ul class="w3-ul w3-border"> <ul>
${this.stored_connections.map( ${this.stored_connections.map(
(x) => html` (x) => html`
<li class="w3-bar"> <li>
<button <button
class="w3-bar-item w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => self.forget_stored_connection(x)} @click=${() => self.forget_stored_connection(x)}
> >
Forget Forget
</button> </button>
<button <button
class="w3-bar-item w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${() => tfrpc.rpc.connect(x)} @click=${() => tfrpc.rpc.connect(x)}
> >
Connect Connect
</button> </button>
<div class="w3-bar-item"> ${x.address}:${x.port}
<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>
</li> </li>
` `
)} )}
</ul> </ul>
<h2>Local Accounts</h2> <h2>Local Accounts</h2>
<ul class="w3-ul w3-border"> <ul>
${this.identities.map( ${this.identities.map(
(x) => (x) =>
html`<li class="w3-bar"> html`<li><tf-user id=${x} .users=${this.users}></tf-user></li>`
${x == this.server_identity
? html`<span class="w3-tag w3-medium w3-round w3-theme-l1"
>🖥 local server</span
>`
: undefined}
${this.my_identities.indexOf(x) != -1
? html`<span class="w3-tag w3-medium w3-round w3-theme-d1"
>😎 you</span
>`
: undefined}
<tf-user id=${x} .users=${this.users}></tf-user>
</li>`
)} )}
</ul> </ul>
</div> </div>

View File

@@ -187,7 +187,7 @@ class TfTabNewsFeedElement extends LitElement {
if (!this.hash.startsWith('#@') && !this.hash.startsWith('#%')) { if (!this.hash.startsWith('#@') && !this.hash.startsWith('#%')) {
more = html` more = html`
<p> <p>
<button class="w3-button w3-theme-d1" @click=${this.load_more}> <button class="w3-button w3-dark-grey" @click=${this.load_more}>
Load More Load More
</button> </button>
</p> </p>

View File

@@ -12,7 +12,6 @@ class TfTabNewsElement extends LitElement {
following: {type: Array}, following: {type: Array},
drafts: {type: Object}, drafts: {type: Object},
expanded: {type: Object}, expanded: {type: Object},
loading: {type: Boolean},
}; };
} }
@@ -85,7 +84,10 @@ class TfTabNewsElement extends LitElement {
} else { } else {
delete this.drafts[id]; delete this.drafts[id];
} }
this.drafts = Object.assign({}, this.drafts); /* Only trigger a re-render if we're creating a new draft or discarding an old one. */
if ((previous !== undefined) != (event.detail.draft !== undefined)) {
this.drafts = Object.assign({}, this.drafts);
}
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts)); tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
} }
@@ -114,31 +116,17 @@ class TfTabNewsElement extends LitElement {
.users=${this.users} .users=${this.users}
></tf-profile>` ></tf-profile>`
: undefined; : undefined;
let edit_profile;
if (
!this.loading &&
this.users[this.whoami]?.name === undefined &&
this.hash.substring(1) != this.whoami
) {
edit_profile = html` <div
class="w3-panel w3-padding w3-round w3-card-4 w3-theme-l3"
>
Follow your identity link ☝️ above to edit your profile and set your
name.
</div>`;
}
return html` return html`
<p class="w3-bar"> <p class="w3-bar">
<button <button
class="w3-bar-item w3-button w3-theme-d1" class="w3-bar-item w3-button w3-dark-grey"
@click=${this.show_more} @click=${this.show_more}
> >
${this.new_messages_text()} ${this.new_messages_text()}
</button> </button>
</p> </p>
<div class="w3-bar"> <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}
</div> </div>
<div> <div>
<tf-compose <tf-compose

View File

@@ -110,14 +110,14 @@ class TfTabQueryElement extends LitElement {
<textarea <textarea
id="search" id="search"
rows="8" rows="8"
class="w3-input w3-theme-d1" class="w3-input w3-dark-grey"
style="flex: 1; resize: vertical" style="flex: 1; resize: vertical"
@keydown=${this.search_keydown} @keydown=${this.search_keydown}
> >
${this.query}</textarea ${this.query}</textarea
> >
<button <button
class="w3-button w3-theme-d1" class="w3-button w3-dark-grey"
@click=${(event) => @click=${(event) =>
self.search(self.renderRoot.getElementById('search').value)} self.search(self.renderRoot.getElementById('search').value)}
> >

View File

@@ -78,8 +78,8 @@ class TfTabSearchElement extends LitElement {
let self = this; let self = this;
return html` return html`
<div style="display: flex; flex-direction: row; gap: 4px"> <div style="display: flex; flex-direction: row; gap: 4px">
<input type="text" class="w3-input w3-theme-d1" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input> <input type="text" class="w3-input w3-dark-grey" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
<button class="w3-button w3-theme-d1" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button> <button class="w3-button w3-dark-grey" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button>
</div> </div>
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} @tf-expand=${this.on_expand}></tf-news> <tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} @tf-expand=${this.on_expand}></tf-news>
`; `;

View File

@@ -19,11 +19,6 @@ class TfUserElement extends LitElement {
} }
render() { render() {
let image = html`<span
class="w3-theme-light w3-circle"
style="display: inline-block; width: 2em; height: 2em; text-align: center; line-height: 2em"
>?</span
>`;
let name = this.users?.[this.id]?.name; let name = this.users?.[this.id]?.name;
name = name =
name !== undefined name !== undefined
@@ -31,20 +26,21 @@ class TfUserElement extends LitElement {
: html`<a target="_top" href=${'#' + this.id}>${this.id}</a>`; : html`<a target="_top" href=${'#' + this.id}>${this.id}</a>`;
if (this.users[this.id]) { if (this.users[this.id]) {
let image_link = this.users[this.id].image; let image = this.users[this.id].image;
image_link = image = typeof image == 'string' ? image : image?.link;
typeof image_link == 'string' ? image_link : image_link?.link; return html` <div style="display: inline-block; font-weight: bold">
if (image_link !== undefined) { <img
image = html`<img style="width: 2em; height: 2em; vertical-align: middle; border-radius: 50%"
class="w3-circle" ?hidden=${image === undefined}
style="width: 2em; height: 2em; vertical-align: middle" src="${image ? '/' + image + '/view' : undefined}"
src="/${image_link}/view" />
/>`; ${name}
} </div>`;
} else {
return html` <div style="display: inline-block; font-weight: bold">
${name}
</div>`;
} }
return html` <div style="display: inline-block; font-weight: bold">
${image} ${name}
</div>`;
} }
} }

View File

@@ -1,7 +1,6 @@
import * as linkify from './commonmark-linkify.js';
import * as hashtagify from './commonmark-hashtag.js'; import * as hashtagify from './commonmark-hashtag.js';
const k_code_classes = 'w3-theme-l4 w3-theme-border w3-round';
function image(node, entering) { function image(node, entering) {
if ( if (
node.firstChild?.type === 'text' && node.firstChild?.type === 'text' &&
@@ -62,32 +61,13 @@ function image(node, entering) {
} }
} }
function code(node) {
let attrs = this.attrs(node);
attrs.push(['class', k_code_classes]);
this.tag('code', attrs);
this.out(node.literal);
this.tag('/code');
}
function attrs(node) {
let result = commonmark.HtmlRenderer.prototype.attrs.bind(this)(node);
if (node.type == 'block_quote') {
result.push(['class', 'w3-theme-d1']);
} else if (node.type == 'code_block') {
result.push(['class', k_code_classes]);
}
return result;
}
export function markdown(md) { export function markdown(md) {
let reader = new commonmark.Parser({safe: true}); let reader = new commonmark.Parser({safe: true});
let writer = new commonmark.HtmlRenderer(); let writer = new commonmark.HtmlRenderer();
writer.image = image; writer.image = image;
writer.code = code;
writer.attrs = attrs;
let parsed = reader.parse(md || ''); let parsed = reader.parse(md || '');
parsed = hashtagify.transform(parsed); parsed = hashtagify.transform(parsed);
parsed = linkify.transform(parsed);
let walker = parsed.walker(); let walker = parsed.walker();
let event, node; let event, node;
while ((event = walker.next())) { while ((event = walker.next())) {

View File

@@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "👋", "emoji": "👋",
"previous": "&W5aJp2DgOW5rQ0AOIC9Ut3DpsahPrO6PjkJ1PQbNRdM=.sha256" "previous": "&zFISmRDAv+SXFonfZ9/sHNhrmMe+poTU22gwZzuSkT4=.sha256"
} }

View File

@@ -55,7 +55,7 @@
</p> </p>
<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://www.tildefriends.net/~cory/releases/"
><i class="fa fa-download"></i> Download</a ><i class="fa fa-download"></i> Download</a
> >
<a <a
@@ -63,11 +63,6 @@
href="https://www.tildefriends.net/~cory/apps/" href="https://www.tildefriends.net/~cory/apps/"
><i class="fa fa-link"></i> Try It</a ><i class="fa fa-link"></i> Try It</a
> >
<a
class="w3-button w3-black w3-padding-large"
href="https://dev.tildefriends.net/"
><i class="fa fa-mug-hot"></i> Code</a
>
</div> </div>
<div class="w3-col l4 m6"> <div class="w3-col l4 m6">
<img src="tildefriends.png" class="w3-image w3-right w3-hide-small" /> <img src="tildefriends.png" class="w3-image w3-right w3-hide-small" />
@@ -75,60 +70,6 @@
</div> </div>
</div> </div>
<!-- Getting Starting Section -->
<div class="w3-indigo w3-center">
<div class="w3-row-padding w3-padding-64">
<div class="w3-jumbo">
<i class="fa fa-rocket"></i> <b>Getting Started</b>
</div>
<div>
<h2>First-time user checklist:</h2>
<ol type="1" style="text-align: left">
<li>
<a href="https://dev.tildefriends.net/cory/tildefriends/releases"
>Download</a
>
Tilde Friends and run your own instance, or use
<a href="https://www.tildefriends.net/"
>https://www.tildefriends.net/</a
>.
</li>
<li>
Create an account to identify yourself with that instance by
username and password.
</li>
<li>
Create an SSB identity in the <b>ssb</b> app. This will generate a
keypair used to identify yourself to other users and sign your
messages so that they can be verified as from you.
</li>
<li>
Describe yourself in your profile in the <b>ssb</b> app. Give
yourself a name and an avatar if you like.
</li>
<li>
Connect to others. You will automatically discover peers on the
same instance and same network if there are any. Or use
<a href="https://github.com/staltz/ssb-room/blob/master/FAQ.md"
>rooms</a
>
and pubs to reach more distant users.
<a href="https://www.tildefriends.net/~cory/room/"
>tildefriends.net itself</a
>
operates as a room, so you can connect and see who else is online
and establish a connection.
</li>
<li>Follow people to grow your network.</li>
<li>
Use the <b>edit</b> link at the top of any page to start modifying
and making apps.
</li>
</ol>
</div>
</div>
</div>
<!-- SSB Section --> <!-- SSB Section -->
<div class="w3-light-grey"> <div class="w3-light-grey">
<div class="w3-row-padding w3-padding-64"> <div class="w3-row-padding w3-padding-64">
@@ -258,7 +199,7 @@
</div> </div>
<div class="w3-row" style="margin-top: 64px"> <div class="w3-row" style="margin-top: 64px">
<a href="https://codemirror.net/docs/changelog/" class="w3-col s3"> <a href="https://codemirror.net/5/" class="w3-col s3">
<i class="fa fa-keyboard w3-text-indigo w3-jumbo"></i> <i class="fa fa-keyboard w3-text-indigo w3-jumbo"></i>
<p>CodeMirror</p> <p>CodeMirror</p>
</a> </a>

View File

@@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "📝", "emoji": "📝",
"previous": "&DaYqKHRBKhjFGaOzbKZ1+/pLspJeEkDJYTF2B50tH6k=.sha256" "previous": "&DnfuAUGzzalSh9NgZXnzDc9Ru5aM0omfRJ4h27jYw4k=.sha256"
} }

View File

@@ -4,9 +4,6 @@ import * as utils from './utils.js';
let g_hash; let g_hash;
let g_collection_notifies = {}; let g_collection_notifies = {};
tfrpc.register(async function getActiveIdentity() {
return ssb.getActiveIdentity();
});
tfrpc.register(async function getOwnerIdentities() { tfrpc.register(async function getOwnerIdentities() {
return ssb.getOwnerIdentities(); return ssb.getOwnerIdentities();
}); });
@@ -57,9 +54,6 @@ core.register('message', async function message_handler(message) {
await tfrpc.rpc.hash_changed(message.hash); await tfrpc.rpc.hash_changed(message.hash);
} }
}); });
core.register('setActiveIdentity', async function setActiveIdentityHandler(id) {
await tfrpc.rpc.setActiveIdentity(id);
});
tfrpc.register(function set_hash(hash) { tfrpc.register(function set_hash(hash) {
if (g_hash != hash) { if (g_hash != hash) {

View File

@@ -10,6 +10,7 @@
window.litDisableBundleWarning = true; window.litDisableBundleWarning = true;
</script> </script>
<script src="tf-collection.js" type="module"></script> <script src="tf-collection.js" type="module"></script>
<script src="tf-id-picker.js" type="module"></script>
<script src="tf-wiki-doc.js" type="module"></script> <script src="tf-wiki-doc.js" type="module"></script>
<script src="tf-wiki-app.js" type="module"></script> <script src="tf-wiki-app.js" type="module"></script>
</body> </body>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

44
apps/wiki/tf-id-picker.js Normal file
View File

@@ -0,0 +1,44 @@
import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
/*
** Provide a list of IDs, and this lets the user pick one.
*/
class TfIdentityPickerElement extends LitElement {
static get properties() {
return {
ids: {type: Array},
selected: {type: String},
};
}
constructor() {
super();
this.ids = [];
}
changed(event) {
this.selected = event.srcElement.value;
this.dispatchEvent(
new Event('change', {
srcElement: this,
})
);
}
render() {
return html`
<link rel="stylesheet" href="tildefriends.css" />
<select @change=${this.changed} style="max-width: 100%">
${(this.ids ?? []).map(
(id) =>
html`<option ?selected=${id == this.selected} value=${id}>
${id}
</option>`
)}
</select>
`;
}
}
customElements.define('tf-id-picker', TfIdentityPickerElement);

View File

@@ -31,16 +31,13 @@ class TfCollectionsAppElement extends LitElement {
tfrpc.register(function hash_changed(hash) { tfrpc.register(function hash_changed(hash) {
self.notify_hash_changed(hash); self.notify_hash_changed(hash);
}); });
tfrpc.register(function setActiveIdentity(id) {
self.whoami = id;
});
tfrpc.rpc.get_hash().then((hash) => self.notify_hash_changed(hash)); tfrpc.rpc.get_hash().then((hash) => self.notify_hash_changed(hash));
} }
async load() { async load() {
this.ids = await tfrpc.rpc.getIdentities(); this.ids = await tfrpc.rpc.getIdentities();
this.owner_ids = await tfrpc.rpc.getOwnerIdentities(); this.owner_ids = await tfrpc.rpc.getOwnerIdentities();
this.whoami = await tfrpc.rpc.getActiveIdentity(); this.whoami = await tfrpc.rpc.localStorageGet('collections_whoami');
let ids = [...new Set([...this.owner_ids, this.whoami])].sort(); let ids = [...new Set([...this.owner_ids, this.whoami])].sort();
this.following = Object.keys(await tfrpc.rpc.following(ids, 1)).sort(); this.following = Object.keys(await tfrpc.rpc.following(ids, 1)).sort();
@@ -276,6 +273,9 @@ class TfCollectionsAppElement extends LitElement {
margin-right: 16px; margin-right: 16px;
} }
</style> </style>
<div>
<tf-id-picker .ids=${this.ids} selected=${this.whoami} @change=${this.on_whoami_changed} ?hidden=${!this.ids?.length}></tf-id-picker>
</div>
<div> <div>
${keyed( ${keyed(
this.whoami, this.whoami,

View File

@@ -50,7 +50,7 @@ function new_message() {
return g_new_message_promise; return g_new_message_promise;
} }
core.register('onMessage', function (id) { ssb.addEventListener('message', function (id) {
let resolve = g_new_message_resolve; let resolve = g_new_message_resolve;
g_new_message_promise = new Promise(function (resolve, reject) { g_new_message_promise = new Promise(function (resolve, reject) {
g_new_message_resolve = resolve; g_new_message_resolve = resolve;
@@ -96,7 +96,7 @@ export async function collection(
let rows = []; let rows = [];
await ssb.sqlAsync( await ssb.sqlAsync(
` `
SELECT messages.id, author, json(content) AS content, timestamp SELECT messages.id, author, content, timestamp
FROM messages FROM messages
JOIN json_each(?1) AS id ON messages.author = id.value JOIN json_each(?1) AS id ON messages.author = id.value
WHERE WHERE

View File

@@ -1,3 +1,4 @@
import * as auth from './auth.js';
import * as core from './core.js'; import * as core from './core.js';
let g_next_id = 1; let g_next_id = 1;
@@ -86,7 +87,8 @@ App.prototype.send = function (message) {
function socket(request, response, client) { function socket(request, response, client) {
let process; let process;
let options = {}; let options = {};
let credentials = httpd.auth_query(request.headers); let credentials = auth.query(request.headers);
let refresh = auth.makeRefresh(credentials);
response.onClose = async function () { response.onClose = async function () {
if (process && process.task) { if (process && process.task) {
@@ -141,21 +143,12 @@ function socket(request, response, client) {
} }
} }
response.send( response.send(
JSON.stringify( JSON.stringify({
Object.assign( action: 'session',
{ credentials: credentials,
action: 'session', parentApp: parentApp,
credentials: credentials, id: blobId,
parentApp: parentApp, }),
id: blobId,
},
await ssb.getIdentityInfo(
credentials?.session?.name,
packageOwner,
packageName
)
)
),
0x1 0x1
); );
@@ -219,10 +212,6 @@ function socket(request, response, client) {
if (process) { if (process) {
process.resetPermission(message.permission); process.resetPermission(message.permission);
} }
} else if (message.action == 'setActiveIdentity') {
process.setActiveIdentity(message.identity);
} else if (message.action == 'createIdentity') {
process.createIdentity();
} else if (message.message == 'tfrpc') { } else if (message.message == 'tfrpc') {
if (message.id && g_calls[message.id]) { if (message.id && g_calls[message.id]) {
if (message.error !== undefined) { if (message.error !== undefined) {
@@ -252,7 +241,14 @@ function socket(request, response, client) {
} }
}; };
response.upgrade(100, {}); response.upgrade(
100,
refresh
? {
'Set-Cookie': `session=${refresh.token}; path=/; Max-Age=${refresh.interval}; Secure; SameSite=Strict`,
}
: {}
);
} }
export {socket, App}; export {socket, App};

View File

@@ -19,11 +19,8 @@
Object.assign(app, g_data); Object.assign(app, g_data);
class TfAuthElement extends LitElement { class TfAuthElement extends LitElement {
static get properties() { static get_properties() {
return { return {
code_of_conduct: {type: String},
error: {type: String},
have_administrator: {type: Boolean},
name: {type: String}, name: {type: String},
tab: {type: String}, tab: {type: String},
}; };
@@ -34,6 +31,11 @@
this.tab = 'login'; this.tab = 'login';
} }
tab_changed(name) {
this.tab = name;
this.requestUpdate();
}
render() { render() {
let self = this; let self = this;
return html` return html`
@@ -81,16 +83,16 @@
<h1 ?hidden=${this.name === undefined}>Welcome, ${this.name}.</h1> <h1 ?hidden=${this.name === undefined}>Welcome, ${this.name}.</h1>
<div style="display: flex; flex-direction: row; width: 100%"> <div style="display: flex; flex-direction: row; width: 100%">
<input type="radio" name="tab" id="login" value="Login" ?checked=${this.tab == 'login'} @change=${() => (self.tab = 'login')}></input> <input type="radio" name="tab" id="login" value="Login" ?checked=${this.tab == 'login'} @change=${() => self.tab_changed('login')}></input>
<label for="login" id="login_label">Login</label> <label for="login" id="login_label">Login</label>
<input type="radio" name="tab" id="register" value="Register" ?checked=${this.tab == 'register'} @change=${() => (self.tab = 'register')}></input> <input type="radio" name="tab" id="register" value="Register" ?checked=${this.tab == 'register'} @change=${() => self.tab_changed('register')}></input>
<label for="register" id="register_label">Register</label> <label for="register" id="register_label">Register</label>
<input type="radio" name="tab" id="guest" value="Guest" ?checked=${this.tab == 'guest'} @change=${() => (self.tab = 'guest')}></input> <input type="radio" name="tab" id="guest" value="Guest" ?checked=${this.tab == 'guest'} @change=${() => self.tab_changed('guest')}></input>
<label for="guest" id="guest_label">Guest</label> <label for="guest" id="guest_label">Guest</label>
<input type="radio" name="tab" id="change" value="Change Password" ?checked=${this.tab == 'change'} @change=${() => (self.tab = 'change')}></input> <input type="radio" name="tab" id="change" value="Change Password" ?checked=${this.tab == 'change'} @change=${() => self.tab_changed('change')}></input>
<label for="change" id="change_label">Change Password</label> <label for="change" id="change_label">Change Password</label>
</div> </div>

420
core/auth.js Normal file
View File

@@ -0,0 +1,420 @@
import * as core from './core.js';
import * as form from './form.js';
let gDatabase = new Database('auth');
const kRefreshInterval = 1 * 7 * 24 * 60 * 60 * 1000;
/**
* Makes a Base64 value URL safe
* @param {string} value
* @returns TODOC
*/
function b64url(value) {
value = value.replaceAll('+', '-').replaceAll('/', '_');
let equals = value.indexOf('=');
if (equals !== -1) {
return value.substring(0, equals);
} else {
return value;
}
}
/**
* TODOC
* @param {string} value
* @returns
*/
function unb64url(value) {
value = value.replaceAll('-', '+').replaceAll('_', '/');
let remainder = value.length % 4;
if (remainder == 3) {
return value + '=';
} else if (remainder == 2) {
return value + '==';
} else {
return value;
}
}
/**
* Creates a JSON Web Token
* @param {object} payload Object: {"name": "username"}
* @returns the JWT
*/
function makeJwt(payload) {
const ids = ssb.getIdentities(':auth');
let id;
if (ids?.length) {
id = ids[0];
} else {
id = ssb.createIdentity(':auth');
}
const final_payload = b64url(
base64Encode(
JSON.stringify(
Object.assign({}, payload, {
exp: new Date().valueOf() + kRefreshInterval,
})
)
)
);
const jwt = [
b64url(base64Encode(JSON.stringify({alg: 'HS256', typ: 'JWT'}))),
final_payload,
b64url(ssb.hmacsha256sign(final_payload, ':auth', id)),
].join('.');
return jwt;
}
/**
* Validates a JWT ?
* @param {*} session TODOC
* @returns
*/
function readSession(session) {
let jwt_parts = session?.split('.');
if (jwt_parts?.length === 3) {
let [header, payload, signature] = jwt_parts;
header = JSON.parse(utf8Decode(base64Decode(unb64url(header))));
if (header.typ === 'JWT' && header.alg === 'HS256') {
signature = unb64url(signature);
let id = ssb.getIdentities(':auth');
if (id?.length && ssb.hmacsha256verify(id[0], payload, signature)) {
const result = JSON.parse(utf8Decode(base64Decode(unb64url(payload))));
const now = new Date().valueOf();
if (now < result.exp) {
print(`JWT valid for another ${(result.exp - now) / 1000} seconds.`);
return result;
} else {
print(`JWT expired by ${(now - result.exp) / 1000} seconds.`);
}
} else {
print('JWT verification failed.');
}
} else {
print('Invalid JWT header.');
}
}
}
/**
* Check the provided password matches the hash
* @param {string} password
* @param {string} hash bcrypt hash
* @returns true if the password matches the hash
*/
function verifyPassword(password, hash) {
return bCrypt.hashpw(password, hash) === hash;
}
/**
* Hashes a password
* @param {string} password
* @returns {string} TODOC
*/
function hashPassword(password) {
let salt = bCrypt.gensalt(12);
return bCrypt.hashpw(password, salt);
}
/**
* Check if there is an administrator on the instance
* @returns TODOC
*/
function noAdministrator() {
return (
!core.globalSettings ||
!core.globalSettings.permissions ||
!Object.keys(core.globalSettings.permissions).some(function (name) {
return (
core.globalSettings.permissions[name].indexOf('administration') != -1
);
})
);
}
/**
* Makes a user an administrator
* @param {string} name the user's name
*/
function makeAdministrator(name) {
if (!core.globalSettings.permissions) {
core.globalSettings.permissions = {};
}
if (!core.globalSettings.permissions[name]) {
core.globalSettings.permissions[name] = [];
}
if (core.globalSettings.permissions[name].indexOf('administration') == -1) {
core.globalSettings.permissions[name].push('administration');
}
core.setGlobalSettings(core.globalSettings);
}
/**
* TODOC
* @param {*} headers most likely an object
* @returns
*/
function getCookies(headers) {
let cookies = {};
if (headers.cookie) {
let parts = headers.cookie.split(/,|;/);
for (let i in parts) {
let equals = parts[i].indexOf('=');
let name = parts[i].substring(0, equals).trim();
let value = parts[i].substring(equals + 1).trim();
cookies[name] = value;
}
}
return cookies;
}
/**
* Validates a username
* @param {string} name
* @returns false | boolean[] ?
*/
function isNameValid(name) {
// TODO(tasiaiso): convert this into a regex
let c = name.charAt(0);
return (
((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) &&
name
.split()
.map(
(x) =>
x >= ('a' && x <= 'z') ||
x >= ('A' && x <= 'Z') ||
x >= ('0' && x <= '9')
)
);
}
/**
* Request handler ?
* @param {*} request TODOC
* @param {*} response
* @returns
*/
function handler(request, response) {
// TODO(tasiaiso): split this function
let session = getCookies(request.headers).session;
if (request.uri == '/login') {
let formData = form.decodeForm(request.query);
if (query(request.headers)?.permissions?.authenticated) {
if (formData.return) {
response.writeHead(303, {Location: formData.return});
} else {
response.writeHead(303, {
Location:
(request.client.tls ? 'https://' : 'http://') +
request.headers.host +
'/',
'Content-Length': '0',
});
}
response.end();
return;
}
let sessionIsNew = false;
let loginError;
if (request.method == 'POST' || formData.submit) {
sessionIsNew = true;
formData = form.decodeForm(utf8Decode(request.body), formData);
if (formData.submit == 'Login') {
let account = gDatabase.get('user:' + formData.name);
account = account ? JSON.parse(account) : account;
if (formData.register == '1') {
if (
!account &&
isNameValid(formData.name) &&
formData.password == formData.confirm
) {
let users = new Set();
let users_original = gDatabase.get('users');
try {
users = new Set(JSON.parse(users_original));
} catch {}
if (!users.has(formData.name)) {
users.add(formData.name);
}
users = JSON.stringify([...users].sort());
if (users !== users_original) {
gDatabase.set('users', users);
}
session = makeJwt({name: formData.name});
account = {password: hashPassword(formData.password)};
gDatabase.set('user:' + formData.name, JSON.stringify(account));
if (noAdministrator()) {
makeAdministrator(formData.name);
}
} else {
loginError = 'Error registering account.';
}
} else if (formData.change == '1') {
if (
account &&
isNameValid(formData.name) &&
formData.new_password == formData.confirm &&
verifyPassword(formData.password, account.password)
) {
session = makeJwt({name: formData.name});
account = {password: hashPassword(formData.new_password)};
gDatabase.set('user:' + formData.name, JSON.stringify(account));
} else {
loginError = 'Error changing password.';
}
} else {
if (
account &&
account.password &&
verifyPassword(formData.password, account.password)
) {
session = makeJwt({name: formData.name});
if (noAdministrator()) {
makeAdministrator(formData.name);
}
} else {
loginError = 'Invalid username or password.';
}
}
} else {
// Proceed as Guest
session = makeJwt({name: 'guest'});
}
}
let cookie = `session=${session}; path=/; Max-Age=${kRefreshInterval}; ${request.client.tls ? 'Secure; ' : ''}SameSite=Strict; HttpOnly`;
let entry = readSession(session);
if (entry && formData.return) {
response.writeHead(303, {
Location: formData.return,
'Set-Cookie': cookie,
});
response.end();
} else {
File.readFile('core/auth.html')
.then(function (data) {
let html = utf8Decode(data);
let auth_data = {
session_is_new: sessionIsNew,
name: entry?.name,
error: loginError,
code_of_conduct: core.globalSettings.code_of_conduct,
have_administrator: !noAdministrator(),
};
html = utf8Encode(
html.replace('$AUTH_DATA', JSON.stringify(auth_data))
);
response.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8',
'Set-Cookie': cookie,
'Content-Length': html.length,
});
response.end(html);
})
.catch(function (error) {
response.writeHead(404, {
'Content-Type': 'text/plain; charset=utf-8',
Connection: 'close',
});
response.end('404 File not found');
});
}
} else if (request.uri == '/login/logout') {
response.writeHead(303, {
'Set-Cookie': `session=; path=/; ${request.client.tls ? 'Secure; ' : ''}SameSite=Strict; expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly`,
Location: '/login' + (request.query ? '?' + request.query : ''),
});
response.end();
} else {
response.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8',
Connection: 'close',
});
response.end('Hello, ' + request.client.peerName + '.');
}
}
/**
* Gets a user's permissions based on it's session ?
* @param {*} session TODOC
* @returns
*/
function getPermissions(session) {
let permissions;
let entry = readSession(session);
if (entry) {
permissions = getPermissionsForUser(entry.name);
permissions.authenticated = entry.name !== 'guest';
}
return permissions || {};
}
/**
* Get a user's permissions ?
* @param {string} userName TODOC
* @returns
*/
function getPermissionsForUser(userName) {
let permissions = {};
if (
core.globalSettings &&
core.globalSettings.permissions &&
core.globalSettings.permissions[userName]
) {
for (let i in core.globalSettings.permissions[userName]) {
permissions[core.globalSettings.permissions[userName][i]] = true;
}
}
return permissions;
}
/**
* TODOC
* @param {*} headers
* @returns
*/
function query(headers) {
let session = getCookies(headers).session;
let entry;
let autologin = tildefriends.args.autologin;
if ((entry = autologin ? {name: autologin} : readSession(session))) {
return {
session: entry,
permissions: autologin
? getPermissionsForUser(autologin)
: getPermissions(session),
};
}
}
/**
* Refreshes a JWT ?
* @param {*} credentials TODOC
* @returns
*/
function makeRefresh(credentials) {
if (credentials?.session?.name) {
return {
token: makeJwt({name: credentials.session.name}),
interval: kRefreshInterval,
};
}
}
export {handler, query, makeRefresh};

View File

@@ -56,9 +56,6 @@ class TfNavigationElement extends LitElement {
spark_lines: {type: Object}, spark_lines: {type: Object},
version: {type: Object}, version: {type: Object},
show_version: {type: Boolean}, show_version: {type: Boolean},
identity: {type: String},
identities: {type: Array},
names: {type: Object},
}; };
} }
@@ -68,8 +65,6 @@ class TfNavigationElement extends LitElement {
this.show_permissions = false; this.show_permissions = false;
this.status = {}; this.status = {};
this.spark_lines = {}; this.spark_lines = {};
this.identities = [];
this.names = {};
} }
/** /**
@@ -102,10 +97,10 @@ class TfNavigationElement extends LitElement {
get_spark_line(key, options) { get_spark_line(key, options) {
if (!this.spark_lines[key]) { if (!this.spark_lines[key]) {
let spark_line = document.createElement('tf-sparkline'); let spark_line = document.createElement('tf-sparkline');
spark_line.style.display = 'flex';
spark_line.style.flexDirection = 'row';
spark_line.style.flex = '0 50 5em';
spark_line.title = key; spark_line.title = key;
spark_line.classList.add('w3-bar-item');
spark_line.classList.add('w3-hide-small');
spark_line.style.paddingRight = '0';
if (options) { if (options) {
if (options.max) { if (options.max) {
spark_line.max = options.max; spark_line.max = options.max;
@@ -123,108 +118,16 @@ class TfNavigationElement extends LitElement {
*/ */
render_login() { render_login() {
if (this?.credentials?.session?.name) { if (this?.credentials?.session?.name) {
return html`<a return html`<a id="login" href="/login/logout?return=${url() + hash()}"
class="w3-bar-item w3-right"
id="login"
href="/login/logout?return=${url() + hash()}"
>logout ${this.credentials.session.name}</a >logout ${this.credentials.session.name}</a
>`; >`;
} else { } else {
return html`<a return html`<a id="login" href="/login?return=${url() + hash()}"
class="w3-bar-item w3-right"
id="login"
href="/login?return=${url() + hash()}"
>login</a >login</a
>`; >`;
} }
} }
set_active_identity(id) {
send({action: 'setActiveIdentity', identity: id});
this.renderRoot.getElementById('id_dropdown').classList.remove('w3-show');
}
create_identity(event) {
if (confirm('Are you sure you want to create a new identity?')) {
send({action: 'createIdentity'});
}
}
toggle_id_dropdown() {
this.renderRoot.getElementById('id_dropdown').classList.toggle('w3-show');
}
edit_profile() {
window.location.href = '/~core/ssb/#' + this.identity;
}
render_identity() {
let self = this;
if (this.identities?.length) {
return html`
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
<div class="w3-dropdown-click w3-right" style="max-width: 100%">
<button
class="w3-button w3-rest w3-cyan"
style="text-overflow: ellipsis; overflow: hidden; white-space: nowrap; max-width: 100%"
@click=${self.toggle_id_dropdown}
>
${self.names[this.identity]}${self.names[this.identity] ===
this.identity
? ''
: html` - ${this.identity}`}
</button>
<div
id="id_dropdown"
class="w3-dropdown-content w3-bar-block w3-card-4"
style="max-width: 100%"
>
<button
class="w3-bar-item w3-button w3-border"
@click=${() => (window.location.href = '/~core/identity')}
>
Manage Identities...
</button>
<button
class="w3-bar-item w3-button w3-border"
@click=${self.edit_profile}
>
Edit Profile...
</button>
${this.identities.map(
(x) => html`
<button
class="w3-bar-item w3-button ${x === self.identity
? 'w3-cyan'
: ''}"
@click=${() => self.set_active_identity(x)}
style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap"
>
${self.names[x]}${self.names[x] === x ? '' : html` - ${x}`}
</button>
`
)}
</div>
</div>
`;
} else if (
this.credentials?.session?.name &&
this.credentials.session.name !== 'guest'
) {
return html`
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
<button
id="create_identity"
@click=${this.create_identity}
class="w3-button w3-mobile w3-blue w3-right"
>
Create an Identity
</button>
`;
}
}
/** /**
* TODOC * TODOC
* @returns * @returns
@@ -242,17 +145,11 @@ class TfNavigationElement extends LitElement {
<div>This app has the following permissions:</div> <div>This app has the following permissions:</div>
${Object.keys(this.permissions).map( ${Object.keys(this.permissions).map(
(key) => html` (key) => html`
<div> <div>
<span>${key}</span>: <span>${key}</span>: ${this.permissions[key] ? '✅ Allowed' : '❌ Denied'}
${this.permissions[key] ? '✅ Allowed' : '❌ Denied'} <button @click=${() => this.reset_permission(key)} class='w3-button w3-red">Reset</button>
<button </div>
@click=${() => this.reset_permission(key)} `
class="w3-button w3-red"
>
Reset
</button>
</div>
`
)} )}
<button <button
@click=${() => (this.show_permissions = false)} @click=${() => (this.show_permissions = false)}
@@ -266,10 +163,6 @@ class TfNavigationElement extends LitElement {
} }
} }
clear_error() {
this.status = {};
}
/** /**
* TODOC * TODOC
* @returns * @returns
@@ -277,7 +170,6 @@ class TfNavigationElement extends LitElement {
render() { render() {
let self = this; let self = this;
return html` return html`
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
<style> <style>
${k_global_style} .tooltip { ${k_global_style} .tooltip {
position: absolute; position: absolute;
@@ -293,17 +185,17 @@ class TfNavigationElement extends LitElement {
display: inline-block; display: inline-block;
} }
</style> </style>
<div class="w3-black w3-bar"> <div
style="margin: 4px; display: flex; flex-direction: row; flex-wrap: nowrap; gap: 3px; align-items: center"
>
<span <span
class="w3-bar-item"
style="cursor: pointer" style="cursor: pointer"
@click=${() => (this.show_version = !this.show_version)} @click=${() => (this.show_version = !this.show_version)}
>😎</span >😎</span
> >
<span <span
class="w3-bar-item" ?hidden=${!this.show_version}
style=${'white-space: nowrap' + style="flex: 0 0; white-space: nowrap"
(this.show_version ? '' : '; display: none')}
title=${this.version?.name + title=${this.version?.name +
' ' + ' ' +
Object.entries(this.version || {}) Object.entries(this.version || {})
@@ -312,7 +204,6 @@ class TfNavigationElement extends LitElement {
>${this.version?.number}</span >${this.version?.number}</span
> >
<a <a
class="w3-bar-item"
accesskey="h" accesskey="h"
@mouseover=${set_access_key_title} @mouseover=${set_access_key_title}
data-tip="Open home app." data-tip="Open home app."
@@ -321,7 +212,6 @@ class TfNavigationElement extends LitElement {
>TF</a >TF</a
> >
<a <a
class="w3-bar-item"
accesskey="a" accesskey="a"
@mouseover=${set_access_key_title} @mouseover=${set_access_key_title}
data-tip="Open apps list." data-tip="Open apps list."
@@ -329,7 +219,6 @@ class TfNavigationElement extends LitElement {
>apps</a >apps</a
> >
<a <a
class="w3-bar-item"
accesskey="e" accesskey="e"
@mouseover=${set_access_key_title} @mouseover=${set_access_key_title}
data-tip="Toggle the app editor." data-tip="Toggle the app editor."
@@ -338,7 +227,6 @@ class TfNavigationElement extends LitElement {
>edit</a >edit</a
> >
<a <a
class="w3-bar-item"
accesskey="p" accesskey="p"
@mouseover=${set_access_key_title} @mouseover=${set_access_key_title}
data-tip="View and change permissions." data-tip="View and change permissions."
@@ -346,34 +234,27 @@ class TfNavigationElement extends LitElement {
@click=${() => (self.show_permissions = !self.show_permissions)} @click=${() => (self.show_permissions = !self.show_permissions)}
>🎛️</a >🎛️</a
> >
<span
style="display: inline-block; vertical-align: top; white-space: pre; color: ${this
.status.color ?? kErrorColor}"
>${this.status.message}</span
>
<span id="requests"></span>
${this.render_permissions()} ${this.render_permissions()}
${this.status?.message && !this.status.is_error <span
? html` style="flex: 1 1; display: flex; flex-direction: row; white-space: nowrap; margin: 0; padding: 0"
<link type="text/css" rel="stylesheet" href="/static/w3.css" /> >${Object.keys(this.spark_lines)
<div .sort()
class="w3-bar-item" .map((x) => this.spark_lines[x])
style="color: ${this.status.color ?? kStatusColor}" .map((x) => [
> html`<span style="font-size: xx-small">${x.dataset.emoji}</span>`,
${this.status.message} x,
</div> ])}</span
` >
: undefined} <span style="flex: 0 0; white-space: nowrap"
${Object.keys(this.spark_lines) >${this.render_login()}</span
.sort() >
.map((x) => this.spark_lines[x])}
${this.render_login()} ${this.render_identity()}
</div> </div>
${this.status?.is_error
? html`
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
<div class="w3-model w3-animate-top" style="position: absolute; left: 50%; transform: translate(-50%); z-index: 1">
<dijv class="w3-modal-content w3-card-4" style="display: block; padding: 1em">
<span @click=${self.clear_error} class="w3-button w3-display-topright">&times;</span>
<div style="color: ${this.status.color ?? kErrorColor}"><b>ERROR:</b><p style="white-space: pre">${this.status.message}</p></div>
</div>
</div>
`
: undefined}
`; `;
} }
} }
@@ -697,13 +578,13 @@ class TfSparkLineElement extends LitElement {
) / 10.0; ) / 10.0;
return html` return html`
<svg <svg
style="max-width: 7.5em; margin: 0; padding: 0; background: #000; height: 1em" style="max-width: 7.5em; max-height: 1.5em; margin: 0; padding: 0; background: #000"
viewBox="0 0 50 10" viewBox="0 0 50 10"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
${this.lines.map((x) => this.render_line(x))} ${this.lines.map((x) => this.render_line(x))}
<text x="0" y="1em" style="font: 8px sans-serif; fill: #fff"> <text x="0" y="1em" style="font: 8px sans-serif; fill: #fff">
${this.dataset.emoji}${max} ${max}
</text> </text>
</svg> </svg>
`; `;
@@ -1119,9 +1000,9 @@ function api_postMessage(message) {
function api_error(error) { function api_error(error) {
if (error) { if (error) {
if (typeof error == 'string') { if (typeof error == 'string') {
setStatusMessage('⚠️ ' + error, kErrorColor); setStatusMessage('⚠️ ' + error, '#f00');
} else { } else {
setStatusMessage('⚠️ ' + error.message + '\n' + error.stack, kErrorColor); setStatusMessage('⚠️ ' + error.message + '\n' + error.stack, '#f00');
} }
} }
console.log('error', error); console.log('error', error);
@@ -1238,19 +1119,11 @@ function api_setHash(hash) {
function _receive_websocket_message(message) { function _receive_websocket_message(message) {
if (message && message.action == 'session') { if (message && message.action == 'session') {
setStatusMessage('🟢 Executing...', kStatusColor); setStatusMessage('🟢 Executing...', kStatusColor);
let navigation = document.getElementsByTagName('tf-navigation')[0]; document.getElementsByTagName('tf-navigation')[0].credentials =
navigation.credentials = message.credentials; message.credentials;
navigation.identities = message.identities;
navigation.identity = message.identity;
navigation.names = message.names;
} else if (message && message.action == 'permissions') { } else if (message && message.action == 'permissions') {
let navigation = document.getElementsByTagName('tf-navigation')[0]; document.getElementsByTagName('tf-navigation')[0].permissions =
navigation.permissions = message.permissions ?? {}; message.permissions ?? {};
} else if (message && message.action == 'identities') {
let navigation = document.getElementsByTagName('tf-navigation')[0];
navigation.identities = message.identities;
navigation.identity = message.identity;
navigation.names = message.names;
} else if (message && message.action == 'ready') { } else if (message && message.action == 'ready') {
setStatusMessage(null); setStatusMessage(null);
if (window.location.hash) { if (window.location.hash) {
@@ -1338,7 +1211,6 @@ function setStatusMessage(message, color) {
document.getElementsByTagName('tf-navigation')[0].status = { document.getElementsByTagName('tf-navigation')[0].status = {
message: message, message: message,
color: color, color: color,
is_error: color == kErrorColor,
}; };
} }

View File

@@ -1,4 +1,5 @@
import * as app from './app.js'; import * as app from './app.js';
import * as auth from './auth.js';
import * as form from './form.js'; import * as form from './form.js';
import * as http from './http.js'; import * as http from './http.js';
@@ -8,6 +9,116 @@ let gStatsTimer = false;
const k_content_security_policy = const k_content_security_policy =
'sandbox allow-downloads allow-top-navigation-by-user-activation'; 'sandbox allow-downloads allow-top-navigation-by-user-activation';
const k_mime_types = {
css: 'text/css',
html: 'text/html',
js: 'text/javascript',
json: 'text/json',
map: 'application/json',
svg: 'image/svg+xml',
};
const k_magic_bytes = [
{bytes: [0xff, 0xd8, 0xff, 0xdb], type: 'image/jpeg'},
{
bytes: [
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01,
],
type: 'image/jpeg',
},
{bytes: [0xff, 0xd8, 0xff, 0xee], type: 'image/jpeg'},
{
bytes: [
0xff,
0xd8,
0xff,
0xe1,
null,
null,
0x45,
0x78,
0x69,
0x66,
0x00,
0x00,
],
type: 'image/jpeg',
},
{bytes: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], type: 'image/png'},
{bytes: [0x47, 0x49, 0x46, 0x38, 0x37, 0x61], type: 'image/gif'},
{bytes: [0x47, 0x49, 0x46, 0x38, 0x39, 0x61], type: 'image/gif'},
{
bytes: [
0x52,
0x49,
0x46,
0x46,
null,
null,
null,
null,
0x57,
0x45,
0x42,
0x50,
],
type: 'image/webp',
},
{bytes: [0x3c, 0x73, 0x76, 0x67], type: 'image/svg+xml'},
{
bytes: [
null,
null,
null,
null,
0x66,
0x74,
0x79,
0x70,
0x6d,
0x70,
0x34,
0x32,
],
type: 'audio/mpeg',
},
{
bytes: [
null,
null,
null,
null,
0x66,
0x74,
0x79,
0x70,
0x69,
0x73,
0x6f,
0x6d,
],
type: 'video/mp4',
},
{
bytes: [
null,
null,
null,
null,
0x66,
0x74,
0x79,
0x70,
0x6d,
0x70,
0x34,
0x32,
],
type: 'video/mp4',
},
{bytes: [0x4d, 0x54, 0x68, 0x64], type: 'audio/midi'},
];
let k_static_files = [ let k_static_files = [
{uri: '/', path: 'index.html', type: 'text/html; charset=UTF-8'}, {uri: '/', path: 'index.html', type: 'text/html; charset=UTF-8'},
]; ];
@@ -134,7 +245,6 @@ function broadcastEvent(eventName, argv) {
} }
return Promise.all(promises); return Promise.all(promises);
} }
/** /**
* TODOC * TODOC
* @param {*} message * @param {*} message
@@ -156,34 +266,6 @@ function broadcast(message) {
return Promise.all(promises); return Promise.all(promises);
} }
/**
* TODOC
* @param {String} eventName
* @param {*} argv
* @returns
*/
function broadcastAppEventToUser(
user,
packageOwner,
packageName,
eventName,
argv
) {
let promises = [];
for (let process of Object.values(gProcesses)) {
if (
process.credentials?.session?.name === user &&
process.packageOwner == packageOwner &&
process.packageName == packageName
) {
if (process.eventHandlers[eventName]) {
promises.push(invoke(process.eventHandlers[eventName], argv));
}
}
}
return Promise.all(promises);
}
/** /**
* TODOC * TODOC
* @param {*} caller * @param {*} caller
@@ -279,8 +361,6 @@ async function getProcessBlob(blobId, key, options) {
process.key = key; process.key = key;
process.credentials = options.credentials || {}; process.credentials = options.credentials || {};
process.task = new Task(); process.task = new Task();
process.packageOwner = options.packageOwner;
process.packageName = options.packageName;
process.eventHandlers = {}; process.eventHandlers = {};
if (!options?.script || options?.script === 'app.js') { if (!options?.script || options?.script === 'app.js') {
process.app = new app.App(); process.app = new app.App();
@@ -429,67 +509,6 @@ async function getProcessBlob(blobId, key, options) {
url: options?.url, url: options?.url,
}, },
}; };
process.sendIdentities = async function () {
process.app.send(
Object.assign(
{
action: 'identities',
},
await ssb.getIdentityInfo(
process?.credentials?.session?.name,
options?.packageOwner,
options?.packageName
)
)
);
};
process.setActiveIdentity = async function (identity) {
if (
process?.credentials?.session?.name &&
options.packageOwner &&
options.packageName
) {
await new Database(process?.credentials?.session?.name).set(
`id:${options.packageOwner}:${options.packageName}`,
identity
);
}
process.sendIdentities();
broadcastAppEventToUser(
process?.credentials?.session?.name,
options.packageOwner,
options.packageName,
'setActiveIdentity',
[identity]
);
};
process.createIdentity = async function () {
if (
process.credentials &&
process.credentials.session &&
process.credentials.session.name &&
process.credentials.session.name !== 'guest'
) {
let id = ssb.createIdentity(process.credentials.session.name);
await process.sendIdentities();
broadcastAppEventToUser(
process?.credentials?.session?.name,
options.packageOwner,
options.packageName,
'setActiveIdentity',
[
await ssb.getActiveIdentity(
process.credentials?.session?.name,
options.packageOwner,
options.packageName
),
]
);
return id;
} else {
throw new Error('Must be signed-in to create an account.');
}
};
if (process.credentials?.permissions?.administration) { if (process.credentials?.permissions?.administration) {
imports.core.globalSettingsDescriptions = function () { imports.core.globalSettingsDescriptions = function () {
let settings = Object.assign({}, k_global_settings); let settings = Object.assign({}, k_global_settings);
@@ -560,7 +579,15 @@ async function getProcessBlob(blobId, key, options) {
Object.keys(ssb).map((key) => [key, ssb[key].bind(ssb)]) Object.keys(ssb).map((key) => [key, ssb[key].bind(ssb)])
); );
imports.ssb.port = tildefriends.ssb_port; imports.ssb.port = tildefriends.ssb_port;
imports.ssb.createIdentity = () => process.createIdentity(); imports.ssb.createIdentity = function () {
if (
process.credentials &&
process.credentials.session &&
process.credentials.session.name
) {
return ssb.createIdentity(process.credentials.session.name);
}
};
imports.ssb.addIdentity = function (id) { imports.ssb.addIdentity = function (id) {
if ( if (
process.credentials && process.credentials &&
@@ -587,13 +614,6 @@ async function getProcessBlob(blobId, key, options) {
}); });
} }
}; };
imports.ssb.setActiveIdentity = (id) => process.setActiveIdentity(id);
imports.ssb.getActiveIdentity = () =>
ssb.getActiveIdentity(
process.credentials?.session?.name,
options.packageOwner,
options.packageName
);
imports.ssb.getOwnerIdentities = function () { imports.ssb.getOwnerIdentities = function () {
if (options.packageOwner) { if (options.packageOwner) {
return ssb.getIdentities(options.packageOwner); return ssb.getIdentities(options.packageOwner);
@@ -678,9 +698,6 @@ async function getProcessBlob(blobId, key, options) {
); );
} }
}; };
imports.ssb.addEventListener = undefined;
imports.ssb.removeEventListener = undefined;
imports.ssb.getIdentityInfo = undefined;
imports.fetch = function (url, options) { imports.fetch = function (url, options) {
return http.fetch(url, options, gGlobalSettings.fetch_hosts); return http.fetch(url, options, gGlobalSettings.fetch_hosts);
}; };
@@ -833,6 +850,29 @@ function startsWithBytes(data, bytes) {
} }
} }
/**
* TODOC
* @param {*} path
* @returns
*/
function guessTypeFromName(path) {
let extension = path.split('.').pop();
return k_mime_types[extension];
}
/**
* TODOC
* @param {*} data
* @returns
*/
function guessTypeFromMagicBytes(data) {
for (let magic of k_magic_bytes) {
if (startsWithBytes(data, magic.bytes)) {
return magic.type;
}
}
}
/** /**
* TODOC * TODOC
* @param {*} response * @param {*} response
@@ -848,9 +888,7 @@ function sendData(response, data, type, headers, status_code) {
Object.assign( Object.assign(
{ {
'Content-Type': 'Content-Type':
type || type || guessTypeFromMagicBytes(data) || 'application/binary',
httpd.mime_type_from_magic_bytes(data) ||
'application/binary',
'Content-Length': data.byteLength, 'Content-Length': data.byteLength,
}, },
headers || {} headers || {}
@@ -929,7 +967,7 @@ async function useAppHandler(
}, },
respond: do_resolve, respond: do_resolve,
}, },
credentials: httpd.auth_query(headers), credentials: auth.query(headers),
packageOwner: packageOwner, packageOwner: packageOwner,
packageName: packageName, packageName: packageName,
} }
@@ -1060,7 +1098,7 @@ async function blobHandler(request, response, blobId, uri) {
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) { if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
let user = match[1]; let user = match[1];
let appName = match[2]; let appName = match[2];
let credentials = httpd.auth_query(request.headers); let credentials = auth.query(request.headers);
if ( if (
credentials && credentials &&
credentials.session && credentials.session &&
@@ -1123,7 +1161,7 @@ async function blobHandler(request, response, blobId, uri) {
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) { if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
let user = match[1]; let user = match[1];
let appName = match[2]; let appName = match[2];
let credentials = httpd.auth_query(request.headers); let credentials = auth.query(request.headers);
if ( if (
credentials && credentials &&
credentials.session && credentials.session &&
@@ -1219,9 +1257,7 @@ async function blobHandler(request, response, blobId, uri) {
'Content-Security-Policy': k_content_security_policy, 'Content-Security-Policy': k_content_security_policy,
}; };
data = await getBlobOrContent(id); data = await getBlobOrContent(id);
let type = let type = guessTypeFromName(uri) || guessTypeFromMagicBytes(data);
httpd.mime_type_from_extension(uri) ||
httpd.mime_type_from_magic_bytes(data);
sendData(response, data, type, headers); sendData(response, data, type, headers);
} }
} else { } else {
@@ -1230,10 +1266,6 @@ async function blobHandler(request, response, blobId, uri) {
} }
} }
ssb.addEventListener('message', function () {
broadcastEvent('onMessage', [...arguments]);
});
ssb.addEventListener('broadcasts', function () { ssb.addEventListener('broadcasts', function () {
broadcastEvent('onBroadcastsChanged', []); broadcastEvent('onBroadcastsChanged', []);
}); });
@@ -1302,10 +1334,39 @@ loadSettings()
if (tildefriends.https_port && gGlobalSettings.http_redirect) { if (tildefriends.https_port && gGlobalSettings.http_redirect) {
httpd.set_http_redirect(gGlobalSettings.http_redirect); httpd.set_http_redirect(gGlobalSettings.http_redirect);
} }
httpd.all('/login', auth.handler);
httpd.all('/login/logout', auth.handler);
httpd.all('/app/socket', app.socket); httpd.all('/app/socket', app.socket);
httpd.all('', function default_http_handler(request, response) { httpd.all('', function default_http_handler(request, response) {
let match; let match;
if ((match = /^(\/~[^\/]+\/[^\/]+)(\/?.*)$/.exec(request.uri))) { if (request.uri === '/' || request.uri === '') {
let host = request.headers['x-forwarded-host'] ?? request.headers.host;
try {
for (let line of (gGlobalSettings.index_map || '').split('\n')) {
let parts = line.split('=');
if (parts.length == 2 && host.match(new RegExp(parts[0], 'i'))) {
response.writeHead(303, {
Location:
(request.client.tls ? 'https://' : 'http://') +
host +
parts[1],
'Content-Length': '0',
});
return response.end();
}
}
} catch (e) {
print(e);
}
response.writeHead(303, {
Location:
(request.client.tls ? 'https://' : 'http://') +
host +
gGlobalSettings.index,
'Content-Length': '0',
});
return response.end();
} else if ((match = /^(\/~[^\/]+\/[^\/]+)(\/?.*)$/.exec(request.uri))) {
return blobHandler(request, response, match[1], match[2]); return blobHandler(request, response, match[1], match[2]);
} else if ( } else if (
(match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri)) (match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri))
@@ -1345,15 +1406,8 @@ loadSettings()
async function start_tls() { async function start_tls() {
const kCertificatePath = 'data/httpd/certificate.pem'; const kCertificatePath = 'data/httpd/certificate.pem';
const kPrivateKeyPath = 'data/httpd/privatekey.pem'; const kPrivateKeyPath = 'data/httpd/privatekey.pem';
let privateKey; let privateKey = utf8Decode(await File.readFile(kPrivateKeyPath));
let certificate; let certificate = utf8Decode(await File.readFile(kCertificatePath));
try {
privateKey = utf8Decode(await File.readFile(kPrivateKeyPath));
certificate = utf8Decode(await File.readFile(kCertificatePath));
} catch (e) {
print(`TLS disabled (${e.message}).`);
return;
}
let context = new TlsContext(); let context = new TlsContext();
context.setPrivateKey(privateKey); context.setPrivateKey(privateKey);
context.setCertificate(certificate); context.setCertificate(certificate);

View File

@@ -15,6 +15,22 @@ body {
margin: 0; margin: 0;
} }
a:link {
color: #268bd2;
}
a:visited {
color: #6c71c4;
}
a:hover {
color: #859900;
}
a:active {
color: #2aa198;
}
#logo { #logo {
vertical-align: middle; vertical-align: middle;
} }

View File

@@ -1,68 +0,0 @@
# How to upgrade to a newer version
# - Comment `src.hash`
# - Change `version`
# - Run `$ nix build`
# This will fetch the source code
# Since `hash` is not provided, nix will stop building and throw an error:
#
# error: hash mismatch in fixed-output derivation '/nix/store/fghi3ljs6fhz8pwm3dh73j5fwjpq5wbz-source.drv':
# specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
# got: sha256-+uthA1w8CmZfW+WOK9wYGl2fUl/k10ufOc8W+Pwa9iQ=
# error: 1 dependencies of derivation '/nix/store/imcwsw5r74vkd8r0qa2k7cys2xfgraaz-tildefriends-0.0.18.drv' failed to build
#
# - Change `src.hash` to the new one, ie `sha256-+uthA1w8CmZfW+WOK9wYGl2fUl/k10ufOc8W+Pwa9iQ=`
# - Uncomment `src.hash`
# - Build again, this time it should work.
# - Check the release notes, if there's a new dependency or a change to `GNUMakefile`, this file might need to be changed too.
# For more details, contact tasiaiso @ https://tilde.club/~tasiaiso/
#
# WARNING: currently it is pinned to `47838d5e482cb4aac40190fa0414f08b8cf94d40`. I couldn't get v0.0.18 to work for some reason.
# I'll change this in the next release - tasiaiso
{
pkgs ? import <nixpkgs> {},
lib ? import <nixpkgs/lib>,
}:
pkgs.stdenv.mkDerivation rec {
pname = "tildefriends";
version = "0.0.19-wip";
src = pkgs.fetchFromGitea {
domain = "dev.tildefriends.net";
owner = "cory";
repo = "tildefriends";
# rev = "v${version}";
rev = "47838d5e482cb4aac40190fa0414f08b8cf94d40";
hash = "sha256-mb5KYvWPIqgV64FOaXKHm2ownBJiiSRtdH8+YWiXwvE="; # 47838d5e482cb4aac40190fa0414f08b8cf94d40
fetchSubmodules = true;
};
nativeBuildInputs = with pkgs; [
gnumake
openssl
which
];
buildInputs = with pkgs; [
openssl
which
];
buildPhase = ''
make -j $NIX_BUILD_CORES release
'';
installPhase = ''
mkdir -p $out/bin
cp -r out/release/tildefriends $out/bin
'';
doCheck = false;
meta = with pkgs; {
homepage = "https://tildefriends.net";
description = "Make apps and friends from the comfort of your web browser.";
mainProgram = "tildefriends";
license = with lib.licenses; [mit];
platforms = lib.platforms.all;
};
}

File diff suppressed because one or more lines are too long

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

@@ -19,9 +19,9 @@
} }
}, },
"node_modules/@codemirror/autocomplete": { "node_modules/@codemirror/autocomplete": {
"version": "6.16.0", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.16.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.15.0.tgz",
"integrity": "sha512-P/LeCTtZHRTCU4xQsa89vSKWecYv1ZqwzOd5topheGRf+qtacFgBeIMQi3eL8Kt/BUNvxUWkx+5qP2jlGoARrg==", "integrity": "sha512-G2Zm0mXznxz97JhaaOdoEG2cVupn4JjPaS4AcNvZzhOsnnG9YVN68VzfoUw6dYTsIxT6a/cmoFEN47KAWhXaOg==",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
@@ -36,9 +36,9 @@
} }
}, },
"node_modules/@codemirror/commands": { "node_modules/@codemirror/commands": {
"version": "6.5.0", "version": "6.3.3",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.5.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.3.3.tgz",
"integrity": "sha512-rK+sj4fCAN/QfcY9BEzYMgp4wwL/q5aj/VfNSoH1RWPF9XS/dUwBkvlL3hpWgEjOqlpdN1uLC9UkjJ4tmyjJYg==", "integrity": "sha512-dO4hcF0fGT9tu1Pj1D2PvGvxjeGkbC6RGcZw6Qs74TH+Ed1gw98jmUgd2axWvIZEqTeTuFrg1lEB1KV6cK9h1A==",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0", "@codemirror/state": "^6.4.0",
@@ -59,9 +59,9 @@
} }
}, },
"node_modules/@codemirror/lang-html": { "node_modules/@codemirror/lang-html": {
"version": "6.4.9", "version": "6.4.8",
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.8.tgz",
"integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==", "integrity": "sha512-tE2YK7wDlb9ZpAH6mpTPiYm6rhfdQKVDa5r9IwIFlwwgvVaKsCfuKKZoJGWsmMZIf3FQAuJ5CHMPLymOtg1hXw==",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.0.0", "@codemirror/autocomplete": "^6.0.0",
"@codemirror/lang-css": "^6.0.0", "@codemirror/lang-css": "^6.0.0",
@@ -111,9 +111,9 @@
} }
}, },
"node_modules/@codemirror/lint": { "node_modules/@codemirror/lint": {
"version": "6.7.0", "version": "6.5.0",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.7.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.5.0.tgz",
"integrity": "sha512-LTLOL2nT41ADNSCCCCw8Q/UmdAFzB23OUYSjsHTdsVaH0XEo+orhuqbDNWzrzodm14w6FOxqxpmy4LF8Lixqjw==", "integrity": "sha512-+5YyicIaaAZKU8K43IQi8TBy6mF6giGeWAH7N96Z5LC30Wm5JMjqxOYIE9mxwMG1NbhT2mA3l9hA4uuKUM3E5g==",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0", "@codemirror/view": "^6.0.0",
@@ -147,9 +147,9 @@
} }
}, },
"node_modules/@codemirror/view": { "node_modules/@codemirror/view": {
"version": "6.26.3", "version": "6.25.1",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.3.tgz", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.25.1.tgz",
"integrity": "sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==", "integrity": "sha512-2LXLxsQnHDdfGzDvjzAwZh2ZviNJm7im6tGpa0IONIDnFd8RZ80D2SNi8PDi6YjKcMoMRK20v6OmKIdsrwsyoQ==",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.4.0", "@codemirror/state": "^6.4.0",
"style-mod": "^4.1.0", "style-mod": "^4.1.0",
@@ -248,9 +248,9 @@
} }
}, },
"node_modules/@lezer/javascript": { "node_modules/@lezer/javascript": {
"version": "1.4.15", "version": "1.4.13",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.15.tgz", "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.13.tgz",
"integrity": "sha512-B082ZdjI0vo2AgLqD834GlRTE9gwRX8NzHzKq5uDwEnQ9Dq+A/CEhd3nf68tiNA2f9O+8jS1NeSTUYT9IAqcTw==", "integrity": "sha512-5IBr8LIO3xJdJH1e9aj/ZNLE4LSbdsx25wFmGRAZsj2zSmwAYjx26JyU/BYOCpRQlu1jcv1z3vy4NB9+UkfRow==",
"dependencies": { "dependencies": {
"@lezer/common": "^1.2.0", "@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.1.3", "@lezer/highlight": "^1.1.3",
@@ -343,9 +343,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.17.2", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz",
"integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==", "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -355,9 +355,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.17.2", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz",
"integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==", "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -367,9 +367,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.17.2", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz",
"integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==", "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -379,9 +379,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.17.2", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz",
"integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==", "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -391,21 +391,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.17.2", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz",
"integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==", "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz",
"integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -415,9 +403,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.17.2", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz",
"integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==", "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -427,9 +415,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.17.2", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz",
"integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==", "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -438,22 +426,10 @@
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz",
"integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==",
"cpu": [
"ppc64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.17.2", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz",
"integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==", "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -462,22 +438,10 @@
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz",
"integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==",
"cpu": [
"s390x"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.17.2", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz",
"integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==", "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -487,9 +451,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.17.2", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz",
"integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==", "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -499,9 +463,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.17.2", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz",
"integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==", "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -511,9 +475,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.17.2", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz",
"integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==", "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -523,9 +487,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.17.2", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz",
"integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==", "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -715,9 +679,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.17.2", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz",
"integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==", "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==",
"dependencies": { "dependencies": {
"@types/estree": "1.0.5" "@types/estree": "1.0.5"
}, },
@@ -729,22 +693,19 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.17.2", "@rollup/rollup-android-arm-eabi": "4.13.0",
"@rollup/rollup-android-arm64": "4.17.2", "@rollup/rollup-android-arm64": "4.13.0",
"@rollup/rollup-darwin-arm64": "4.17.2", "@rollup/rollup-darwin-arm64": "4.13.0",
"@rollup/rollup-darwin-x64": "4.17.2", "@rollup/rollup-darwin-x64": "4.13.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.17.2", "@rollup/rollup-linux-arm-gnueabihf": "4.13.0",
"@rollup/rollup-linux-arm-musleabihf": "4.17.2", "@rollup/rollup-linux-arm64-gnu": "4.13.0",
"@rollup/rollup-linux-arm64-gnu": "4.17.2", "@rollup/rollup-linux-arm64-musl": "4.13.0",
"@rollup/rollup-linux-arm64-musl": "4.17.2", "@rollup/rollup-linux-riscv64-gnu": "4.13.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.17.2", "@rollup/rollup-linux-x64-gnu": "4.13.0",
"@rollup/rollup-linux-riscv64-gnu": "4.17.2", "@rollup/rollup-linux-x64-musl": "4.13.0",
"@rollup/rollup-linux-s390x-gnu": "4.17.2", "@rollup/rollup-win32-arm64-msvc": "4.13.0",
"@rollup/rollup-linux-x64-gnu": "4.17.2", "@rollup/rollup-win32-ia32-msvc": "4.13.0",
"@rollup/rollup-linux-x64-musl": "4.17.2", "@rollup/rollup-win32-x64-msvc": "4.13.0",
"@rollup/rollup-win32-arm64-msvc": "4.17.2",
"@rollup/rollup-win32-ia32-msvc": "4.17.2",
"@rollup/rollup-win32-x64-msvc": "4.17.2",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@@ -778,9 +739,9 @@
} }
}, },
"node_modules/smob": { "node_modules/smob": {
"version": "1.5.0", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz",
"integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", "integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==",
"dev": true "dev": true
}, },
"node_modules/source-map": { "node_modules/source-map": {
@@ -819,9 +780,9 @@
} }
}, },
"node_modules/terser": { "node_modules/terser": {
"version": "5.31.0", "version": "5.29.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz",
"integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", "integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@jridgewell/source-map": "^0.3.3", "@jridgewell/source-map": "^0.3.3",

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

2
deps/quickjs vendored

33
deps/sqlite/shell.c vendored
View File

@@ -14753,15 +14753,6 @@ static void dbdataValue(
} }
} }
/* This macro is a copy of the MX_CELL() macro in the SQLite core. Given
** a page-size, it returns the maximum number of cells that may be present
** on the page. */
#define DBDATA_MX_CELL(pgsz) ((pgsz-8)/6)
/* Maximum number of fields that may appear in a single record. This is
** the "hard-limit", according to comments in sqliteLimit.h. */
#define DBDATA_MX_FIELD 32676
/* /*
** Move an sqlite_dbdata or sqlite_dbptr cursor to the next entry. ** Move an sqlite_dbdata or sqlite_dbptr cursor to the next entry.
*/ */
@@ -14790,9 +14781,6 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
assert( iOff+3+2<=pCsr->nPage ); assert( iOff+3+2<=pCsr->nPage );
pCsr->iCell = pTab->bPtr ? -2 : 0; pCsr->iCell = pTab->bPtr ? -2 : 0;
pCsr->nCell = get_uint16(&pCsr->aPage[iOff+3]); pCsr->nCell = get_uint16(&pCsr->aPage[iOff+3]);
if( pCsr->nCell>DBDATA_MX_CELL(pCsr->nPage) ){
pCsr->nCell = DBDATA_MX_CELL(pCsr->nPage);
}
} }
if( pTab->bPtr ){ if( pTab->bPtr ){
@@ -14837,19 +14825,19 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
if( pCsr->iCell>=pCsr->nCell ){ if( pCsr->iCell>=pCsr->nCell ){
bNextPage = 1; bNextPage = 1;
}else{ }else{
int iCellPtr = iOff + 8 + nPointer + pCsr->iCell*2;
if( iCellPtr>pCsr->nPage ){ iOff += 8 + nPointer + pCsr->iCell*2;
if( iOff>pCsr->nPage ){
bNextPage = 1; bNextPage = 1;
}else{ }else{
iOff = get_uint16(&pCsr->aPage[iCellPtr]); iOff = get_uint16(&pCsr->aPage[iOff]);
} }
/* For an interior node cell, skip past the child-page number */ /* For an interior node cell, skip past the child-page number */
iOff += nPointer; iOff += nPointer;
/* Load the "byte of payload including overflow" field */ /* Load the "byte of payload including overflow" field */
if( bNextPage || iOff>pCsr->nPage || iOff<=iCellPtr ){ if( bNextPage || iOff>pCsr->nPage ){
bNextPage = 1; bNextPage = 1;
}else{ }else{
iOff += dbdataGetVarintU32(&pCsr->aPage[iOff], &nPayload); iOff += dbdataGetVarintU32(&pCsr->aPage[iOff], &nPayload);
@@ -14932,9 +14920,7 @@ static int dbdataNext(sqlite3_vtab_cursor *pCursor){
pCsr->iField++; pCsr->iField++;
if( pCsr->iField>0 ){ if( pCsr->iField>0 ){
sqlite3_int64 iType; sqlite3_int64 iType;
if( pCsr->pHdrPtr>=&pCsr->pRec[pCsr->nRec] if( pCsr->pHdrPtr>&pCsr->pRec[pCsr->nRec] ){
|| pCsr->iField>=DBDATA_MX_FIELD
){
bNextPage = 1; bNextPage = 1;
}else{ }else{
int szField = 0; int szField = 0;
@@ -16422,7 +16408,7 @@ static int recoverWriteSchema1(sqlite3_recover *p){
if( bTable && !bVirtual ){ if( bTable && !bVirtual ){
if( SQLITE_ROW==sqlite3_step(pTblname) ){ if( SQLITE_ROW==sqlite3_step(pTblname) ){
const char *zTbl = (const char*)sqlite3_column_text(pTblname, 0); const char *zTbl = (const char*)sqlite3_column_text(pTblname, 0);
if( zTbl ) recoverAddTable(p, zTbl, iRoot); recoverAddTable(p, zTbl, iRoot);
} }
recoverReset(p, pTblname); recoverReset(p, pTblname);
} }
@@ -28785,7 +28771,6 @@ static const char zOptions[] =
" -newline SEP set output row separator. Default: '\\n'\n" " -newline SEP set output row separator. Default: '\\n'\n"
" -nofollow refuse to open symbolic links to database files\n" " -nofollow refuse to open symbolic links to database files\n"
" -nonce STRING set the safe-mode escape nonce\n" " -nonce STRING set the safe-mode escape nonce\n"
" -no-rowid-in-view Disable rowid-in-view using sqlite3_config()\n"
" -nullvalue TEXT set text string for NULL values. Default ''\n" " -nullvalue TEXT set text string for NULL values. Default ''\n"
" -pagecache SIZE N use N slots of SZ bytes each for page cache memory\n" " -pagecache SIZE N use N slots of SZ bytes each for page cache memory\n"
" -pcachetrace trace all page cache operations\n" " -pcachetrace trace all page cache operations\n"
@@ -29076,10 +29061,6 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
stdin_is_interactive = 0; stdin_is_interactive = 0;
}else if( cli_strcmp(z,"-utf8")==0 ){ }else if( cli_strcmp(z,"-utf8")==0 ){
}else if( cli_strcmp(z,"-no-utf8")==0 ){ }else if( cli_strcmp(z,"-no-utf8")==0 ){
}else if( cli_strcmp(z,"-no-rowid-in-view")==0 ){
int val = 0;
sqlite3_config(SQLITE_CONFIG_ROWID_IN_VIEW, &val);
assert( val==0 );
}else if( cli_strcmp(z,"-heap")==0 ){ }else if( cli_strcmp(z,"-heap")==0 ){
#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) #if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
const char *zSize; const char *zSize;
@@ -29355,8 +29336,6 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
/* already handled */ /* already handled */
}else if( cli_strcmp(z,"-no-utf8")==0 ){ }else if( cli_strcmp(z,"-no-utf8")==0 ){
/* already handled */ /* already handled */
}else if( cli_strcmp(z,"-no-rowid-in-view")==0 ){
/* already handled */
}else if( cli_strcmp(z,"-heap")==0 ){ }else if( cli_strcmp(z,"-heap")==0 ){
i++; i++;
}else if( cli_strcmp(z,"-pagecache")==0 ){ }else if( cli_strcmp(z,"-pagecache")==0 ){

205
deps/sqlite/sqlite3.c vendored
View File

@@ -1,6 +1,6 @@
/****************************************************************************** /******************************************************************************
** This file is an amalgamation of many separate C source files from SQLite ** This file is an amalgamation of many separate C source files from SQLite
** version 3.45.3. By combining all the individual C code files into this ** version 3.45.2. By combining all the individual C code files into this
** single large file, the entire code can be compiled as a single translation ** single large file, the entire code can be compiled as a single translation
** unit. This allows many compilers to do optimizations that would not be ** unit. This allows many compilers to do optimizations that would not be
** possible if the files were compiled separately. Performance improvements ** possible if the files were compiled separately. Performance improvements
@@ -18,7 +18,7 @@
** separate file. This file contains only code for the core SQLite library. ** separate file. This file contains only code for the core SQLite library.
** **
** The content in this amalgamation comes from Fossil check-in ** The content in this amalgamation comes from Fossil check-in
** 8653b758870e6ef0c98d46b3ace27849054a. ** d8cd6d49b46a395b13955387d05e9e1a2a47.
*/ */
#define SQLITE_CORE 1 #define SQLITE_CORE 1
#define SQLITE_AMALGAMATION 1 #define SQLITE_AMALGAMATION 1
@@ -459,9 +459,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.45.3" #define SQLITE_VERSION "3.45.2"
#define SQLITE_VERSION_NUMBER 3045003 #define SQLITE_VERSION_NUMBER 3045002
#define SQLITE_SOURCE_ID "2024-04-15 13:34:05 8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355" #define SQLITE_SOURCE_ID "2024-03-12 11:06:23 d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77"
/* /*
** CAPI3REF: Run-Time Library Version Numbers ** CAPI3REF: Run-Time Library Version Numbers
@@ -2456,22 +2456,6 @@ struct sqlite3_mem_methods {
** configuration setting is never used, then the default maximum is determined ** configuration setting is never used, then the default maximum is determined
** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option. If that ** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option. If that
** compile-time option is not set, then the default maximum is 1073741824. ** compile-time option is not set, then the default maximum is 1073741824.
**
** [[SQLITE_CONFIG_ROWID_IN_VIEW]]
** <dt>SQLITE_CONFIG_ROWID_IN_VIEW
** <dd>The SQLITE_CONFIG_ROWID_IN_VIEW option enables or disables the ability
** for VIEWs to have a ROWID. The capability can only be enabled if SQLite is
** compiled with -DSQLITE_ALLOW_ROWID_IN_VIEW, in which case the capability
** defaults to on. This configuration option queries the current setting or
** changes the setting to off or on. The argument is a pointer to an integer.
** If that integer initially holds a value of 1, then the ability for VIEWs to
** have ROWIDs is activated. If the integer initially holds zero, then the
** ability is deactivated. Any other initial value for the integer leaves the
** setting unchanged. After changes, if any, the integer is written with
** a 1 or 0, if the ability for VIEWs to have ROWIDs is on or off. If SQLite
** is compiled without -DSQLITE_ALLOW_ROWID_IN_VIEW (which is the usual and
** recommended case) then the integer is always filled with zero, regardless
** if its initial value.
** </dl> ** </dl>
*/ */
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
@@ -2503,7 +2487,6 @@ struct sqlite3_mem_methods {
#define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */ #define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */
#define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */ #define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */
#define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */ #define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */
#define SQLITE_CONFIG_ROWID_IN_VIEW 30 /* int* */
/* /*
** CAPI3REF: Database Connection Configuration Options ** CAPI3REF: Database Connection Configuration Options
@@ -18447,15 +18430,6 @@ struct Table {
#define HasRowid(X) (((X)->tabFlags & TF_WithoutRowid)==0) #define HasRowid(X) (((X)->tabFlags & TF_WithoutRowid)==0)
#define VisibleRowid(X) (((X)->tabFlags & TF_NoVisibleRowid)==0) #define VisibleRowid(X) (((X)->tabFlags & TF_NoVisibleRowid)==0)
/* Macro is true if the SQLITE_ALLOW_ROWID_IN_VIEW (mis-)feature is
** available. By default, this macro is false
*/
#ifndef SQLITE_ALLOW_ROWID_IN_VIEW
# define ViewCanHaveRowid 0
#else
# define ViewCanHaveRowid (sqlite3Config.mNoVisibleRowid==0)
#endif
/* /*
** Each foreign key constraint is an instance of the following structure. ** Each foreign key constraint is an instance of the following structure.
** **
@@ -20170,11 +20144,6 @@ struct Sqlite3Config {
#endif #endif
#ifndef SQLITE_UNTESTABLE #ifndef SQLITE_UNTESTABLE
int (*xTestCallback)(int); /* Invoked by sqlite3FaultSim() */ int (*xTestCallback)(int); /* Invoked by sqlite3FaultSim() */
#endif
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
u32 mNoVisibleRowid; /* TF_NoVisibleRowid if the ROWID_IN_VIEW
** feature is disabled. 0 if rowids can
** occur in views. */
#endif #endif
int bLocaltimeFault; /* True to fail localtime() calls */ int bLocaltimeFault; /* True to fail localtime() calls */
int (*xAltLocaltime)(const void*,void*); /* Alternative localtime() routine */ int (*xAltLocaltime)(const void*,void*); /* Alternative localtime() routine */
@@ -20631,13 +20600,10 @@ SQLITE_PRIVATE void sqlite3MutexWarnOnContention(sqlite3_mutex*);
# define EXP754 (((u64)0x7ff)<<52) # define EXP754 (((u64)0x7ff)<<52)
# define MAN754 ((((u64)1)<<52)-1) # define MAN754 ((((u64)1)<<52)-1)
# define IsNaN(X) (((X)&EXP754)==EXP754 && ((X)&MAN754)!=0) # define IsNaN(X) (((X)&EXP754)==EXP754 && ((X)&MAN754)!=0)
# define IsOvfl(X) (((X)&EXP754)==EXP754)
SQLITE_PRIVATE int sqlite3IsNaN(double); SQLITE_PRIVATE int sqlite3IsNaN(double);
SQLITE_PRIVATE int sqlite3IsOverflow(double);
#else #else
# define IsNaN(X) 0 # define IsNaN(X) 0
# define sqlite3IsNaN(X) 0 # define sqlite3IsNaN(X) 0
# define sqlite3IsOVerflow(X) 0
#endif #endif
/* /*
@@ -21873,9 +21839,6 @@ static const char * const sqlite3azCompileOpt[] = {
"ALLOW_COVERING_INDEX_SCAN=" CTIMEOPT_VAL(SQLITE_ALLOW_COVERING_INDEX_SCAN), "ALLOW_COVERING_INDEX_SCAN=" CTIMEOPT_VAL(SQLITE_ALLOW_COVERING_INDEX_SCAN),
# endif # endif
#endif #endif
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
"ALLOW_ROWID_IN_VIEW",
#endif
#ifdef SQLITE_ALLOW_URI_AUTHORITY #ifdef SQLITE_ALLOW_URI_AUTHORITY
"ALLOW_URI_AUTHORITY", "ALLOW_URI_AUTHORITY",
#endif #endif
@@ -22895,9 +22858,6 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = {
#endif #endif
#ifndef SQLITE_UNTESTABLE #ifndef SQLITE_UNTESTABLE
0, /* xTestCallback */ 0, /* xTestCallback */
#endif
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
0, /* mNoVisibleRowid. 0 == allow rowid-in-view */
#endif #endif
0, /* bLocaltimeFault */ 0, /* bLocaltimeFault */
0, /* xAltLocaltime */ 0, /* xAltLocaltime */
@@ -34686,19 +34646,6 @@ SQLITE_PRIVATE int sqlite3IsNaN(double x){
} }
#endif /* SQLITE_OMIT_FLOATING_POINT */ #endif /* SQLITE_OMIT_FLOATING_POINT */
#ifndef SQLITE_OMIT_FLOATING_POINT
/*
** Return true if the floating point value is NaN or +Inf or -Inf.
*/
SQLITE_PRIVATE int sqlite3IsOverflow(double x){
int rc; /* The value return */
u64 y;
memcpy(&y,&x,sizeof(y));
rc = IsOvfl(y);
return rc;
}
#endif /* SQLITE_OMIT_FLOATING_POINT */
/* /*
** Compute a string length that is limited to what can be stored in ** Compute a string length that is limited to what can be stored in
** lower 30 bits of a 32-bit signed integer. ** lower 30 bits of a 32-bit signed integer.
@@ -63855,7 +63802,7 @@ SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager *pPager){
** This will be either the rollback journal or the WAL file. ** This will be either the rollback journal or the WAL file.
*/ */
SQLITE_PRIVATE sqlite3_file *sqlite3PagerJrnlFile(Pager *pPager){ SQLITE_PRIVATE sqlite3_file *sqlite3PagerJrnlFile(Pager *pPager){
#ifdef SQLITE_OMIT_WAL #if SQLITE_OMIT_WAL
return pPager->jfd; return pPager->jfd;
#else #else
return pPager->pWal ? sqlite3WalFile(pPager->pWal) : pPager->jfd; return pPager->pWal ? sqlite3WalFile(pPager->pWal) : pPager->jfd;
@@ -79672,7 +79619,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
}else if( loc<0 && pPage->nCell>0 ){ }else if( loc<0 && pPage->nCell>0 ){
assert( pPage->leaf ); assert( pPage->leaf );
idx = ++pCur->ix; idx = ++pCur->ix;
pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); pCur->curFlags &= ~BTCF_ValidNKey;
}else{ }else{
assert( pPage->leaf ); assert( pPage->leaf );
} }
@@ -79702,7 +79649,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
*/ */
if( pPage->nOverflow ){ if( pPage->nOverflow ){
assert( rc==SQLITE_OK ); assert( rc==SQLITE_OK );
pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); pCur->curFlags &= ~(BTCF_ValidNKey);
rc = balance(pCur); rc = balance(pCur);
/* Must make sure nOverflow is reset to zero even if the balance() /* Must make sure nOverflow is reset to zero even if the balance()
@@ -106709,37 +106656,8 @@ static int lookupName(
} }
} }
if( 0==cnt && VisibleRowid(pTab) ){ if( 0==cnt && VisibleRowid(pTab) ){
/* pTab is a potential ROWID match. Keep track of it and match
** the ROWID later if that seems appropriate. (Search for "cntTab"
** to find related code.) Only allow a ROWID match if there is
** a single ROWID match candidate.
*/
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
/* In SQLITE_ALLOW_ROWID_IN_VIEW mode, allow a ROWID match
** if there is a single VIEW candidate or if there is a single
** non-VIEW candidate plus multiple VIEW candidates. In other
** words non-VIEW candidate terms take precedence over VIEWs.
*/
if( cntTab==0
|| (cntTab==1
&& ALWAYS(pMatch!=0)
&& ALWAYS(pMatch->pTab!=0)
&& (pMatch->pTab->tabFlags & TF_Ephemeral)!=0
&& (pTab->tabFlags & TF_Ephemeral)==0)
){
cntTab = 1;
pMatch = pItem;
}else{
cntTab++;
}
#else
/* The (much more common) non-SQLITE_ALLOW_ROWID_IN_VIEW case is
** simpler since we require exactly one candidate, which will
** always be a non-VIEW
*/
cntTab++; cntTab++;
pMatch = pItem; pMatch = pItem;
#endif
} }
} }
if( pMatch ){ if( pMatch ){
@@ -106865,13 +106783,13 @@ static int lookupName(
** Perhaps the name is a reference to the ROWID ** Perhaps the name is a reference to the ROWID
*/ */
if( cnt==0 if( cnt==0
&& cntTab>=1 && cntTab==1
&& pMatch && pMatch
&& (pNC->ncFlags & (NC_IdxExpr|NC_GenCol))==0 && (pNC->ncFlags & (NC_IdxExpr|NC_GenCol))==0
&& sqlite3IsRowid(zCol) && sqlite3IsRowid(zCol)
&& ALWAYS(VisibleRowid(pMatch->pTab) || pMatch->fg.isNestedFrom) && ALWAYS(VisibleRowid(pMatch->pTab) || pMatch->fg.isNestedFrom)
){ ){
cnt = cntTab; cnt = 1;
if( pMatch->fg.isNestedFrom==0 ) pExpr->iColumn = -1; if( pMatch->fg.isNestedFrom==0 ) pExpr->iColumn = -1;
pExpr->affExpr = SQLITE_AFF_INTEGER; pExpr->affExpr = SQLITE_AFF_INTEGER;
} }
@@ -108729,10 +108647,9 @@ SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr *pExpr){
assert( pExpr->x.pList->nExpr>0 ); assert( pExpr->x.pList->nExpr>0 );
assert( pExpr->op==TK_FUNCTION ); assert( pExpr->op==TK_FUNCTION );
pExpr = pExpr->x.pList->a[0].pExpr; pExpr = pExpr->x.pList->a[0].pExpr;
}else if( pExpr->op==TK_COLLATE ){
pExpr = pExpr->pLeft;
}else{ }else{
break; assert( pExpr->op==TK_COLLATE );
pExpr = pExpr->pLeft;
} }
} }
return pExpr; return pExpr;
@@ -111251,12 +111168,9 @@ SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr *p){
return 0; return 0;
case TK_COLUMN: case TK_COLUMN:
assert( ExprUseYTab(p) ); assert( ExprUseYTab(p) );
return ExprHasProperty(p, EP_CanBeNull) return ExprHasProperty(p, EP_CanBeNull) ||
|| NEVER(p->y.pTab==0) /* Reference to column of index on expr */ NEVER(p->y.pTab==0) || /* Reference to column of index on expr */
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW (p->iColumn>=0
|| (p->iColumn==XN_ROWID && IsView(p->y.pTab))
#endif
|| (p->iColumn>=0
&& p->y.pTab->aCol!=0 /* Possible due to prior error */ && p->y.pTab->aCol!=0 /* Possible due to prior error */
&& ALWAYS(p->iColumn<p->y.pTab->nCol) && ALWAYS(p->iColumn<p->y.pTab->nCol)
&& p->y.pTab->aCol[p->iColumn].notNull==0); && p->y.pTab->aCol[p->iColumn].notNull==0);
@@ -123747,12 +123661,9 @@ SQLITE_PRIVATE void sqlite3CreateView(
** on a view, even though views do not have rowids. The following flag ** on a view, even though views do not have rowids. The following flag
** setting fixes this problem. But the fix can be disabled by compiling ** setting fixes this problem. But the fix can be disabled by compiling
** with -DSQLITE_ALLOW_ROWID_IN_VIEW in case there are legacy apps that ** with -DSQLITE_ALLOW_ROWID_IN_VIEW in case there are legacy apps that
** depend upon the old buggy behavior. The ability can also be toggled ** depend upon the old buggy behavior. */
** using sqlite3_config(SQLITE_CONFIG_ROWID_IN_VIEW,...) */ #ifndef SQLITE_ALLOW_ROWID_IN_VIEW
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW p->tabFlags |= TF_NoVisibleRowid;
p->tabFlags |= sqlite3Config.mNoVisibleRowid; /* Optional. Allow by default */
#else
p->tabFlags |= TF_NoVisibleRowid; /* Never allow rowid in view */
#endif #endif
sqlite3TwoPartName(pParse, pName1, pName2, &pName); sqlite3TwoPartName(pParse, pName1, pName2, &pName);
@@ -129916,7 +129827,7 @@ static void sumFinalize(sqlite3_context *context){
if( p->approx ){ if( p->approx ){
if( p->ovrfl ){ if( p->ovrfl ){
sqlite3_result_error(context,"integer overflow",-1); sqlite3_result_error(context,"integer overflow",-1);
}else if( !sqlite3IsOverflow(p->rErr) ){ }else if( !sqlite3IsNaN(p->rErr) ){
sqlite3_result_double(context, p->rSum+p->rErr); sqlite3_result_double(context, p->rSum+p->rErr);
}else{ }else{
sqlite3_result_double(context, p->rSum); sqlite3_result_double(context, p->rSum);
@@ -129933,7 +129844,7 @@ static void avgFinalize(sqlite3_context *context){
double r; double r;
if( p->approx ){ if( p->approx ){
r = p->rSum; r = p->rSum;
if( !sqlite3IsOverflow(p->rErr) ) r += p->rErr; if( !sqlite3IsNaN(p->rErr) ) r += p->rErr;
}else{ }else{
r = (double)(p->iSum); r = (double)(p->iSum);
} }
@@ -129947,7 +129858,7 @@ static void totalFinalize(sqlite3_context *context){
if( p ){ if( p ){
if( p->approx ){ if( p->approx ){
r = p->rSum; r = p->rSum;
if( !sqlite3IsOverflow(p->rErr) ) r += p->rErr; if( !sqlite3IsNaN(p->rErr) ) r += p->rErr;
}else{ }else{
r = (double)(p->iSum); r = (double)(p->iSum);
} }
@@ -135245,10 +135156,7 @@ static int xferOptimization(
} }
} }
#ifndef SQLITE_OMIT_CHECK #ifndef SQLITE_OMIT_CHECK
if( pDest->pCheck if( pDest->pCheck && sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1) ){
&& (db->mDbFlags & DBFLAG_Vacuum)==0
&& sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1)
){
return 0; /* Tables have different CHECK constraints. Ticket #2252 */ return 0; /* Tables have different CHECK constraints. Ticket #2252 */
} }
#endif #endif
@@ -140649,11 +140557,7 @@ static int pragmaVtabBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
j = seen[0]-1; j = seen[0]-1;
pIdxInfo->aConstraintUsage[j].argvIndex = 1; pIdxInfo->aConstraintUsage[j].argvIndex = 1;
pIdxInfo->aConstraintUsage[j].omit = 1; pIdxInfo->aConstraintUsage[j].omit = 1;
if( seen[1]==0 ){ if( seen[1]==0 ) return SQLITE_OK;
pIdxInfo->estimatedCost = (double)1000;
pIdxInfo->estimatedRows = 1000;
return SQLITE_OK;
}
pIdxInfo->estimatedCost = (double)20; pIdxInfo->estimatedCost = (double)20;
pIdxInfo->estimatedRows = 20; pIdxInfo->estimatedRows = 20;
j = seen[1]-1; j = seen[1]-1;
@@ -143880,7 +143784,11 @@ static const char *columnTypeImpl(
** data for the result-set column of the sub-select. ** data for the result-set column of the sub-select.
*/ */
if( iCol<pS->pEList->nExpr if( iCol<pS->pEList->nExpr
&& (!ViewCanHaveRowid || iCol>=0) #ifdef SQLITE_ALLOW_ROWID_IN_VIEW
&& iCol>=0
#else
&& ALWAYS(iCol>=0)
#endif
){ ){
/* If iCol is less than zero, then the expression requests the /* If iCol is less than zero, then the expression requests the
** rowid of the sub-select or view. This expression is legal (see ** rowid of the sub-select or view. This expression is legal (see
@@ -147055,10 +146963,6 @@ static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){
** **
** (11) The subquery is not a VALUES clause ** (11) The subquery is not a VALUES clause
** **
** (12) The WHERE clause is not "rowid ISNULL" or the equivalent. This
** case only comes up if SQLite is compiled using
** SQLITE_ALLOW_ROWID_IN_VIEW.
**
** Return 0 if no changes are made and non-zero if one or more WHERE clause ** Return 0 if no changes are made and non-zero if one or more WHERE clause
** terms are duplicated into the subquery. ** terms are duplicated into the subquery.
*/ */
@@ -147169,18 +147073,6 @@ static int pushDownWhereTerms(
} }
#endif #endif
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
if( ViewCanHaveRowid && (pWhere->op==TK_ISNULL || pWhere->op==TK_NOTNULL) ){
Expr *pLeft = pWhere->pLeft;
if( ALWAYS(pLeft)
&& pLeft->op==TK_COLUMN
&& pLeft->iColumn < 0
){
return 0; /* Restriction (12) */
}
}
#endif
if( sqlite3ExprIsSingleTableConstraint(pWhere, pSrcList, iSrc) ){ if( sqlite3ExprIsSingleTableConstraint(pWhere, pSrcList, iSrc) ){
nChng++; nChng++;
pSubq->selFlags |= SF_PushDown; pSubq->selFlags |= SF_PushDown;
@@ -147808,14 +147700,12 @@ SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse *pParse, SrcItem *pFrom){
while( pSel->pPrior ){ pSel = pSel->pPrior; } while( pSel->pPrior ){ pSel = pSel->pPrior; }
sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol); sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol);
pTab->iPKey = -1; pTab->iPKey = -1;
pTab->eTabType = TABTYP_VIEW;
pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) );
#ifndef SQLITE_ALLOW_ROWID_IN_VIEW #ifndef SQLITE_ALLOW_ROWID_IN_VIEW
/* The usual case - do not allow ROWID on a subquery */ /* The usual case - do not allow ROWID on a subquery */
pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid; pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid;
#else #else
/* Legacy compatibility mode */ pTab->tabFlags |= TF_Ephemeral; /* Legacy compatibility mode */
pTab->tabFlags |= TF_Ephemeral | sqlite3Config.mNoVisibleRowid;
#endif #endif
return pParse->nErr ? SQLITE_ERROR : SQLITE_OK; return pParse->nErr ? SQLITE_ERROR : SQLITE_OK;
} }
@@ -148083,7 +147973,7 @@ static int selectExpander(Walker *pWalker, Select *p){
pNestedFrom = pFrom->pSelect->pEList; pNestedFrom = pFrom->pSelect->pEList;
assert( pNestedFrom!=0 ); assert( pNestedFrom!=0 );
assert( pNestedFrom->nExpr==pTab->nCol ); assert( pNestedFrom->nExpr==pTab->nCol );
assert( VisibleRowid(pTab)==0 || ViewCanHaveRowid ); assert( VisibleRowid(pTab)==0 );
}else{ }else{
if( zTName && sqlite3StrICmp(zTName, zTabName)!=0 ){ if( zTName && sqlite3StrICmp(zTName, zTabName)!=0 ){
continue; continue;
@@ -148115,8 +148005,7 @@ static int selectExpander(Walker *pWalker, Select *p){
pUsing = 0; pUsing = 0;
} }
nAdd = pTab->nCol; nAdd = pTab->nCol + (VisibleRowid(pTab) && (selFlags&SF_NestedFrom));
if( VisibleRowid(pTab) && (selFlags & SF_NestedFrom)!=0 ) nAdd++;
for(j=0; j<nAdd; j++){ for(j=0; j<nAdd; j++){
const char *zName; const char *zName;
struct ExprList_item *pX; /* Newly added ExprList term */ struct ExprList_item *pX; /* Newly added ExprList term */
@@ -148198,8 +148087,7 @@ static int selectExpander(Walker *pWalker, Select *p){
pX = &pNew->a[pNew->nExpr-1]; pX = &pNew->a[pNew->nExpr-1];
assert( pX->zEName==0 ); assert( pX->zEName==0 );
if( (selFlags & SF_NestedFrom)!=0 && !IN_RENAME_OBJECT ){ if( (selFlags & SF_NestedFrom)!=0 && !IN_RENAME_OBJECT ){
if( pNestedFrom && (!ViewCanHaveRowid || j<pNestedFrom->nExpr) ){ if( pNestedFrom ){
assert( j<pNestedFrom->nExpr );
pX->zEName = sqlite3DbStrDup(db, pNestedFrom->a[j].zEName); pX->zEName = sqlite3DbStrDup(db, pNestedFrom->a[j].zEName);
testcase( pX->zEName==0 ); testcase( pX->zEName==0 );
}else{ }else{
@@ -153133,9 +153021,6 @@ SQLITE_PRIVATE void sqlite3Update(
} }
} }
if( chngRowid==0 && pPk==0 ){ if( chngRowid==0 && pPk==0 ){
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
if( isView ) sqlite3VdbeAddOp2(v, OP_Null, 0, regOldRowid);
#endif
sqlite3VdbeAddOp2(v, OP_Copy, regOldRowid, regNewRowid); sqlite3VdbeAddOp2(v, OP_Copy, regOldRowid, regNewRowid);
} }
} }
@@ -166845,10 +166730,16 @@ static SQLITE_NOINLINE void whereAddIndexedExpr(
for(i=0; i<pIdx->nColumn; i++){ for(i=0; i<pIdx->nColumn; i++){
Expr *pExpr; Expr *pExpr;
int j = pIdx->aiColumn[i]; int j = pIdx->aiColumn[i];
int bMaybeNullRow;
if( j==XN_EXPR ){ if( j==XN_EXPR ){
pExpr = pIdx->aColExpr->a[i].pExpr; pExpr = pIdx->aColExpr->a[i].pExpr;
testcase( pTabItem->fg.jointype & JT_LEFT );
testcase( pTabItem->fg.jointype & JT_RIGHT );
testcase( pTabItem->fg.jointype & JT_LTORJ );
bMaybeNullRow = (pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0;
}else if( j>=0 && (pTab->aCol[j].colFlags & COLFLAG_VIRTUAL)!=0 ){ }else if( j>=0 && (pTab->aCol[j].colFlags & COLFLAG_VIRTUAL)!=0 ){
pExpr = sqlite3ColumnExpr(pTab, &pTab->aCol[j]); pExpr = sqlite3ColumnExpr(pTab, &pTab->aCol[j]);
bMaybeNullRow = 0;
}else{ }else{
continue; continue;
} }
@@ -166880,7 +166771,7 @@ static SQLITE_NOINLINE void whereAddIndexedExpr(
p->iDataCur = pTabItem->iCursor; p->iDataCur = pTabItem->iCursor;
p->iIdxCur = iIdxCur; p->iIdxCur = iIdxCur;
p->iIdxCol = i; p->iIdxCol = i;
p->bMaybeNullRow = (pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0; p->bMaybeNullRow = bMaybeNullRow;
if( sqlite3IndexAffinityStr(pParse->db, pIdx) ){ if( sqlite3IndexAffinityStr(pParse->db, pIdx) ){
p->aff = pIdx->zColAff[i]; p->aff = pIdx->zColAff[i];
} }
@@ -179085,18 +178976,6 @@ SQLITE_API int sqlite3_config(int op, ...){
} }
#endif /* SQLITE_OMIT_DESERIALIZE */ #endif /* SQLITE_OMIT_DESERIALIZE */
case SQLITE_CONFIG_ROWID_IN_VIEW: {
int *pVal = va_arg(ap,int*);
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW
if( 0==*pVal ) sqlite3GlobalConfig.mNoVisibleRowid = TF_NoVisibleRowid;
if( 1==*pVal ) sqlite3GlobalConfig.mNoVisibleRowid = 0;
*pVal = (sqlite3GlobalConfig.mNoVisibleRowid==0);
#else
*pVal = 0;
#endif
break;
}
default: { default: {
rc = SQLITE_ERROR; rc = SQLITE_ERROR;
break; break;
@@ -250799,7 +250678,7 @@ static void fts5SourceIdFunc(
){ ){
assert( nArg==0 ); assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused); UNUSED_PARAM2(nArg, apUnused);
sqlite3_result_text(pCtx, "fts5: 2024-04-15 13:34:05 8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355", -1, SQLITE_TRANSIENT); sqlite3_result_text(pCtx, "fts5: 2024-03-12 11:06:23 d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77", -1, SQLITE_TRANSIENT);
} }
/* /*

23
deps/sqlite/sqlite3.h vendored
View File

@@ -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.45.3" #define SQLITE_VERSION "3.45.2"
#define SQLITE_VERSION_NUMBER 3045003 #define SQLITE_VERSION_NUMBER 3045002
#define SQLITE_SOURCE_ID "2024-04-15 13:34:05 8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355" #define SQLITE_SOURCE_ID "2024-03-12 11:06:23 d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77"
/* /*
** CAPI3REF: Run-Time Library Version Numbers ** CAPI3REF: Run-Time Library Version Numbers
@@ -2143,22 +2143,6 @@ struct sqlite3_mem_methods {
** configuration setting is never used, then the default maximum is determined ** configuration setting is never used, then the default maximum is determined
** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option. If that ** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option. If that
** compile-time option is not set, then the default maximum is 1073741824. ** compile-time option is not set, then the default maximum is 1073741824.
**
** [[SQLITE_CONFIG_ROWID_IN_VIEW]]
** <dt>SQLITE_CONFIG_ROWID_IN_VIEW
** <dd>The SQLITE_CONFIG_ROWID_IN_VIEW option enables or disables the ability
** for VIEWs to have a ROWID. The capability can only be enabled if SQLite is
** compiled with -DSQLITE_ALLOW_ROWID_IN_VIEW, in which case the capability
** defaults to on. This configuration option queries the current setting or
** changes the setting to off or on. The argument is a pointer to an integer.
** If that integer initially holds a value of 1, then the ability for VIEWs to
** have ROWIDs is activated. If the integer initially holds zero, then the
** ability is deactivated. Any other initial value for the integer leaves the
** setting unchanged. After changes, if any, the integer is written with
** a 1 or 0, if the ability for VIEWs to have ROWIDs is on or off. If SQLite
** is compiled without -DSQLITE_ALLOW_ROWID_IN_VIEW (which is the usual and
** recommended case) then the integer is always filled with zero, regardless
** if its initial value.
** </dl> ** </dl>
*/ */
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
@@ -2190,7 +2174,6 @@ struct sqlite3_mem_methods {
#define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */ #define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */
#define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */ #define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */
#define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */ #define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */
#define SQLITE_CONFIG_ROWID_IN_VIEW 30 /* int* */
/* /*
** CAPI3REF: Database Connection Configuration Options ** CAPI3REF: Database Connection Configuration Options

View File

@@ -1,53 +0,0 @@
# Writing Tilde Friends applications7
TODO
## Creating your environment
1. Open an existing application (ie: `identity`);
2. Open the editing panel;
3. Save the app under a new name (ie `/~YOUR_USERNAME/my-app/`);
4. Go back to the main menu and open your new app;
5. You can now edit your app, save it and see changes in the real time.
## Project structure
An application has a `app.js` file that gets run when a user enters the app.
This file contains a function (typically called `main()`) that's considered the entry point.
Paste this in `app.js`:
```javascript
async function main() {
let ids = await ssb.getIdentities();
await app.setDocument(`
<body style="font-family: sans-serif; color: white">
<h1>Hello world!</h1>
</body>
</body>`);
}
main();
```
Save the app, and you should now be seeing `Hello world!` on the screen.
## Components
Once your app grows to a certain size, you'll want to introduce components.
In Tilde Friends, the de facto standard is [Lit](TODO).
Althogh you an use any framework you want, you're encouraged to use Lit as you can reuse
First, add lit-all-min.js into your project.
TODO
<!-- mention shadow dom -->
TODO: tfrpc
Apps can interact with Tilde Friends using tfrpc.
Read [tfrpc.md](https://dev.tildefriends.net/cory/tildefriends/src/branch/main/docs/apps/tfrpc.md)
TODO: sharing apps

View File

@@ -1,7 +0,0 @@
# RPC documentation
Quick start
Complete documentation
TODO

View File

@@ -1,78 +0,0 @@
# How to build Tilde Friends
> Disclaimer: this documentation has been written by a Linux user and has not been reviewed by other people on other platforms. The procedure may vary slightly depending on your operating system.
Builds **on** Linux (`x86_64` and `aarch64`), MacOS, OpenBSD, and Haiku.
Builds **for** all of those host platforms plus `mingw64`, iOS, and android.
Dependencies:
- `openssl` (`libssl-dev`, in debian-speak)
Dependencies for Android:
- TODO
Dependencies for iOS:
- TODO
Dependencies for Windows:
- TODO
> All other dependencies are kept up to date as git submodules.
1. Clone the repository with the submodules: `git clone --recursive https://dev.tildefriends.net/cory/tildefriends.git`
2. Run `make -j $(nproc) debug` or `make -j $(nproc) release`
If you're unsure whether you should choose `debug` or `release`, stick to `release`.
> `-j $(nproc)` will start a compiler for every CPU thread, which will dramatically reduce the time needed to compile Tilde Friends.
An executable will be generated in a subdirectory of `out/`
It's possible to build for Android, iOS, and Windows on Linux, if you have the right dependencies in the right places. Run `make -j $(nproc) windebug winrelease iosdebug-ipa iosrelease-ipa release-apk`
To build in docker, `docker build .`
<!-- On NixOS: TODO -->
<!-- Add shell.nix and nix derivs first -->
Now that you have a binary, head over to [running.md](https://dev.tildefriends.net/cory/tildefriends/src/branch/main/docs/running.md).
## Troubleshooting
### The compiler throws a warning and I can't build the binary
You can choose to tell the compiler to ignore warnings.
Open `GNUMakefile` and edit the CFLAGS environment variable around line 50.
For example given this error:
```text
src/http.c: In function 'tf_http_get_cookie':
src/http.c:1089:128: error: check of 'name' for NULL after already dereferencing it [-Werror=analyzer-deref-before-check]
```
Add:
```diff
CFLAGS += \
-std=gnu11 \
-Wall \
-Wextra \
-Wno-unused-parameter \
+ -Wno-analyzer-deref-before-check \
-MMD \
-MP \
-ffunction-sections \
-fdata-sections \
-fno-exceptions \
-g
```
Now the compiler will ignore this error and *should* continue building anyways.
Note this is a dirty hack to get Tilde Friends to compile and you should not propose to keep this flag on. Instead, open a bug report.

View File

@@ -1,60 +0,0 @@
# How to contribute
## Philosophy
TODO
## Best practices
TODO
## How to get your changes merged
- Fork this repository
- Clone your repository
1. Alternatively, you can add a remote called `fork`:
`$ git remote add fork https://dev.tildefriends.net/YOUR_USERNAME/tildefriends.git`
You'll need to set your branch's upstream to `fork`:
`$ git push --set-upstream fork my-branch`
2. or you can change the `origin` remote on your existing clone altogether:
`$ git remote set-url origin https://dev.tildefriends.net/YOUR_USERNAME/tildefriends.git`
- Make your changes
- I want to edit C code !
TODO
- I want to edit JavaScript code !
TODO
- I want to write documentation !
Great! Before you do, have a look at the [documentation guidelines](https://dev.tildefriends.net/cory/tildefriends/src/branch/main/docs/guidelines/documentation-guidelines.md) to learn how to write consistent documentation.
In all cases:
- Make sure that your commit messages are descriptive.
<!-- - hi -->
- Format your changes:
If you've edited C code: run `make format`
If you've edited JavaScript code or the documentation: run `npm run format`
- Open a pull request
TODO
- Get your changes reviewed and merged
TODO

View File

@@ -1,13 +0,0 @@
# Tilde Friends documentation
## Building
See [building.md](https://dev.tildefriends.net/cory/tildefriends/src/branch/main/docs/building.md).
## Contibuting
See [contributing.md](https://dev.tildefriends.net/cory/tildefriends/src/branch/main/docs/contributing.md).
## FAQ / Troubleshooting
See [faq.md](https://dev.tildefriends.net/cory/tildefriends/src/branch/main/docs/faq.md).

View File

@@ -1,13 +0,0 @@
# Troubleshooting
## I started tildefriends. Now what ?
See [running.md](https://dev.tildefriends.net/cory/tildefriends/src/branch/main/docs/running.md).
### The compiler throws an error and I can't build the binary
See [building.md](https://dev.tildefriends.net/cory/tildefriends/src/branch/main/docs/building.md).
### Where is my database located ?
TODO

View File

@@ -1,4 +1,36 @@
<!-- # Philosophy
Tilde Friends is a platform for making, running, and sharing web applications.
When you visit Tilde Friends in a web browser, you are presented with a
terminal interface, typically with a big text output box covering most of the
page and an input box at the bottom, into which text or commands can be
entered. A script runs to produce text output and consume user input.
The script is a Tilde Friends application, and it runs on the server, which
means that unlike client-side JavaScript, it can have the ability to read and
write files on the server or create network connections to other machines.
Unlike node.js or other server-side runtime environments, applications are
limited for security reasons to not interfere with each other or bring the
entire server down.
Above the terminal, an "Edit" link brings a visitor to the source code for the
current Tilde Friends application, which they can then edit, save as their own,
and run.
# Architecture
Tilde Friends is a C++ application with a JavaScript runtime that provides
restricted access to filesystem, network, and other system resources. The core
process runs a core set of scripts that implement a web server, typically
starting a new process for each visitor's session which runs scripts for the
active application and stopping it when the visitor leaves.
Only the core process has access to most system resources, but session
processes can be given accesss through the core process.
Service processes are identical to session processes, but they are not tied to
a user session.
## Communication ## Communication
@@ -34,7 +66,7 @@ performance reasons to minimize the data size transferred between processes.
// Receive the above message and call the function. // Receive the above message and call the function.
core.register("onMessage", function(sender, message) { core.register("onMessage", function(sender, message) {
message.add(3, 4).then(x => terminal.print(x.toString())); message.add(3, 4).then(x => terminal.print(x.toString()));
}); });
Finally, there is a core web interface that runs on the client's browser that Finally, there is a core web interface that runs on the client's browser that
@@ -103,18 +135,16 @@ Sets the browser window/tab title.
Reconfigures the terminal layout, potentially into multiple split panes. Reconfigures the terminal layout, potentially into multiple split panes.
```javascript terminal.split([
terminal.split( {
[{ type: "horizontal",
type: "horizontal", children: [
children: [ {name: "left", basis: "2in", grow: 0, shrink: 0},
{name: "left", basis: "2in", grow: 0, shrink: 0}, {name: "middle", grow: 1},
{name: "middle", grow: 1}, {name: "right", basis: "2in", grow: 0, shrink: 0},
{name: "right", basis: "2in", grow: 0, shrink: 0}, ],
], },
}] ]);
);
```
#### terminal.select(name) #### terminal.select(name)
@@ -177,5 +207,3 @@ Writes data to the connection.
#### connection.close() #### connection.close()
Closes the connection. Closes the connection.
-->

View File

@@ -1 +0,0 @@
# TODO

View File

@@ -1,75 +0,0 @@
# Documentation guidelines
This document defines the rules used to write documentation in order to make it more consistent.
This documentation is a living document and so are it's rules; you are free to propose changes but in the meantime, please stick to them.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119/).
## File naming
Files SHOULD be named using [kebab-case](https://www.freecodecamp.org/news/snake-case-vs-camel-case-vs-pascal-case-vs-kebab-case-whats-the-difference/#kebab-case).
Their names should be meaningful and SHOULD not conflict with other files in other directories:
> Example: this document is named `docs/guidelines/documentation-guidelines.md` instead of `docs/guidelines/documentation.md` because it could cause confusion with `docs/documentation.md`.
## Documentation
When writing documentation, the author should have in mind it's target audience: people with varying technical skills and backgrounds, fluency in peer-to-peer-specific terms and mental ability.
The documentation should therefore be acessible and usefule to most people interested in building, using and contributing to Tilde Friends.
### Terminology
`Tilde Friends` refers to the projectas a whole. This can be abbreviated to `TF`.
`tildefriends` refers to the program.
### Style guide
1. Lines SHOULD NOT be wrapped, to allow clients to dynamically wrap them however they want:
```text
This is not very pleasant to read because
the text
is manually wrapped, but the size of the
screen is
smaller than the size the text is wrapped
at. I
need to write even more useless text here
so I get
my point across. Also hi! If you're here
that
means you're either going to contribute to
Tilde
Friends, or that you're reviewing my
stupid
changes. Either way, you're awesome!
```
You MAY use one line per sentence.
2. Lines ending with an `inline code block` or hyperlinks SHOULD NOT end with a period to make copy-pasting easier.
> Example: To build in docker, `$ docker build .`
NB: this does not apply to file names or other text that are not meant to be copy-pasted.
> Example: this document is named `docs/guidelines/documentation-guidelines.md` instead of `docs/guidelines/documentation.md` because it could cause confusion with `docs/documentation.md`.
3. Commands SHOULD start with a caret: (is that the tehnical term ?)
- `$` if the command should be run as the current user
- `#` if the command should be run as root
> Example: To build in docker, `$ docker build .`
More TODO
## License
As per the rest of the code in this repository, the documentation is shared under the [MIT](https://opensource.org/licenses/MIT/) license.
## Changelog
### v1 (2024-05-12)
First version; 3 new guidelines.

View File

@@ -1 +0,0 @@
# TODO

View File

@@ -1,37 +0,0 @@
# Tilde Friends in depth
## Philosophy
Tilde Friends is a platform for making, running, and sharing web applications.
<!-- When you visit Tilde Friends in a web browser, you are presented with a
terminal interface, typically with a big text output box covering most of the
page and an input box at the bottom, into which text or commands can be
entered. A script runs to produce text output and consume user input.
The script is a Tilde Friends application, and it runs on the server, which
means that unlike client-side JavaScript, it can have the ability to read and
write files on the server or create network connections to other machines.
Unlike node.js or other server-side runtime environments, applications are
limited for security reasons to not interfere with each other or bring the
entire server down.
Above the terminal, an "Edit" link brings a visitor to the source code for the
current Tilde Friends application, which they can then edit, save as their own,
and run. -->
## Architecture
Tilde Friends is a C++ application with a JavaScript runtime that provides restricted access to filesystem, network, and other system resources.
The core process runs a core set of scripts that implement a web server, typically starting a new process for each visitor's session which runs scripts for the active application and stopping it when the visitor leaves.
Only the core process has access to most system resources, but session processes can be given accesss through the core process.
Service processes are identical to session processes, but they are not tied to a user session.
```text
/-------\ /-------------\ /--------------\
| C app | <-----> | Server-side | <-----> | Client-side |
| | tfrpc | JS runtime | | JS (Browser) |
\-------/ \-------------/ \--------------/
```

View File

@@ -1,50 +0,0 @@
# Running Tilde Friends
> Disclaimer: this documentation has been written by a Linux user and has not been reviewed by other people on other platforms. The procedure may vary slightly depending on your operating system.
The binaries should appear at `out/debug/tildefriends` and `out/release/tildefriends`.
For Android, iOS and Windows: TODO
You can now start the server by running `$ ./out/debug/tildefriends` or `$ ./out/release/tildefriends`.
By default, running the built `tildefriends` executable will start a web server
at <http://localhost:12345/>. `$ tildefriends -h` lists further options.
## How to use TF
### Initial setup
Now you have a Tilde Friends instance running. The first thing you'll want to do is create your account. Click "login" in the top right corner, then "Register".
Enter your username and password.
> The first user to create an account and log in will be granted administrative privileges.
> Further administration can be done at <http://localhost:12345/~core/admin/>
Next, create a Scuttlebutt identity by pressing the "Create an identity" button.
This will create a pair of keys that are used to sign your messages with.
Because of the way Scuttlebutt is designed, you cannot log into your account without your keys.
Tilde Friends locks your keys behind a password, but if you were to destroy your database, the keys would be gone forever, and with it your possibility to send messages using this account. Click on the `identity` app and under "Identities", export your newly created identity.
You'll be prompted with a dialog box saying "This app is requesting the following permission:ssb_id_export".
This is because applications are not trusted to have access to your keys by default.
Click on "Allow" and you'll see a list of 12 words. You need to write those down in a password manager or on a piece of paperand keep it private and secure.
> Warning: Nobody needs to know these 12 words. Anybody that has access to those keys can post messages as you, see your private messages and documents and much more.
Now that your keys are safe, we can start connecting to the outside world.
### Replication
You've probably noticed asdtring of random characters by now. This is your public key, a unique identifier for your account you can share to anyone. If you go back to the home menu and into the `ssb` app, you can click on your public key. This will lead you to your profile, which is empty at the time. Edit it and enter your name.
TODO: joining a room
TODO: initial sync
TODO: send messages
TODO: how messages spread to friends
TODO: other apps

61
flake.lock generated
View File

@@ -1,61 +0,0 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1715395895,
"narHash": "sha256-DreMqi6+qa21ffLQqhMQL2XRUkAGt3N7iVB5FhJKie4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "71bae31b7dbc335528ca7e96f479ec93462323ff",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -1,37 +0,0 @@
{
description = "Tilde Friends is a platform for making, running, and sharing web applications.";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = {
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {
inherit system;
};
in rec
{
# Nix formatter, run using `$ nix fmt`
formatter = pkgs.alejandra;
# Exports the tildefriends package
# Build with `$ nix build`
packages.default = pkgs.callPackage ./default.nix {};
# Creates a shell with the necessary dependencies
# Enter using `$ nix develop`
devShell = pkgs.mkShell {
buildInputs = with pkgs; [
openssl
llvmPackages_17.clang-unwrapped
unzip
];
};
});
}

716
package-lock.json generated
View File

@@ -6,442 +6,13 @@
"": { "": {
"name": "tildefriends", "name": "tildefriends",
"license": "MIT", "license": "MIT",
"devDependencies": {
"markdownlint-cli": "0.40.0",
"prettier": "3.2.5"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"dependencies": { "dependencies": {
"string-width": "^5.1.2", "prettier": "^3.2.5"
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/commander": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
"integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==",
"dev": true,
"engines": {
"node": ">=18"
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dev": true,
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"dev": true,
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.0",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/get-stdin": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz",
"integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/glob": {
"version": "10.3.14",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.14.tgz",
"integrity": "sha512-4fkAqu93xe9Mk7le9v0y3VrPDqLKHarNi2s4Pv7f2yOvfhWfhc7hRPHC/JyqMqb8B/Dt/eGS4n7ykwf3fOsl8g==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^2.3.6",
"minimatch": "^9.0.1",
"minipass": "^7.0.4",
"path-scurry": "^1.11.0"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ignore": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
"dev": true,
"engines": {
"node": ">= 4"
}
},
"node_modules/ini": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz",
"integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==",
"dev": true,
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
"node_modules/jackspeak": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
"dev": true,
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsonc-parser": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz",
"integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==",
"dev": true
},
"node_modules/jsonpointer": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz",
"integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"dev": true,
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/lru-cache": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
"integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
"dev": true,
"engines": {
"node": "14 || >=16.14"
}
},
"node_modules/markdown-it": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/markdownlint": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.34.0.tgz",
"integrity": "sha512-qwGyuyKwjkEMOJ10XN6OTKNOVYvOIi35RNvDLNxTof5s8UmyGHlCdpngRHoRGNvQVGuxO3BJ7uNSgdeX166WXw==",
"dev": true,
"dependencies": {
"markdown-it": "14.1.0",
"markdownlint-micromark": "0.1.9"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/DavidAnson"
}
},
"node_modules/markdownlint-cli": {
"version": "0.40.0",
"resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.40.0.tgz",
"integrity": "sha512-JXhI3dRQcaqwiFYpPz6VJ7aKYheD53GmTz9y4D/d0F1MbZDGOp9pqKlbOfUX/pHP/iAoeiE4wYRmk8/kjLakxA==",
"dev": true,
"dependencies": {
"commander": "~12.0.0",
"get-stdin": "~9.0.0",
"glob": "~10.3.12",
"ignore": "~5.3.1",
"js-yaml": "^4.1.0",
"jsonc-parser": "~3.2.1",
"jsonpointer": "5.0.1",
"markdownlint": "~0.34.0",
"minimatch": "~9.0.4",
"run-con": "~1.3.2",
"toml": "~3.0.0"
},
"bin": {
"markdownlint": "markdownlint.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/markdownlint-micromark": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.9.tgz",
"integrity": "sha512-5hVs/DzAFa8XqYosbEAEg6ok6MF2smDj89ztn9pKkCtdKHVdPQuGMH7frFfYL9mLkvfFe4pTyAMffLbjf3/EyA==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/DavidAnson"
}
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"dev": true
},
"node_modules/minimatch": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz",
"integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==",
"dev": true,
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/path-scurry": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.0.tgz",
"integrity": "sha512-LNHTaVkzaYaLGlO+0u3rQTz7QrHTFOuKyba9JMTQutkmtNew8dw8wOD7mTU/5fCPZzCWpfW0XnQKzY61P0aTaw==",
"dev": true,
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "3.2.5", "version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", "license": "MIT",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"dev": true,
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
}, },
@@ -451,289 +22,6 @@
"funding": { "funding": {
"url": "https://github.com/prettier/prettier?sponsor=1" "url": "https://github.com/prettier/prettier?sponsor=1"
} }
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/run-con": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/run-con/-/run-con-1.3.2.tgz",
"integrity": "sha512-CcfE+mYiTcKEzg0IqS08+efdnH0oJ3zV0wSUFBNrMHMuxCtXvBCLzCJHatwuXDcu/RlhjTziTo/a1ruQik6/Yg==",
"dev": true,
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~4.1.0",
"minimist": "^1.2.8",
"strip-json-comments": "~3.1.1"
},
"bin": {
"run-con": "cli.js"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true,
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/toml": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz",
"integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==",
"dev": true
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"dev": true
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
} }
} }
} }

View File

@@ -1,14 +1,12 @@
{ {
"name": "tildefriends", "name": "tildefriends",
"scripts": { "scripts": {
"format": "npm run prettier && npm run markdown", "prettier": "prettier . --check --cache --write",
"prettier": "npx prettier --cache --write --check .", "postinstall": "sh tools/install_dependencies.sh"
"markdown": "npx markdownlint-cli --fix 'docs/**/*.md'"
}, },
"author": "Cory McWilliams", "author": "Cory McWilliams",
"license": "MIT", "license": "MIT",
"devDependencies": { "dependencies": {
"markdownlint-cli": "0.40.0", "prettier": "^3.2.5"
"prettier": "3.2.5"
} }
} }

BIN
sqlite3 Executable file

Binary file not shown.

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