Compare commits

..

93 Commits

Author SHA1 Message Date
58dbf42a3a theme picker: preselect the saved theme 2024-03-21 22:29:39 +01:00
a1f221879b remove the identity app 2024-03-21 20:45:04 +01:00
2a928dcafc feat-user_settings): add ability to import and delete an identity, formatting 2024-03-21 20:10:35 +01:00
5474c5a101 Merge branch 'user_settings' of https://dev.tildefriends.net/tasiaiso/tildefriends into user_settings 2024-03-21 19:33:08 +01:00
4b7261fa20 Merge branch 'main' into user_settings 2024-03-21 18:28:52 +00:00
4992ff3a2d feat(user_settings): finish ui, add ability to export and create identities, add tildefriends.css to the /static/ folder 2024-03-21 19:27:29 +01:00
88ee0aa6f0 feat(user_settings): finish ui, add ability to export and create identities, add tildefriends.css to the /static/ folder 2024-03-21 19:25:07 +01:00
fdda628be8 Fix paths in the source tarball. 2024-03-20 20:43:41 -04:00
2b45d8aa05 Doh. Never mean to add that. 2024-03-20 20:37:52 -04:00
0e2fc65301 Document run -k flag. 2024-03-20 20:33:23 -04:00
392206c19e Merge branch 'main' into user_settings 2024-03-20 10:51:57 +00:00
e8ef7e74de Fixed a leak in JS blob store. 2024-03-18 12:46:12 -04:00
c32e1b9583 http request cleanup crash fix. 2024-03-18 16:34:07 +00:00
9d0f6ec155 Fix the sneaker app RE: JSONB. 2024-03-18 12:32:40 -04:00
855d603795 docs + prettier 2024-03-17 13:21:33 -04:00
af25782185 More http/request shutdown issues. 2024-03-17 12:38:37 -04:00
e5ba51b80a Chasing a leak that looks like an EBT clock. Deleted some unneeded code and adding a missing JS free. 2024-03-17 13:44:05 +00:00
5e240de677 Fix requesting blobs from blob_wants. ids were trucated. Yikes. 2024-03-17 09:16:06 -04:00
418cfac0e3 Add a stock app with local room connection info. #15 2024-03-14 00:43:11 +00:00
9d09607013 Update CodeMirror. 2024-03-13 20:23:48 -04:00
eddf25b622 Give libuv the same download treatment as sqlite. 2024-03-13 19:53:57 -04:00
537a8654fa Rename sequence_before_author => flags. #29 2024-03-13 19:40:09 -04:00
9de33d06d2 Specifying -fsanitize=... early seems good. 2024-03-13 18:26:24 -04:00
0e5f320664 sqlite 3.45.2. 2024-03-13 12:30:14 -04:00
88d8e60511 Some minor paranoia to appease valgrind. 2024-03-12 21:44:20 -04:00
439f07162e Disable Haiku automation tests until I find a way to automate a browser on Haiku. 2024-03-09 08:44:06 -05:00
efe2b6cbd9 A make target to run prettier. 2024-03-08 21:43:08 -05:00
0aa1ed9464 Fix a failure requesting more blobs. 2024-03-08 21:38:31 -05:00
cb94ed6a2a Some plumbing to expose the actual bound SHS port so that I can make a dynamic room app. 2024-03-07 21:03:14 -05:00
cf187ee46b Reorder things so that we only zipalign -z during a dist build. To slow for make all. 2024-03-07 20:42:08 -05:00
3e71fc20fd Prettier. 2024-03-06 21:14:09 -05:00
f3601321f7 That's all the doxygen warnings. #27 2024-03-06 21:13:16 -05:00
540059368c 11 make docs warnings left, but I'm out of time for tonight. 2024-03-06 20:57:38 -05:00
7ce89123f7 85 make docs warnings remain. 2024-03-06 12:46:27 -05:00
e3c7c86212 All but the two biggest .h files have docs. 2024-03-06 12:31:17 -05:00
794804e27f A few more .h file docs. 2024-03-05 21:17:20 -05:00
6d89c1da6e Format. 2024-03-05 20:49:30 -05:00
d059554464 Some workarounds for Haiku. uv_fs_scandir can't tell if a dirent is a file. setrlimit doesn't do anything productive for us. 2024-03-05 20:49:16 -05:00
3a392d4a9f More .h docs. 2024-03-05 12:47:58 -05:00
e3071b372a Poking at TCP binds from Haiku. 2024-03-04 21:51:27 -05:00
18bd279b0c Some progress on .h docs, and add a preliminary CONTRIBUTING.md. 2024-03-04 12:23:00 -05:00
5b93db7463 A buncha muncha cruncha .h docs. Also add vim temporary files to .gitignore. 2024-03-03 18:12:44 -05:00
5b7e5eb91b Give fts a better chance of working with jsonb messages.content. 2024-03-03 18:55:58 +00:00
78ca383e3c http.h docs. 2024-03-03 12:35:10 -05:00
c1eed9ada3 Fixed a leak in ssb.getServerIdentity(). 2024-03-03 12:20:03 -05:00
8d6feb5394 Set the root of private messages correct so that other clients show them. 2024-03-03 12:09:03 -05:00
42994f8977 Make the SSB network key configurable by command-line argument. 2024-03-02 15:01:09 -05:00
f0a871e1f8 More docs. 2024-03-01 21:18:12 -05:00
a710c30572 Fix apps for jsonb. 2024-02-29 19:26:56 -05:00
c991763b00 tests.h and tlscontext.js.h docs. 2024-02-28 21:18:59 -05:00
72dae14f87 Android NDK update. 2024-02-28 21:04:22 -05:00
5800340762 Fix up some more jsonb references. 2024-02-28 20:41:27 -05:00
c5f5adcac6 Missed some more jsonb messages.content use issues. 2024-02-28 20:31:25 -05:00
591642efb3 Convert messages.content to JSONB. This is a very disruptive change. 2024-02-28 20:01:52 -05:00
6182ffa1d4 Docs for tls.h and trace.h. 2024-02-28 19:12:41 -05:00
402a898d96 Let's start working on 0.0.17. 2024-02-28 18:47:21 -05:00
13d43d8319 Let's release 0.0.16. 2024-02-28 18:24:12 -05:00
7bcdbd3813 Revert "Update commonmark.js to 0.31.0."
This reverts commit 165f25db69.
2024-02-26 12:34:50 -05:00
60ada22674 Add a button to toggle visible whitespace for now. Not yet persisted. 2024-02-25 22:31:31 -05:00
637119d46d ideviceinstaller makes this unnecessary. 2024-02-25 21:41:32 -05:00
40f3da6a65 Fix a leak in returning HTTP responses. 2024-02-25 19:38:00 -05:00
f4697fe7f7 Update CodeMirror. 2024-02-25 18:54:35 -05:00
3bc18b9021 Docs for util.js.h. 2024-02-25 18:52:34 -05:00
c21581aefa Use zipalign w/zopfli for APKs to save a little on size. 2024-02-25 18:29:10 -05:00
165f25db69 Update commonmark.js to 0.31.0. 2024-02-25 16:25:23 -05:00
9aa0617aa1 Fix android argv. 2024-02-25 16:02:56 -05:00
ddce88dce6 Merge branch 'tasiaiso-wiki-improvements' 2024-02-25 15:37:56 -05:00
6aa2bce2be Merge branch 'wiki-improvements' of https://dev.tildefriends.net/tasiaiso/tildefriends into tasiaiso-wiki-improvements 2024-02-25 15:37:13 -05:00
a43c1d3d1e Format. 2024-02-25 15:03:43 -05:00
1ed0e817e8 BSD compile fix. 2024-02-25 14:57:14 -05:00
709ca55e65 Fix overbuild on macos. 2024-02-25 14:52:35 -05:00
8c13f5dbba xopt => getopt_long. I give up on xopt. It didn't help me as much as I had hoped, and I had problems building for mingw with only some versions of GCC. Not worth any further time. 2024-02-25 14:45:31 -05:00
4cb82d81b7 apps/gg doesn't belong here and isn't ready for prime time.. 2024-02-24 11:19:36 -05:00
0c42921387 Appease prettier in index.html. 2024-02-24 11:16:07 -05:00
70a3e7fc7d Make app export append a trailing newline to the app.json files so that we match prettier. 2024-02-24 11:12:35 -05:00
d5267be38c Run prettier. 2024-02-24 11:09:34 -05:00
8e7e0ed490 Merge branch 'tasiaiso-prettier' 2024-02-24 11:03:36 -05:00
8cf2837725 Export app json files indented with tabs. 2024-02-24 10:58:53 -05:00
63ae186c76 Export app json files indented with tabs. 2024-02-24 10:55:09 -05:00
dbf5c7b832 Merge branch 'main' into prettier 2024-02-23 09:50:49 +00:00
bfbfc01e99 keep the new config files 2024-02-23 10:42:26 +01:00
8fa9d0e843 Revert "build: Add prettier to the project"
This reverts commit 41024ddb79.
2024-02-23 10:35:39 +01:00
2d3e108fd9 Reapply "build: Add prettier to the project"
This reverts commit 7822b30dcb.
2024-02-23 10:29:46 +01:00
7822b30dcb Revert "build: Add prettier to the project"
This reverts commit 41024ddb79.
2024-02-23 10:25:51 +01:00
2701b7d04e Address some gcc-13 analyzer warnings. #33 2024-02-22 20:13:51 -05:00
f9e95e5733 test 2024-02-22 23:28:15 +01:00
1444c945de feat: Create the user settings app 2024-02-22 23:18:12 +01:00
e361c3f975 chore(wiki): the button class is now optional for input elements 2024-02-22 22:34:11 +01:00
260706c172 chore: copy .prettierrc.yaml over to client.js 2024-02-22 21:31:15 +01:00
1d5cdf9607 feat(wiki): improvements to the wiki's UI 2024-02-22 18:39:53 +01:00
a4bf3542e0 Merge branch 'main' into wiki-improvements 2024-02-22 15:14:49 +00:00
df82cfe66b chore rename core.css to tildefriends.css, remove license from tildefriends.css 2024-02-22 16:11:49 +01:00
53f9547cc5 style(wiki): use core.js 2024-02-22 13:03:21 +01:00
142 changed files with 6026 additions and 9311 deletions

8
.gitignore vendored
View File

@@ -1,8 +1,10 @@
**/node_modules
.keys
.zsign_cache/
db.*
deps/ios_toolchain/
deps/openssl/
dist/
.keys
**/node_modules
out
*.swo
*.swp
.zsign_cache/

View File

@@ -3,8 +3,3 @@ useTabs: true
semi: true
singleQuote: true
bracketSpacing: false
# overrides:
# - files: '**/*.json'
# options:
# useTabs: false
# tabWidth: 2

37
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,37 @@
# Contributing to Tilde Friends
Thank you for your interest in Tilde Friends.
Above all, Tilde Friends aims to be a fun, safe place to play. When that is at
odds with the course of development, we will work through it with respectful
communication.
## How can I contribute?
The nature of Tilde Friends makes for a wide range of ways to contribute
- Just use it. Really, just kicking the tires will probably shake out issues
in useful ways at this point.
- Report and comment on bugs: https://dev.tildefriends.net/issues.
- Make apps. You don't need my permission to make and share apps with Tilde
Friends. I hope that an ecosystem of good apps grows outside of this
repository. If you want to recreate better versions of the stock apps, just
do it. If you make a better ssb app or whatever and drop me a line however
is most convenient for you, I will probably take a look and consider
replacing the stock one with it.
- Write about it. Docs in the git repository, blog posts, private messages to
me with ideas...really there is no wrong answer. Just make some noise, and
I'll do my best to incorporate or otherwise link your feedback and make the
most of it.
- Write C code in the git repository. I'm really striving for it to be the
case that other people don't really need to meddle in there, but if you can
help out, I will gladly review your pull requests via
https://dev.tildefriends.net/pulls.
## Best practices
- The C code is formatted with clang-format. Run `make format`.
- The rest is formatted with prettier. Run `npm run prettier`.
- We strive to have code compile on all platforms with no warnings and run with
no sanitizer issues.
- There are tests. Run `out/debug/tildefriends test`.

View File

@@ -3,9 +3,12 @@
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
VERSION_CODE := 16
VERSION_NUMBER := 0.0.16-wip
VERSION_NAME := Medium English breakfast tea.
VERSION_CODE := 17
VERSION_NUMBER := 0.0.17-wip
VERSION_NAME := Please enjoy responsibly.
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
PROJECT = tildefriends
BUILD_DIR ?= out
@@ -14,6 +17,18 @@ UNAME_M := $(shell uname -m)
ANDROID_SDK ?= ~/Android/Sdk
ifeq ($(UNAME_M),x86_64)
ifneq ($(UNAME_S),Haiku)
debug: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
debug: LDFLAGS += -fsanitize=address -fsanitize=undefined
endif
endif
ifeq ($(UNAME_M),aarch64)
debug: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
debug: LDFLAGS += -fsanitize=address -fsanitize=undefined
endif
ifeq ($(UNAME_S),Darwin)
BUILD_TYPES := macosdebug macosrelease iosdebug iosrelease iossimdebug iossimrelease
else ifeq ($(UNAME_S),Linux)
@@ -55,7 +70,7 @@ CFLAGS += \
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-34
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.1.10909125
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.2.11394342
ANDROID_MIN_SDK_VERSION := 24
ANDROID_TARGET_SDK_VERSION := 34
@@ -207,18 +222,6 @@ $(IOS_TARGETS): LDFLAGS += -Ldeps/openssl/ios/ios64-xcrun/usr/local/lib
$(IOSSIM_TARGETS): CFLAGS += -Ideps/openssl/ios/iossimulator-xcrun/usr/local/include
$(IOSSIM_TARGETS): LDFLAGS += -Ldeps/openssl/ios/iossimulator-xcrun/usr/local/lib
ifeq ($(UNAME_M),x86_64)
ifneq ($(UNAME_S),Haiku)
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
get_objs = \
$(foreach build_type,$(BUILD_TYPES),$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)))))) \
$(foreach build_type,debug release,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_unix))))) \
@@ -245,7 +248,6 @@ $(APP_OBJS): CFLAGS += \
-Ideps/quickjs \
-Ideps/sqlite \
-Ideps/valgrind \
-Ideps/xopt \
-Wdouble-promotion \
-Werror
ifeq ($(UNAME_M),x86_64)
@@ -497,18 +499,6 @@ $(SQLITE_OBJS): CFLAGS += \
-Wno-unused-function \
-Wno-unused-variable
XOPT_SOURCES := deps/xopt/xopt.c
XOPT_OBJS := $(call get_objs,XOPT_SOURCES)
$(filter $(BUILD_DIR)/win%,$(XOPT_OBJS)): CFLAGS += \
-DHAVE_SNPRINTF \
-DHAVE_VSNPRINTF \
-DHAVE_VASNPRINTF \
-DHAVE_VASPRINTF \
-Dvsnprintf=rpl_vsnprintf
$(XOPT_OBJS): CFLAGS += \
-Wno-implicit-const-int-float-conversion \
-Wno-pointer-to-int-cast
QUICKJS_SOURCES := \
deps/quickjs/cutils.c \
deps/quickjs/libbf.c \
@@ -637,8 +627,7 @@ ALL_APP_OBJS := \
$(QUICKJS_OBJS) \
$(SODIUM_OBJS) \
$(SQLITE_OBJS) \
$(UV_OBJS) \
$(XOPT_OBJS)
$(UV_OBJS)
DEPS = $(ALL_APP_OBJS:.o=.d)
-include $(DEPS)
@@ -717,7 +706,7 @@ PACKAGE_DIRS := \
deps/prettier/ \
deps/lit/
RAW_FILES := $(filter-out apps/blog% apps/gg% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f))
RAW_FILES := $(filter-out apps/blog% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f))
out/apk/TildeFriends-arm-debug.unsigned.apk: BUILD_TYPE := debug
out/apk/TildeFriends-arm-release.unsigned.apk: BUILD_TYPE := release
@@ -736,10 +725,11 @@ out/apk/TildeFriends-arm-%.unsigned.apk:
@cp out/android$(BUILD_TYPE)-armv7a/tildefriends out/apk-arm-$(BUILD_TYPE)/lib/armeabi-v7a/tildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-arm-$(BUILD_TYPE)/lib/arm64-v8a/tildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-arm-$(BUILD_TYPE)/lib/armeabi-v7a/tildefriends.so
@cp out/apk/res.apk $@
@cp out/apk/res.apk $@.zip
@cp out/apk/classes.dex out/apk-arm-$(BUILD_TYPE)/
@cd out/apk-arm-$(BUILD_TYPE) && zip -u ../../$@ -q -9 -r . && cd ../../
@zip -u $@ -q -9 $(RAW_FILES)
@cd out/apk-arm-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../
@zip -u $@.zip -q $(RAW_FILES)
@$(ANDROID_BUILD_TOOLS)/zipalign -f 4 $@.zip $@
out/apk/TildeFriends-x86-%.unsigned.apk:
@mkdir -p $(dir $@) out/apk-x86-$(BUILD_TYPE)/lib/x86_64/ out/apk-x86-$(BUILD_TYPE)/lib/x86/
@@ -748,16 +738,22 @@ out/apk/TildeFriends-x86-%.unsigned.apk:
@cp out/android$(BUILD_TYPE)-x86/tildefriends out/apk-x86-$(BUILD_TYPE)/lib/x86/tildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-x86-$(BUILD_TYPE)/lib/x86_64/tildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-x86-$(BUILD_TYPE)/lib/x86/tildefriends.so
@cp out/apk/res.apk $@
@cp out/apk/res.apk $@.zip
@cp out/apk/classes.dex out/apk-x86-$(BUILD_TYPE)/
@cd out/apk-x86-$(BUILD_TYPE) && zip -u ../../$@ -q -9 -r . && cd ../../
@zip -u $@ -q -9 $(RAW_FILES)
@cd out/apk-x86-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../
@zip -u $@.zip -q $(RAW_FILES)
@$(ANDROID_BUILD_TOOLS)/zipalign -f 4 $@.zip $@
out/%.apk: out/apk/%.unsigned.apk
@echo "[apksigner] $(notdir $@)"
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --out $@ $<
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $<
release-apk: out/TildeFriends-arm-release.apk out/TildeFriends-x86-release.apk
out/%.zopfli.apk: out/%.apk
@echo "[zopfli] $(notdir $@)"
$(ANDROID_BUILD_TOOLS)/zipalign -f -z 4 $< $@.zopfli
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $@.zopfli
release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-release.zopfli.apk
.PHONY: release-apk
releaseapkgo: out/TildeFriends-arm-release.apk
@@ -814,11 +810,13 @@ apklog:
fetchdeps:
@echo "[fetch] libuv"
@test -f out/deps/libuv.tar.gz || (mkdir -p out/deps/ && curl -q https://dist.libuv.org/dist/v1.48.0/libuv-v1.48.0.tar.gz -o out/deps/libuv.tar.gz)
@test -d deps/libuv/ || (mkdir -p deps/libuv/ && tar -C deps/libuv/ -m --strip=1 -xf out/deps/libuv.tar.gz)
@test -f out/deps/libuv.tar.gz && test "$$(cat out/deps/libuv.txt 2>/dev/null)" = $(LIBUV_URL) || (mkdir -p out/deps/ && curl -q $(LIBUV_URL) -o out/deps/libuv.tar.gz)
@test -d deps/libuv/ && test "$$(cat out/deps/libuv.txt 2>/dev/null)" = $(LIBUV_URL) || (rm -rf deps/libuv/ && mkdir -p deps/libuv/ && tar -C deps/libuv/ -m --strip=1 -xf out/deps/libuv.tar.gz)
@echo -n $(LIBUV_URL) > out/deps/libuv.txt
@echo "[fetch] sqlite"
@test -f out/deps/sqlite.zip || (mkdir -p out/deps/ && curl -q https://www.sqlite.org/2024/sqlite-amalgamation-3450100.zip -o out/deps/sqlite.zip)
@test -d deps/sqlite/ || (mkdir -p deps/sqlite/ && unzip -qDj -d deps/sqlite/ out/deps/sqlite.zip)
@test -f out/deps/sqlite.zip && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p out/deps/ && curl -q $(SQLITE_URL) -o out/deps/sqlite.zip)
@test -d deps/sqlite/ && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p deps/sqlite/ && unzip -qDjo -d deps/sqlite/ out/deps/sqlite.zip)
@echo -n $(SQLITE_URL) > out/deps/sqlite.txt
@echo "[fetch] prettier"
@test -f deps/prettier/standalone.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/standalone.mjs
@test -f deps/prettier/html.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/html.mjs
@@ -839,7 +837,7 @@ $(filter $(BUILD_DIR)/win%,$(APP_OBJS)): | $(WINDOWS_DEPS)
endif
ifeq ($(UNAME_S),Darwin)
IOS_DEPS := deps/openssl/ios/usr/local/lib/libssl.a
IOS_DEPS := deps/openssl/ios/ios64-xcrun/usr/local/lib/libssl.a
$(IOS_DEPS):
+@tools/ssl-ios
$(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(IOS_DEPS)
@@ -855,7 +853,6 @@ dist: release-apk iosrelease-ipa
@mkdir -p dist/ out/tildefriends-$(VERSION_NUMBER)
@git archive main | tar -x -C out/tildefriends-$(VERSION_NUMBER)
@tar \
--exclude=apps/gg* \
--exclude=apps/welcome* \
--exclude=deps/libbacktrace/Isaac.Newton-Opticks.txt \
--exclude=deps/libsodium/builds/msvc/vs* \
@@ -870,12 +867,13 @@ dist: release-apk iosrelease-ipa
--exclude=deps/sqlite/shell.c \
--exclude=deps/zlib/contrib/vstudio \
--exclude=deps/zlib/doc \
-caf dist/tildefriends-$(VERSION_NUMBER).tar.xz out/tildefriends-$(VERSION_NUMBER)
#@rm -rf out/tildefriends-$(VERSION_NUMBER)
-caf dist/tildefriends-$(VERSION_NUMBER).tar.xz \
-C out/ \
tildefriends-$(VERSION_NUMBER)
@echo "[cp] TildeFriends-x86-$(VERSION_NUMBER).apk"
@cp out/TildeFriends-x86-release.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"
@cp out/TildeFriends-arm-release.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"
@cp out/tildefriends-release.ipa dist/TildeFriends-$(VERSION_NUMBER).ipa
.PHONY: dist
@@ -891,6 +889,10 @@ format:
@clang-format -i $(wildcard src/*.c src/*.h src/*.m)
.PHONY: format
prettier:
@npm run prettier
.PHONY: prettier
docs:
@doxygen
.PHONY: docs

View File

@@ -4,34 +4,46 @@ Tilde Friends is a tool for making and sharing.
A public instance lives at https://www.tildefriends.net/.
It is both a peer-to-peer social network client, participating in Secure Scuttlebutt, as well as a platform for writing and running web applications.
It is both a peer-to-peer social network client, participating in Secure
Scuttlebutt, as well as a platform for writing and running web applications.
## Goals
1. Make it easy and fun to run all sorts of web applications.
2. Provide security that is easy to understand and protects your data.
3. Make creating and sharing web applications accessible to anyone with a browser.
3. Make creating and sharing web applications accessible to anyone with a
browser.
## Building
Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. Builds for all of those host platforms plus mingw64, iOS, and android.
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`.
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.
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/>.
The first user to create an account and log in will be granted administrative
privileges. Further administration can be done at
<http://localhost:12345/~core/admin/>.
## Documentation
Docs are a work in progress: <https://www.tildefriends.net/~cory/wiki/#test-wiki/tf-app-quick-reference>.
Docs are a work in progress:
<https://www.tildefriends.net/~cory/wiki/#test-wiki/tf-app-quick-reference>.
## License
All code unless otherwise noted in is provided under the [MIT](https://opensource.org/licenses/MIT) license.
All code unless otherwise noted in is provided under the
[MIT](https://opensource.org/licenses/MIT) license.

View File

@@ -87,6 +87,6 @@ window.addEventListener('load', function () {
.map((x) => html`${input_template(x, data.settings[x])}`)}
</div>
${users_template(data.users)}
</div>`;
</div> `;
render(page_template(g_data), document.body);
});

View File

@@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "💻",
"previous": "&RdVEsVscZm3aWzcMrEZS8mskO5tUmvaEUihex2MMfZQ=.sha256"
"previous": "&icsPplXHgmpkbNWyo/WTvRdT/A8BXxW4lJixOtP4ueQ=.sha256"
}

View File

@@ -28,10 +28,10 @@ async function fetch_shared_apps() {
await ssb.sqlAsync(
`
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages_fts('"application/tildefriends"')
JOIN messages ON messages.rowid = messages_fts.rowid
ORDER BY timestamp
ORDER BY messages.timestamp
`,
[],
function (row) {

View File

@@ -1,4 +1,4 @@
{
"type": "tildefriends-app",
"emoji": "💽"
}
"type": "tildefriends-app",
"emoji": "💽"
}

View File

@@ -1,5 +0,0 @@
{
"type": "tildefriends-app",
"emoji": "🗺",
"previous": "&0XSp+xdQwVtQ88bXzvWdH15Ex63hv5zUKTa4zx7HBGM=.sha256"
}

View File

@@ -1,85 +0,0 @@
import * as tfrpc from '/tfrpc.js';
import * as strava from './strava.js';
let g_database;
let g_shared_database;
tfrpc.register(async function createIdentity() {
return ssb.createIdentity();
});
tfrpc.register(async function appendMessage(id, message) {
print('APPEND', JSON.stringify(message));
return ssb.appendMessageWithIdentity(id, message);
});
tfrpc.register(function url() {
return core.url;
});
tfrpc.register(async function getUser() {
return core.user;
});
tfrpc.register(function getIdentities() {
return ssb.getIdentities();
});
tfrpc.register(async function databaseGet(key) {
return g_database ? g_database.get(key) : undefined;
});
tfrpc.register(async function databaseSet(key, value) {
return g_database ? g_database.set(key, value) : undefined;
});
tfrpc.register(async function databaseRemove(key, value) {
return g_database ? g_database.remove(key, value) : undefined;
});
tfrpc.register(async function sharedDatabaseGet(key) {
return g_shared_database ? g_shared_database.get(key) : undefined;
});
tfrpc.register(async function sharedDatabaseSet(key, value) {
return g_shared_database ? g_shared_database.set(key, value) : undefined;
});
tfrpc.register(async function sharedDatabaseRemove(key, value) {
return g_shared_database ? g_shared_database.remove(key, value) : undefined;
});
tfrpc.register(async function query(sql, args) {
let result = [];
await ssb.sqlAsync(sql, args, function callback(row) {
result.push(row);
});
return result;
});
tfrpc.register(async function store_blob(blob) {
if (typeof blob == 'string') {
blob = utf8Encode(blob);
}
if (Array.isArray(blob)) {
blob = Uint8Array.from(blob);
}
return await ssb.blobStore(blob);
});
tfrpc.register(async function get_blob(id) {
return utf8Decode(await ssb.blobGet(id));
});
tfrpc.register(strava.refresh_token);
async function main() {
g_shared_database = await shared_database('state');
if (core.user.credentials?.session?.name) {
g_database = await database('state');
}
let attempt;
if (core.user.credentials?.session?.name) {
let shared_db = await shared_database('state');
attempt = await shared_db.get(core.user.credentials.session.name);
}
app.setDocument(
utf8Decode(getFile('index.html')).replace(
'${data}',
JSON.stringify({
attempt: attempt,
state: core.user?.credentials?.session?.name,
})
)
);
}
main();

File diff suppressed because one or more lines are too long

View File

@@ -1,84 +0,0 @@
function xml_parse(xml) {
let result;
let path = [];
let tag_begin;
let text_begin;
for (let i = 0; i < xml.length; i++) {
let c = xml.charAt(i);
if (!tag_begin && c == '<') {
if (i > text_begin && path.length) {
let value = xml.substring(text_begin, i);
if (!/^\s*$/.test(value)) {
path[path.length - 1].value = value;
}
}
tag_begin = i + 1;
} else if (tag_begin && c == '>') {
let tag = xml.substring(tag_begin, i).trim();
if (tag.startsWith('?') && tag.endsWith('?')) {
/* Ignore directives. */
} else if (tag.startsWith('/')) {
path.pop();
} else {
let parts = tag.split(' ');
let attributes = {};
for (let j = 1; j < parts.length; j++) {
let eq = parts[j].indexOf('=');
let value = parts[j].substring(eq + 1);
if (value.startsWith('"') && value.endsWith('"')) {
value = value.substring(1, value.length - 1);
}
attributes[parts[j].substring(0, eq)] = value;
}
let next = {name: parts[0], children: [], attributes: attributes};
if (path.length) {
path[path.length - 1].children.push(next);
} else {
result = next;
}
if (!tag.endsWith('/')) {
path.push(next);
}
}
tag_begin = undefined;
text_begin = i + 1;
}
}
return result;
}
function* xml_each(node, name) {
for (let child of node.children) {
if (child.name == name) {
yield child;
}
}
}
export function gpx_parse(xml) {
let result = {segments: []};
let tree = xml_parse(xml);
if (tree?.name == 'gpx') {
for (let trk of xml_each(tree, 'trk')) {
for (let trkseg of xml_each(trk, 'trkseg')) {
let segment = [];
for (let trkpt of xml_each(trkseg, 'trkpt')) {
segment.push({
lat: parseFloat(trkpt.attributes.lat),
lon: parseFloat(trkpt.attributes.lon),
});
}
result.segments.push(segment);
}
}
}
for (let metadata of xml_each(tree, 'metadata')) {
for (let link of xml_each(metadata, 'link')) {
result.link = link.attributes.href;
}
for (let time of xml_each(metadata, 'time')) {
result.time = time.value;
}
}
return result;
}

View File

@@ -1,21 +0,0 @@
import * as strava from './strava.js';
async function main() {
print('handler running');
let r = await strava.authorization_code(request.query.code);
print('state =', request.query.state);
print('body = ', r.body);
if (request.query.state && r.body) {
let shared_db = await shared_database('state');
await shared_db.set(request.query.state, utf8Decode(r.body));
}
await respond({
data: r.body,
content_type: 'text/plain',
headers: {
Location: 'https://tildefriends.net/~cory/gg/',
},
status_code: 307,
});
}
main();

View File

@@ -1,26 +0,0 @@
<!doctype html>
<html style="width: 100%; height: 100%; margin: 0; padding: 0">
<head>
<script>
window.litDisableBundleWarning = true;
</script>
<script>
let g_data = ${data};
</script>
<script src="script.js" type="module"></script>
<script src="leaflet.js"></script>
</head>
<body
style="
color: #fff;
display: flex;
flex-flow: column;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
"
>
<gg-app style="width: 100%; height: 100%" id="ggapp"></gg-app>
</body>
</html>

View File

@@ -1,661 +0,0 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Prevents IE11 from highlighting tiles in blue */
.leaflet-tile::selection {
background: transparent;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg {
max-width: none !important;
max-height: none !important;
}
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer,
.leaflet-container .leaflet-tile {
max-width: none !important;
max-height: none !important;
width: auto;
padding: 0;
}
.leaflet-container img.leaflet-tile {
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
mix-blend-mode: plus-lighter;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
svg.leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive,
svg.leaflet-image-layer.leaflet-interactive path {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline-offset: 1px;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
font-size: 12px;
font-size: 0.75rem;
line-height: 1.5;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover,
.leaflet-bar a:focus {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
font-size: 13px;
font-size: 1.08333em;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.8);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
line-height: 1.4;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover,
.leaflet-control-attribution a:focus {
text-decoration: underline;
}
.leaflet-attribution-flag {
display: inline !important;
vertical-align: baseline !important;
width: 1em;
height: 0.6669em;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
white-space: nowrap;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.8);
text-shadow: 1px 1px #fff;
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 24px 13px 20px;
line-height: 1.3;
font-size: 13px;
font-size: 1.08333em;
min-height: 1px;
}
.leaflet-popup-content p {
margin: 17px 0;
margin: 1.3em 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-top: -1px;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
pointer-events: auto;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
border: none;
text-align: center;
width: 24px;
height: 24px;
font: 16px/24px Tahoma, Verdana, sans-serif;
color: #757575;
text-decoration: none;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus {
color: #585858;
}
.leaflet-popup-scrolled {
overflow: auto;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
-ms-zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-interactive {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}
/* Printing */
@media print {
/* Prevent printers from removing background-images of controls. */
.leaflet-control {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}

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,162 +0,0 @@
/**
* Based off of [the offical Google document](https://developers.google.com/maps/documentation/utilities/polylinealgorithm)
*
* Some parts from [this implementation](http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/PolylineEncoder.js)
* by [Mark McClure](http://facstaff.unca.edu/mcmcclur/)
*
* @module polyline
*/
var polyline = {};
function py2_round(value) {
// Google's polyline algorithm uses the same rounding strategy as Python 2, which is different from JS for negative values
return Math.floor(Math.abs(value) + 0.5) * (value >= 0 ? 1 : -1);
}
function encode(current, previous, factor) {
current = py2_round(current * factor);
previous = py2_round(previous * factor);
var coordinate = (current - previous) * 2;
if (coordinate < 0) {
coordinate = -coordinate - 1;
}
var output = '';
while (coordinate >= 0x20) {
output += String.fromCharCode((0x20 | (coordinate & 0x1f)) + 63);
coordinate /= 32;
}
output += String.fromCharCode((coordinate | 0) + 63);
return output;
}
/**
* Decodes to a [latitude, longitude] coordinates array.
*
* This is adapted from the implementation in Project-OSRM.
*
* @param {String} str
* @param {Number} precision
* @returns {Array}
*
* @see https://github.com/Project-OSRM/osrm-frontend/blob/master/WebContent/routing/OSRM.RoutingGeometry.js
*/
polyline.decode = function (str, precision) {
var index = 0,
lat = 0,
lng = 0,
coordinates = [],
shift = 0,
result = 0,
byte = null,
latitude_change,
longitude_change,
factor = Math.pow(10, Number.isInteger(precision) ? precision : 5);
// Coordinates have variable length when encoded, so just keep
// track of whether we've hit the end of the string. In each
// loop iteration, a single coordinate is decoded.
while (index < str.length) {
// Reset shift, result, and byte
byte = null;
shift = 1;
result = 0;
do {
byte = str.charCodeAt(index++) - 63;
result += (byte & 0x1f) * shift;
shift *= 32;
} while (byte >= 0x20);
latitude_change = result & 1 ? (-result - 1) / 2 : result / 2;
shift = 1;
result = 0;
do {
byte = str.charCodeAt(index++) - 63;
result += (byte & 0x1f) * shift;
shift *= 32;
} while (byte >= 0x20);
longitude_change = result & 1 ? (-result - 1) / 2 : result / 2;
lat += latitude_change;
lng += longitude_change;
coordinates.push([lat / factor, lng / factor]);
}
return coordinates;
};
/**
* Encodes the given [latitude, longitude] coordinates array.
*
* @param {Array.<Array.<Number>>} coordinates
* @param {Number} precision
* @returns {String}
*/
polyline.encode = function (coordinates, precision) {
if (!coordinates.length) {
return '';
}
var factor = Math.pow(10, Number.isInteger(precision) ? precision : 5),
output =
encode(coordinates[0][0], 0, factor) +
encode(coordinates[0][1], 0, factor);
for (var i = 1; i < coordinates.length; i++) {
var a = coordinates[i],
b = coordinates[i - 1];
output += encode(a[0], b[0], factor);
output += encode(a[1], b[1], factor);
}
return output;
};
function flipped(coords) {
var flipped = [];
for (var i = 0; i < coords.length; i++) {
var coord = coords[i].slice();
flipped.push([coord[1], coord[0]]);
}
return flipped;
}
/**
* Encodes a GeoJSON LineString feature/geometry.
*
* @param {Object} geojson
* @param {Number} precision
* @returns {String}
*/
polyline.fromGeoJSON = function (geojson, precision) {
if (geojson && geojson.type === 'Feature') {
geojson = geojson.geometry;
}
if (!geojson || geojson.type !== 'LineString') {
throw new Error('Input must be a GeoJSON LineString');
}
return polyline.encode(flipped(geojson.coordinates), precision);
};
/**
* Decodes to a GeoJSON LineString geometry.
*
* @param {String} str
* @param {Number} precision
* @returns {Object}
*/
polyline.toGeoJSON = function (str, precision) {
var coords = polyline.decode(str, precision);
return {
type: 'LineString',
coordinates: flipped(coords),
};
};
let polyline_decode = polyline.decode;
export {polyline_decode as decode};

View File

@@ -1,909 +0,0 @@
import {
LitElement,
html,
unsafeHTML,
css,
guard,
until,
} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import * as polyline from './polyline.js';
import {gpx_parse} from './gpx.js';
const k_client_id = '28276';
const k_redirect_url = 'https://tildefriends.net/~cory/gg/login';
const k_color_snow = [128, 128, 255, 255];
const k_color_ice = [160, 160, 255, 255];
const k_color_water = [0, 0, 255, 255];
const k_color_dirt = [128, 129, 130, 255];
const k_color_pavement = [32, 32, 32, 255];
const k_color_grass = [0, 255, 0, 255];
const k_color_default = [128, 128, 128, 255];
const k_store = {
'🦞': 15,
'🛶': 10,
'🏠': 10,
'⛰': 10,
'🐠': 10,
};
const k_marker_snap = {x: 5, y: 4};
class GgAppElement extends LitElement {
static get properties() {
return {
user: {type: Object},
strava: {type: Object},
activities: {type: Array},
activity: {type: Object},
world: {type: Object},
whoami: {type: String},
status: {type: Object},
tab: {type: String},
url: {type: String},
currency: {type: Number},
to_build: {type: String},
emoji_of_the_day: {type: String},
};
}
constructor() {
super();
this.activities = [];
this.activity = {};
this.loaded_activities = [];
this.placed_emojis = [];
this.strava = {};
this.min_lat = Number.MAX_VALUE;
this.min_lon = Number.MAX_VALUE;
this.max_lat = -Number.MAX_VALUE;
this.max_lon = -Number.MAX_VALUE;
this.focus = undefined;
this.status = undefined;
this.tab = 'map';
this.load().catch(function (e) {
console.log('load error', e);
});
this.to_build = '🏠';
}
async load() {
console.log('load');
let emojis = await (await fetch('emojis.json')).json();
emojis = Object.values(emojis)
.map((x) => Object.values(x))
.flat();
let today = new Date();
let date_index =
today.getYear() * 356 + today.getMonth() * 31 + today.getDate();
this.emoji_of_the_day = emojis[(date_index * 123457) % emojis.length];
this.user = await tfrpc.rpc.getUser();
this.url = (await tfrpc.rpc.url()).split('?')[0];
try {
await this.update_credentials();
} catch (e) {
console.log('update_credentials failed', e);
}
try {
await this.update_activities();
} catch (e) {
console.log('update_activities failed', e);
}
await this.acquire_ssb_identity();
if (this.whoami && this.activities?.length) {
await this.sync_activities();
}
await this.get_activities_from_ssb();
}
/* https://gist.github.com/jcouyang/632709f30e12a7879a73e9e132c0d56b?permalink_comment_id=3591045#gistcomment-3591045 */
async promise_all(promises, max_concurrent) {
let index = 0;
let results = [];
async function exec_thread() {
while (index < promises.length) {
const current = index++;
results[current] = await promises[current];
}
}
const threads = [];
for (let thread = 0; thread < max_concurrent; thread++) {
threads.push(exec_thread());
}
await Promise.all(threads);
return results;
}
async get_activities_from_ssb() {
this.status = {text: 'loading activities'};
this.loaded_activities = [];
let rows = await tfrpc.rpc.query(
`
SELECT messages.author, json_extract(mention.value, '$.link') AS blob_id
FROM messages_fts('"gg-activity"')
JOIN messages ON messages.rowid = messages_fts.rowid,
json_each(messages.content, '$.mentions') as mention
WHERE json_extract(messages.content, '$.type') = 'gg-activity' AND
json_extract(mention.value, '$.name') = 'activity_data'
ORDER BY messages.timestamp DESC
`,
[]
);
this.status = {text: 'loading activity data'};
let authors = rows.map((x) => x.author);
let blobs = await this.promise_all(
rows.map((x) => tfrpc.rpc.get_blob(x.blob_id)),
8
);
this.status = {text: 'processing activity data'};
for (let [index, blob] of blobs.entries()) {
let activity;
try {
activity = JSON.parse(blob);
} catch {
activity = gpx_parse(blob);
}
if (activity) {
activity.author = authors[index];
this.loaded_activities.push(activity);
}
}
this.status = {text: 'calculating balance'};
rows = await tfrpc.rpc.query(
`
SELECT count(*) AS currency FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'gg-activity'
`,
[this.whoami]
);
let currency = rows[0].currency;
rows = await tfrpc.rpc.query(
`
SELECT SUM(json_extract(content, '$.cost')) AS cost FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'gg-place'
`,
[this.whoami]
);
let spent = rows[0].cost;
this.currency = currency - spent;
this.status = {text: 'getting placed emojis'};
rows = await tfrpc.rpc.query(`
SELECT messages.content
FROM messages_fts('"gg-place"')
JOIN messages ON messages.rowid = messages_fts.rowid
WHERE json_extract(messages.content, '$.type') = 'gg-place'
ORDER BY messages.timestamp
`);
for (let row of rows) {
console.log(row.content);
let content = JSON.parse(row.content);
this.placed_emojis.push({
position: content.position,
emoji: content.emoji,
});
}
console.log(this.placed_emojis);
this.status = undefined;
this.update_map();
}
async sync_activities() {
let ids = this.activities.map(
(x) => `https://www.strava.com/activities/${x.id}`
);
let missing = await tfrpc.rpc.query(
`
WITH my_activities AS (
SELECT json_extract(mention.value, '$.link') AS url
FROM messages, json_each(messages.content, '$.mentions') AS mention
WHERE
author = ? AND
json_extract(messages.content, '$.type') = 'gg-activity' AND
json_extract(mention.value, '$.name') = 'activity_url')
SELECT from_strava.value FROM json_each(?) AS from_strava
LEFT OUTER JOIN my_activities ON from_strava.value = my_activities.url
WHERE my_activities.url IS NULL
`,
[this.whoami, JSON.stringify(ids)]
);
console.log('missing = ', missing);
for (let [index, row] of missing.entries()) {
this.status = {
text: 'syncing from strava',
value: index,
max: missing.length,
};
let url = row.value;
let id = url.match(/.*\/(\d+)/)[1];
let response = await fetch(
`https://www.strava.com/api/v3/activities/${id}`,
{
headers: {
Authorization: `Bearer ${this.strava.access_token}`,
},
}
);
let activity = await response.json();
let blob_id = await tfrpc.rpc.store_blob(JSON.stringify(activity));
let message = {
type: 'gg-activity',
mentions: [
{
link: url,
name: 'activity_url',
},
{
link: blob_id,
name: 'activity_data',
},
],
};
await tfrpc.rpc.appendMessage(this.whoami, message);
}
this.status = undefined;
}
async acquire_ssb_identity() {
let user = await tfrpc.rpc.getUser();
if (!user?.credentials?.session?.name) {
return;
}
let ids = await tfrpc.rpc.getIdentities();
let players = ids.length
? (
await tfrpc.rpc.query(
`
SELECT author FROM messages JOIN json_each(?) ON messages.author = json_each.value
WHERE
json_extract(messages.content, '$.type') = 'gg-player' AND
json_extract(messages.content, '$.active')
ORDER BY timestamp DESC limit 1
`,
[JSON.stringify(ids)]
)
).map((row) => row.author)
: [];
if (!players.length) {
this.whoami = await tfrpc.rpc.createIdentity();
if (this.whoami) {
await tfrpc.rpc.appendMessage(this.whoami, {
type: 'gg-player',
active: true,
});
}
} else {
players.sort();
this.whoami = players[0];
}
}
async update_credentials() {
let name = this.user?.credentials?.session?.name;
if (!name) {
return;
}
let shared = await tfrpc.rpc.sharedDatabaseGet(name);
if (shared) {
await tfrpc.rpc.databaseSet('strava', shared);
await tfrpc.rpc.sharedDatabaseRemove(name);
}
this.strava = JSON.parse((await tfrpc.rpc.databaseGet('strava')) || '{}');
if (new Date().valueOf() / 1000 > this.strava.expires_at) {
console.log(
'this looks expired',
new Date().valueOf() / 1000,
'>',
this.strava.expires_at
);
let x = await tfrpc.rpc.refresh_token(this.strava);
if (x) {
this.strava = x;
await tfrpc.rpc.databaseSet('strava', JSON.stringify(x));
} else {
this.strava = null;
}
}
}
async update_activities() {
if (this?.strava?.access_token) {
let response = await fetch(
'https://www.strava.com/api/v3/athlete/activities',
{
headers: {
Authorization: `Bearer ${this.strava.access_token}`,
},
}
);
this.activities = await response.json();
this.activities.sort((a, b) => a.id - b.id);
}
}
color_to_emoji(color) {
const k_map = [
[k_color_snow, '⬜'],
[k_color_ice, '🟦'],
[k_color_water, '🟦'],
[k_color_dirt, '🟫'],
[k_color_pavement, '⬛'],
[k_color_grass, '🟩'],
[k_color_default, '🟧'],
];
for (let m of k_map) {
if (
m[0][0] == color[0] &&
m[0][1] == color[1] &&
m[0][2] == color[2] &&
m[0][3] == color[3]
) {
return m[1];
}
}
}
activity_bounds(activity) {
let min_lat = Number.MAX_VALUE;
let min_lon = Number.MAX_VALUE;
let max_lat = -Number.MAX_VALUE;
let max_lon = -Number.MAX_VALUE;
if (activity?.map?.polyline) {
for (let pt of polyline.decode(activity.map.polyline)) {
min_lat = Math.min(min_lat, pt[0]);
min_lon = Math.min(min_lon, pt[1]);
max_lat = Math.max(max_lat, pt[0]);
max_lon = Math.max(max_lon, pt[1]);
}
}
if (activity?.segments) {
for (let segment of activity.segments) {
for (let pt of segment) {
min_lat = Math.min(min_lat, pt.lat);
min_lon = Math.min(min_lon, pt.lon);
max_lat = Math.max(max_lat, pt.lat);
max_lon = Math.max(max_lon, pt.lon);
}
}
}
return {
min: {
lat: min_lat,
lng: min_lon,
},
max: {
lat: max_lat,
lng: max_lon,
},
};
}
on_click(event) {
let popup = L.popup()
.setLatLng(event.latlng)
.setContent(
`
<div><a target="_top" href="https://www.google.com/maps/search/?api=1&query=${event.latlng.lat},${event.latlng.lng}">${event.latlng.lat}, ${event.latlng.lng}</a></div>
`
)
.openOn(this.leaflet);
}
async build() {
if (this.popup) {
this.popup.remove();
}
if (!this.marker) {
return;
}
let latlng = this.marker.getLatLng();
let cost = k_store[this.to_build];
if (cost > this.currency) {
alert('Insufficient funds.');
return;
}
let message = {
type: 'gg-place',
position: {lat: latlng.lat, lng: latlng.lng},
emoji: this.to_build,
cost: cost,
};
let id = await tfrpc.rpc.appendMessage(this.whoami, message);
this.marker.remove();
this.placed_emojis.push({
position: {lat: latlng.lat, lng: latlng.lng},
emoji: this.to_build,
});
this.currency -= cost;
return this.update_map();
}
on_marker_click(event) {
this.popup = L.popup()
.setLatLng(event.latlng)
.setContent(
`
${this.to_build} (-${k_store[this.to_build]}) <input type="button" value="Build" onclick="document.getElementById('ggapp').build()"></input>
`
)
.openOn(this.leaflet);
}
snap_to_grid(latlng, fudge, zoom) {
let position = this.leaflet.options.crs.latLngToPoint(
latlng,
zoom ?? this.leaflet.getZoom()
);
position.x = Math.round(position.x / 16) * 16 + (fudge?.x ?? 0);
position.y = Math.round(position.y / 16) * 16 + (fudge?.y ?? 0);
position = this.leaflet.options.crs.pointToLatLng(
position,
zoom ?? this.leaflet.getZoom()
);
return position;
}
on_marker_move(event) {
if (!this.no_snap && this.marker) {
this.no_snap = true;
this.marker.setLatLng(
this.snap_to_grid(this.marker.getLatLng(), k_marker_snap)
);
this.no_snap = false;
}
}
on_zoom(event) {
if (this.marker) {
this.marker.setLatLng(
this.snap_to_grid(this.marker.getLatLng(), k_marker_snap)
);
}
}
on_mouse_down(event) {
if (this.marker) {
this.marker.remove();
this.marker = undefined;
}
if (this.to_build) {
this.marker = L.marker(this.snap_to_grid(event.latlng, k_marker_snap), {
icon: L.divIcon({className: 'build-icon'}),
draggable: true,
}).addTo(this.leaflet);
this.marker.on({click: this.on_marker_click.bind(this)});
this.marker.on({drag: this.on_marker_move.bind(this)});
}
}
async update_map() {
let map = this.shadowRoot.getElementById('map');
if (!map || !this.loaded_activities.length) {
this.leaflet = undefined;
this.grid_layer = undefined;
return;
}
if (!this.leaflet) {
this.leaflet = L.map(map, {
attributionControl: false,
maxZoom: 16,
bounceAtZoomLimits: false,
});
this.leaflet.on({contextmenu: this.on_click.bind(this)});
this.leaflet.on({click: this.on_mouse_down.bind(this)});
this.leaflet.on({zoom: this.on_zoom.bind(this)});
}
let self = this;
let grid_layer = L.GridLayer.extend({
createTile: function (coords) {
var tile = L.DomUtil.create('canvas', 'leaflet-tile');
var size = this.getTileSize();
tile.width = size.x;
tile.height = size.y;
var context = tile.getContext('2d');
context.font = '10pt sans';
let bounds = this._tileCoordsToBounds(coords);
let degrees = 360.0 / 2 ** coords.z;
let ul = bounds.getNorthWest();
let lr = bounds.getSouthEast();
let mini = document.createElement('canvas');
mini.width = Math.floor(size.x / 16.0);
mini.height = Math.floor(size.y / 16.0);
let mini_context = mini.getContext('2d');
let image_data = context.getImageData(0, 0, mini.width, mini.height);
for (let activity of self.loaded_activities) {
self.draw_activity_to_tile(
image_data,
mini.width,
mini.height,
ul,
lr,
activity
);
}
context.textAlign = 'left';
context.textBaseline = 'bottom';
for (let x = 0; x < mini.width; x++) {
for (let y = 0; y < mini.height; y++) {
let start = (y * mini.width + x) * 4;
let pixel = self.color_to_emoji(
image_data.data.slice(start, start + 4)
);
if (pixel) {
//context.fillRect(x * size.x / mini.width, y * size.y / mini.height, size.x / mini.width, size.y / mini.height);
context.fillText(
pixel,
(x * size.x) / mini.width,
(y * size.y) / mini.height + mini.height
);
}
}
}
for (let placed of self.placed_emojis) {
let position = self.leaflet.options.crs.latLngToPoint(
self.snap_to_grid(placed.position, undefined, coords.z),
coords.z
);
let tile_x = Math.floor(position.x / size.x);
let tile_y = Math.floor(position.y / size.y);
position.x = position.x - tile_x * size.x;
position.y = position.y - tile_y * size.y;
if (tile_x == coords.x && tile_y == coords.y) {
//context.fillRect(position.x, position.y, size.x / mini.width, size.y / mini.height);
context.fillText(
placed.emoji,
position.x,
position.y + mini.height
);
}
}
return tile;
},
});
if (this.grid_layer) {
this.grid_layer.redraw();
} else {
this.grid_layer = new grid_layer();
this.grid_layer.addTo(this.leaflet);
}
for (let activity of this.loaded_activities) {
let bounds = this.activity_bounds(activity);
this.min_lat = Math.min(this.min_lat, bounds.min.lat);
this.min_lon = Math.min(this.min_lon, bounds.min.lng);
this.max_lat = Math.max(this.max_lat, bounds.max.lat);
this.max_lon = Math.max(this.max_lon, bounds.max.lng);
}
if (this.focus) {
this.leaflet.fitBounds([this.focus.min, this.focus.max]);
this.focus = undefined;
} else {
this.leaflet.fitBounds([
[this.min_lat, this.min_lon],
[this.max_lat, this.max_lon],
]);
}
}
activity_to_color(activity) {
let color = [0, 0, 0, 255];
switch (activity.sport_type) {
/* Implies snow. */
case 'AlpineSki':
case 'BackcountrySki':
case 'NordicSki':
case 'Snowshoe':
case 'Snowboard':
color = k_color_snow;
break;
/* Implies ice. */
case 'IceSkate':
case 'InlineSkate':
color = k_color_ice;
break;
/* Implies water. */
case 'Canoeing':
case 'Kayaking':
case 'Kitesurf':
case 'Rowing':
case 'Sail':
case 'StandUpPaddling':
case 'Surfing':
case 'Swim':
case 'Windsurf':
color = k_color_water;
break;
/* Implies dirt. */
case 'EMountainBikeRide':
case 'Hike':
case 'MountainBikeRide':
case 'RockClimbing':
case 'TrailRun':
color = k_color_dirt;
break;
/* Implies pavement. */
case 'EBikeRide':
case 'GravelRide':
case 'Handcycle':
case 'Ride':
case 'RollerSki':
case 'Run':
case 'Skateboard':
case 'Badminton':
case 'Tennis':
case 'Velomobile':
case 'Walk':
case 'Wheelchair':
color = k_color_pavement;
break;
/* Grass, maybe? */
case 'Golf':
case 'Soccer':
case 'Squash':
color = k_color_grass;
break;
// Crossfit,
// Elliptical
// HighIntensityIntervalTraining
// Pickleball
// Pilates
// Racquetball
// StairStepper
// TableTennis,
// VirtualRide
// VirtualRow
// VirtualRun
// WeightTraining
// Workout
// Yoga
default:
color = k_color_default;
}
return color;
}
line(image_data, x0, y0, x1, y1, value) {
/* <3 https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm */
let dx = Math.abs(x1 - x0);
let sx = x0 < x1 ? 1 : -1;
let dy = -Math.abs(y1 - y0);
let sy = y0 < y1 ? 1 : -1;
let error = dx + dy;
while (true) {
if (
x0 >= 0 &&
y0 >= 0 &&
x0 < image_data.width &&
y0 < image_data.height
) {
let base = (y0 * image_data.width + x0) * 4;
image_data.data[base + 0] = value[0];
image_data.data[base + 1] = value[1];
image_data.data[base + 2] = value[2];
image_data.data[base + 3] = value[3];
}
if (x0 == x1 && y0 == y1) {
break;
}
let e2 = 2 * error;
if (e2 >= dy) {
if (x0 == x1) {
break;
}
error += dy;
x0 = Math.round(x0 + sx);
}
if (e2 <= dx) {
if (y0 == y1) {
break;
}
error += dx;
y0 = Math.round(y0 + sy);
}
}
}
draw_activity_to_tile(image_data, width, height, ul, lr, activity) {
let color = this.activity_to_color(activity);
if (activity?.map?.polyline) {
let last;
for (let pt of polyline.decode(activity.map.polyline)) {
let px = [
Math.floor((width * (pt[1] - ul.lng)) / (lr.lng - ul.lng)),
Math.floor((height * (pt[0] - ul.lat)) / (lr.lat - ul.lat)),
];
if (last) {
this.line(image_data, last[0], last[1], px[0], px[1], color);
}
last = px;
}
}
if (activity?.segments) {
for (let segment of activity.segments) {
let last;
for (let pt of segment) {
let px = [
Math.floor((width * (pt.lon - ul.lng)) / (lr.lng - ul.lng)),
Math.floor((height * (pt.lat - ul.lat)) / (lr.lat - ul.lat)),
];
if (last) {
this.line(image_data, last[0], last[1], px[0], px[1], color);
}
last = px;
}
}
}
}
async on_upload(event) {
try {
let file = event.srcElement.files[0];
let xml = await file.text();
let gpx = gpx_parse(xml);
let blob_id = await tfrpc.rpc.store_blob(xml);
console.log('blob_id = ', blob_id);
console.log(gpx);
let message = {
type: 'gg-activity',
mentions: [
{
link: `https://${gpx.link}/activity/${gpx.time}`,
name: 'activity_url',
},
{
link: blob_id,
name: 'activity_data',
},
],
};
console.log('id =', this.whoami, 'message = ', message);
let id = await tfrpc.rpc.appendMessage(this.whoami, message);
console.log('appended message', id);
alert('Activity uploaded.');
await this.get_activities_from_ssb();
} catch (e) {
alert(`Error: ${JSON.stringify(e, null, 2)}`);
}
}
upload() {
let input = document.createElement('input');
input.type = 'file';
input.onchange = (event) => this.on_upload(event);
input.click();
}
updated() {
this.update_map();
}
focus_map(activity) {
let bounds = this.activity_bounds(activity);
if (bounds.min.lat < bounds.max.lat && bounds.min.lng < bounds.max.lng) {
this.tab = 'map';
this.focus = bounds;
}
}
render_news() {
return html`
<ul>
${this.loaded_activities.map(
(x) => html`
<li style="cursor: pointer" @click=${() => this.focus_map(x)}>
${x.author} ${x.name ?? x.time}
</li>
`
)}
</ul>
`;
}
render_store_item(item) {
let [emoji, cost] = item;
return html`
<div>
<input type="button" value="${emoji}" @click=${() => (this.to_build = emoji)}></input> ${cost} ${emoji == this.to_build ? '<-- Will be built next' : undefined}
</div>
`;
}
render_store() {
let store = Object.assign({}, k_store);
store[this.emoji_of_the_day] = 5;
return html`
<h2>Store</h2>
<div><b>Your balance:</b> ${this.currency}</div>
${Object.entries(store).map(this.render_store_item.bind(this))}
`;
}
render() {
let header;
if (!this.user?.credentials?.session?.name) {
header = html`<div style="flex: 1 0">
Please <a target="_top" href="/login?return=${this.url}">login</a> to
Tilde Friends, first.
</div>`;
} else if (!this.strava?.access_token) {
let strava_url = `https://www.strava.com/oauth/authorize?client_id=${k_client_id}&redirect_uri=${k_redirect_url}&response_type=code&approval_prompt=auto&scope=activity%3Aread&state=${g_data.state}`;
header = html`
<div style="flex: 1 0; display: flex; flex-direction: row; align-items: center; gap: 1em; width: 100%">
<div style="flex: 1 1">Please <a target="_top" href=${strava_url}>login</a> to Strava.</div>
<span style="font-size: xx-small; flex: 1 1; word-break: break-all">${this.whoami}</span>
<input type="button" value="📁" @click=${this.upload}></input>
</div>
`;
} else {
header = html`
<div>
<div style="flex: 1 0; display: flex; flex-direction: row; align-items: center; gap: 1em; width: 100%">
<h1>Welcome, ${this.user.credentials.session.name}</h1>
<span style="font-size: xx-small; flex: 1 1; word-break: break-all">${this.whoami}</span>
<input type="button" value="📁" @click=${this.upload}></input>
</div>
<h3 ?hidden=${!this.status?.text}>${this.status?.text} <progress ?hidden=${!this.status?.max} value=${this.status?.value} max=${this.status?.max}>${this.status?.value}</progress></h3>
</div>
`;
}
let navigation = html`
<style>
#navigation input[type="button"] {
min-width: 3em;
min-height: 3em;
flex: 1 0;
font-size: large;
}
</style>
<div id="navigation" style="display: flex; flex-direction: row">
<input type="button" id="button_map" @click=${() => (this.tab = 'map')} value="🗺Map"></input>
<input type="button" id="button_news" @click=${() => (this.tab = 'news')} value="🏃News"></input>
<input type="button" id="button_friends" @click=${() => (this.tab = 'friends')} value="👫Friends"></input>
<input type="button" id="button_store" @click=${() => (this.tab = 'store')} value="🏗Store"></input>
</div>
`;
let content;
switch (this.tab) {
case 'map':
content = html`<div id="map" style="width: 100%; height: 100%"></div>`;
break;
case 'news':
content = this.render_news();
break;
case 'friends':
content = html`<div>Friends</div>`;
break;
case 'store':
content = this.render_store();
break;
}
return html`
<style>
.build-icon::before {
content: '📍';
border: 2px solid red;
}
</style>
<link rel="stylesheet" href="leaflet.css" />
<div
style="width: 100%; height: 100%; display: flex; flex-direction: column"
>
${header}
<div style="flex: 1 0; overflow: scroll">${content}</div>
${navigation}
</div>
`;
}
}
customElements.define('gg-app', GgAppElement);

View File

@@ -1,20 +0,0 @@
const k_client_id = '28276';
const k_client_secret = '3123f1f5afe132d9731111066d1d17bdb22ef27e';
const k_access_token = 'f753e77764c26252bd2d80e7c5cc17ace51a8864';
const k_refresh_token = 'f58d8e1b5a3ec3bf96e681589d5014f9a294f5a4';
const k_redirect_url = 'https://tildefriends.net/~cory/gg/login';
export async function refresh_token(token) {
let r = await fetch('https://www.strava.com/api/v3/oauth/token', {
method: 'POST',
body: `client_id=${k_client_id}&client_secret=${k_client_secret}&refresh_token=${token.refresh_token}&grant_type=refresh_token`,
});
return r?.body ? JSON.parse(utf8Decode(r.body)) : undefined;
}
export async function authorization_code(code) {
return await fetch('https://www.strava.com/api/v3/oauth/token', {
method: 'POST',
body: `client_id=${k_client_id}&client_secret=${k_client_secret}&code=${code}&grant_type=authorization_code`,
});
}

View File

@@ -1,5 +0,0 @@
{
"type": "tildefriends-app",
"emoji": "🪪",
"previous": "&kgukkyDk1RxgfzgMH6H/0QeDPIuwPZypLuAFax21ljk=.sha256"
}

View File

@@ -1,93 +0,0 @@
import * as tfrpc from '/tfrpc.js';
tfrpc.register(async function get_private_key(id) {
return bip39Words(await ssb.getPrivateKey(id));
});
tfrpc.register(async function create_id(id) {
return await ssb.createIdentity();
});
tfrpc.register(async function add_id(id) {
return await ssb.addIdentity(bip39Bytes(id));
});
tfrpc.register(async function delete_id(id) {
return await ssb.deleteIdentity(id);
});
tfrpc.register(async function reload() {
await main();
});
async function main() {
let ids = await ssb.getIdentities();
await app.setDocument(
`<body style="color: #fff">
<script>const handler = {};</script>
<script type="module">
import * as tfrpc from '/static/tfrpc.js';
handler.export_id = async function export_id(event) {
let id = event.srcElement.dataset.id;
let element = document.createElement('textarea');
element.value = await tfrpc.rpc.get_private_key(id);
element.style = 'width: 100%; read-only: true';
element.readOnly = true;
event.srcElement.parentElement.appendChild(element);
event.srcElement.onclick = event => handler.hide_id(event, element);
}
handler.add_id = async function add_id(event) {
let id = document.getElementById('add_id').value;
try {
let new_id = await tfrpc.rpc.add_id(id);
alert('Successfully imported: ' + new_id);
await tfrpc.rpc.reload();
} catch (e) {
alert('Error importing identity: ' + e);
}
}
handler.create_id = async function create_id(event) {
try {
let id = await tfrpc.rpc.create_id();
alert('Successfully created: ' + id);
await tfrpc.rpc.reload();
} catch (e) {
alert('Error creating identity: ' + e);
}
}
handler.hide_id = function hide_id(event, element) {
element.parentNode.removeChild(element);
event.srcElement.onclick = handler.export_id;
}
handler.delete_id = async function delete_id(event) {
let id = event.srcElement.dataset.id;
try {
if (prompt('Are you sure you want to delete "' + id + '"? It cannot be recovered without the exported phrase.\\n\\nEnter the word "DELETE" to confirm you wish to delete it.') === 'DELETE') {
if (await tfrpc.rpc.delete_id(id)) {
alert('Successfully deleted ID: ' + id);
}
await tfrpc.rpc.reload();
}
} catch (e) {
alert('Error deleting ID: ' + e);
}
}
</script>
<h1>SSB Identity Management</h1>
<h2>Create a new identity</h2>
<button id="create_id" onclick="handler.create_id()">Create Identity</button>
<h2>Import an SSB Identity from 12 BIP39 English Words</h2>
<textarea id="add_id" style="width: 100%" rows="4"></textarea><button id="add" onclick="handler.add_id(event)">Import Identity</button>
<h2>Identities</h2>
<ul>` +
ids
.map(
(id) => `<li>
<button onclick="handler.export_id(event)" data-id="${id}">Export Identity</button>
<button onclick="handler.delete_id(event)" data-id="${id}">Delete Identity</button>
${id}
</li>`
)
.join('\n') +
` </ul>
</body>`
);
}
main();

View File

@@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "📝",
"previous": "&2hdIDbBrAg63T2X1MzdGSF7yiqHvlnfF0PnInQLp0DA=.sha256"
"previous": "&b//KqE4Vx6kOSBRODK1p/8wjOLKZJ+CBB5IkaBt5YsM=.sha256"
}

5
apps/room.json Normal file
View File

@@ -0,0 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "📦",
"previous": "&IU+TwyM7TznD8NBfnw7tgW2zxVlMqTVxSqWFjuosLwo=.sha256"
}

13
apps/room/app.js Normal file
View File

@@ -0,0 +1,13 @@
async function main() {
let host = core.url.match(/.*\/\/(.*?)\//)[1];
let id = (await ssb.getServerIdentity()).substring(1);
let room = `net:${host}:${ssb.port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
await app.setDocument(`
<body style="color: #fff">
<h1>Server</h1>
<div>The local server address is:</div>
<div><input type="text" readonly value="${room}" style="width: 100%"></input></div>
</body>
`);
}
main();

View File

@@ -1,4 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "👟"
"emoji": "👟",
"previous": "&lYZRnT2UGQxXxYISbuaZewik9AuxBpcJumakwrePw5c=.sha256"
}

View File

@@ -38,10 +38,11 @@ class TfSneakerAppElement extends LitElement {
}
format_message(message) {
const k_flag_sequence_before_author = 1;
let out = {
previous: message.previous ?? null,
};
if (message.sequence_before_author) {
if (message.flags & k_flag_sequence_before_author) {
out.sequence = message.sequence;
out.author = message.author;
} else {
@@ -72,25 +73,104 @@ class TfSneakerAppElement extends LitElement {
return true;
}
// prettier-ignore
if (startsWith(data, [0xff, 0xd8, 0xff, 0xdb]) ||
startsWith(data, [0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01]) ||
if (
startsWith(data, [0xff, 0xd8, 0xff, 0xdb]) ||
startsWith(
data,
[0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01]
) ||
startsWith(data, [0xff, 0xd8, 0xff, 0xee]) ||
startsWith(data, [0xff, 0xd8, 0xff, 0xe1, null, null, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00])) {
startsWith(data, [
0xff,
0xd8,
0xff,
0xe1,
null,
null,
0x45,
0x78,
0x69,
0x66,
0x00,
0x00,
])
) {
return '.jpg';
} else if (startsWith(data, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
} else if (
startsWith(data, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
) {
return '.png';
} else if (startsWith(data, [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]) ||
startsWith(data, [0x47, 0x49, 0x46, 0x38, 0x39, 0x61])) {
} else if (
startsWith(data, [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]) ||
startsWith(data, [0x47, 0x49, 0x46, 0x38, 0x39, 0x61])
) {
return '.gif';
} else if (startsWith(data, [0x52, 0x49, 0x46, 0x46, null, null, null, null, 0x57, 0x45, 0x42, 0x50])) {
} else if (
startsWith(data, [
0x52,
0x49,
0x46,
0x46,
null,
null,
null,
null,
0x57,
0x45,
0x42,
0x50,
])
) {
return '.webp';
} else if (startsWith(data, [0x3c, 0x73, 0x76, 0x67])) {
return '.svg';
} else if (startsWith(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32])) {
} else if (
startsWith(data, [
null,
null,
null,
null,
0x66,
0x74,
0x79,
0x70,
0x6d,
0x70,
0x34,
0x32,
])
) {
return '.mp3';
} else if (startsWith(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d]) ||
startsWith(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32])) {
} else if (
startsWith(data, [
null,
null,
null,
null,
0x66,
0x74,
0x79,
0x70,
0x69,
0x73,
0x6f,
0x6d,
]) ||
startsWith(data, [
null,
null,
null,
null,
0x66,
0x74,
0x79,
0x70,
0x6d,
0x70,
0x34,
0x32,
])
) {
return '.mp4';
} else {
return '.bin';
@@ -109,7 +189,12 @@ class TfSneakerAppElement extends LitElement {
)[0].total;
while (true) {
let messages = await tfrpc.rpc.query(
'SELECT * FROM messages WHERE author = ? AND SEQUENCE > ? ORDER BY sequence LIMIT 100',
`
SELECT author, id, sequence, timestamp, hash, json(content) AS content, signature, flags
FROM messages
WHERE author = ? AND SEQUENCE > ?
ORDER BY sequence LIMIT 100
`,
[id, sequence]
);
if (messages?.length) {

View File

@@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🐌",
"previous": "&DUxMMCJcuhm6S9jg/eKgEyWodkITu6Tg9g5I5wgLWFU=.sha256"
"previous": "&Xs1X5TzLCk6KVr+5IDc80JAHYxJyoD10cXKBUYpFqWQ=.sha256"
}

View File

@@ -107,7 +107,7 @@ class TfElement extends LitElement {
let abouts = await tfrpc.rpc.query(
`
SELECT
messages.*
messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM
messages,
json_each(?1) AS following
@@ -118,7 +118,7 @@ class TfElement extends LitElement {
json_extract(messages.content, '$.type') = 'about'
UNION
SELECT
messages.*
messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM
messages,
json_each(?2) AS following
@@ -158,7 +158,7 @@ class TfElement extends LitElement {
async fetch_new_message(id) {
let messages = await tfrpc.rpc.query(
`
SELECT messages.*
SELECT messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.id = ?
@@ -221,7 +221,7 @@ class TfElement extends LitElement {
this.tags = await tfrpc.rpc.query(
`
WITH
recent AS (SELECT id, content FROM messages
recent AS (SELECT id, json(content) AS content FROM messages
WHERE messages.timestamp > ? AND json_extract(content, '$.type') = 'post'
ORDER BY timestamp DESC LIMIT 1024),
recent_channels AS (SELECT recent.id, '#' || json_extract(content, '$.channel') AS tag

View File

@@ -262,7 +262,7 @@ class TfComposeElement extends LitElement {
try {
let rows = await tfrpc.rpc.query(
`
SELECT messages.content FROM messages_fts(?)
SELECT json(messages.content) FROM messages_fts(?)
JOIN messages ON messages.rowid = messages_fts.rowid
WHERE messages.content LIKE ?
ORDER BY timestamp DESC LIMIT 10

View File

@@ -3,7 +3,7 @@ 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.
** Provide a list of IDs, and this lets the user pick one.
*/
class TfIdentityPickerElement extends LitElement {
static get properties() {

View File

@@ -265,10 +265,7 @@ ${JSON.stringify(mention, null, 2)}</pre
new CustomEvent('tf-expand', {
bubbles: true,
composed: true,
detail: {
id: (this.message.id || '') + (tag || ''),
expanded: expanded,
},
detail: {id: (this.message.id || '') + (tag || ''), expanded: expanded},
})
);
}
@@ -497,7 +494,7 @@ ${JSON.stringify(mention, null, 2)}</pre
<tf-compose
whoami=${this.whoami}
.users=${this.users}
root=${this.message.content.root || this.message.id}
root=${content.root || this.message.id}
branch=${this.message.id}
.drafts=${this.drafts}
@tf-discard=${this.discard_reply}
@@ -684,7 +681,7 @@ ${JSON.stringify(content, null, 2)}</pre
<tf-compose
whoami=${this.whoami}
.users=${this.users}
root=${this.message.content.root || this.message.id}
root=${content.root || this.message.id}
branch=${this.message.id}
.drafts=${this.drafts}
@tf-discard=${this.discard_reply}

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,7 @@ class TfTabMentionsElement extends LitElement {
console.log('Loading...', this.whoami);
let results = await tfrpc.rpc.query(
`
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages_fts(?)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?) AS following ON messages.author = following.value

View File

@@ -33,12 +33,12 @@ class TfTabNewsFeedElement extends LitElement {
if (this.hash.startsWith('#@')) {
let r = await tfrpc.rpc.query(
`
WITH mine AS (SELECT messages.*
WITH mine AS (SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages
WHERE messages.author = ?
ORDER BY sequence DESC
LIMIT 20)
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM mine
JOIN messages_refs ON mine.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
@@ -51,11 +51,11 @@ class TfTabNewsFeedElement extends LitElement {
} else if (this.hash.startsWith('#%')) {
return await tfrpc.rpc.query(
`
SELECT messages.*
SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages
WHERE id = ?1
UNION
SELECT messages.*
SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages JOIN messages_refs
ON messages.id = messages_refs.message
WHERE messages_refs.ref = ?1
@@ -69,17 +69,17 @@ class TfTabNewsFeedElement extends LitElement {
promises.push(
tfrpc.rpc.query(
`
WITH news AS (SELECT messages.*
WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.timestamp > ? AND messages.timestamp < ?
ORDER BY messages.timestamp DESC)
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
UNION
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.message
JOIN messages ON messages_refs.ref = messages.id
@@ -107,18 +107,18 @@ class TfTabNewsFeedElement extends LitElement {
this.start_time = last_start_time - 24 * 60 * 60 * 1000;
let more = await tfrpc.rpc.query(
`
WITH news AS (SELECT messages.*
WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.timestamp > ?
AND messages.timestamp <= ?
ORDER BY messages.timestamp DESC)
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
UNION
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.message
JOIN messages ON messages_refs.ref = messages.id

View File

@@ -35,7 +35,7 @@ class TfTabSearchElement extends LitElement {
await tfrpc.rpc.setHash('#q=' + encodeURIComponent(query));
let results = await tfrpc.rpc.query(
`
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages_fts(?)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?) AS following ON messages.author = following.value

1
apps/user_settings.json Normal file
View File

@@ -0,0 +1 @@
{"type": "tildefriends-app", "emoji": "⚙️"}

60
apps/user_settings/app.js Normal file
View File

@@ -0,0 +1,60 @@
import * as tfrpc from '/tfrpc.js';
tfrpc.register(async function getIdentities() {
return ssb.getIdentities();
});
tfrpc.register(async function createID(id) {
return await ssb.createIdentity();
});
tfrpc.register(async function getPrivateKey(id) {
return bip39Words(await ssb.getPrivateKey(id));
});
tfrpc.register(async function addID(id) {
return await ssb.addIdentity(bip39Bytes(id));
});
tfrpc.register(async function deleteID(id) {
return await ssb.deleteIdentity(id);
});
tfrpc.register(async function getThemes() {
// TODO
return ['solarized', 'gruvbox', 'light'];
});
tfrpc.register(async function getTheme() {
// TODO
return 'gruvbox';
});
tfrpc.register(async function setTheme() {
// TODO
console.warn('setTheme called - not implemented');
return null;
});
tfrpc.register(async function reload() {
await main();
});
async function main() {
// Get body.html
const body = utf8Decode(await getFile('body.html'));
// Build the document
const document = `
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/static/tildefriends-v1.css"/>
<script src="tf-theme-picker.js" type="module"></script>
<script src="tf-password-form.js" type="module"></script>
<script src="tf-delete-account-btn.js" type="module"></script>
<script src="tf-identity-manager.js" type="module"></script>
</head>
<body class="flex-column">
${body}
</body>
</html>`;
// Send it to the browser
app.setDocument(document);
}
main();

View File

@@ -0,0 +1,20 @@
<h1>Your settings</h1>
<div class="box flex-column">
<h2>Appearance</h2>
<tf-theme-picker></tf-theme-picker>
</div>
<div class="box flex-column">
<h2>Danger Zone</h2>
<h3>Manage your identities</h3>
<tf-identity-manager></tf-identity-manager>
<h3>Change my password</h3>
<tf-password-form></tf-password-form>
<h3>Delete your account</h3>
<tf-delete-account-btn></tf-delete-account-btn>
</div>

View File

@@ -0,0 +1,36 @@
import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
class TfDeleteAccountButtonElement extends LitElement {
static get properties() {
return {};
}
constructor() {
super();
}
deleteAccount() {
const res = confirm(
'Are you really sure you want to delete your account ?'
);
if (!res) return;
console.warn('TODO');
}
render() {
return html`
<link rel="stylesheet" href="/static/tildefriends-v1.css" />
<span>This action is irreversible !</span>
<button class="red" @click=${this.deleteAccount}>
[Not implemented] Delete my Tilde Friends account
</button>
`;
}
}
customElements.define('tf-delete-account-btn', TfDeleteAccountButtonElement);

View File

@@ -0,0 +1,118 @@
import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
class TfIdentityManagerElement extends LitElement {
static get properties() {
return {
ids: {type: Array},
};
}
constructor() {
super();
this.ids = [];
this.load();
}
async load() {
this.ids = await tfrpc.rpc.getIdentities();
}
async createIdentity() {
try {
const id = await tfrpc.rpc.createID();
alert('Successfully created: ' + id);
await tfrpc.rpc.reload();
} catch (err) {
alert('Error creating identity: ' + err);
}
}
async importIdentity() {
const words = this.renderRoot?.querySelector('#import-id-textarea').value;
if (!words) return;
try {
const newID = await tfrpc.rpc.addID(words);
if (newID) alert('Successfully imported a new identity.');
else alert('This identity already exists or is invalid.');
await tfrpc.rpc.reload();
} catch (err) {
alert('Error importing identity: ' + err);
}
}
async exportIdentity(id) {
alert(
'Your private key is:\n' +
(await tfrpc.rpc.getPrivateKey(id)) +
'\nDo not share it with anyone!'
);
}
async deleteIdentity(id) {
try {
if (
prompt(
'Are you sure you want to delete "' +
id +
'"? It cannot be recovered without the exported phrase.\\n\\nEnter the word "DELETE" to confirm you wish to delete it.'
) === 'DELETE'
) {
if (await tfrpc.rpc.deleteID(id)) {
alert('Successfully deleted ID: ' + id);
}
await tfrpc.rpc.reload();
}
} catch (e) {
alert('Error deleting ID: ' + e);
}
}
render() {
return html` <link rel="stylesheet" href="/static/tildefriends-v1.css" />
<style>
.id-span {
font-family: monospace;
margin-left: 8px;
}
</style>
<h4>Create a new identity</h4>
<button id="create-id" class="green" @click=${this.createIdentity}>
Create Identity
</button>
<h4>Import an SSB Identity from 12 BIP39 English Words</h4>
<textarea id="import-id-textarea" style="width: 100%" rows="4"></textarea>
<button class="green" @click=${this.importIdentity}>
Import Identity
</button>
<h4>Warning !</h4>
<strong
>Anybody that knows your private key can gain total access over your
account.</strong
>
<br /><br />
Tilde Friends' contributors will never ask you for your private key !
<ul>
${this.ids.map(
(id) =>
html` <li>
<button class="blue" @click=${() => this.exportIdentity(id)}>
Export Identity
</button>
<button class="red" @click=${() => this.deleteIdentity(id)}>
Delete Identity
</button>
<span class="id-span">${id}</span>
</li>`
)}
</ul>`;
}
}
customElements.define('tf-identity-manager', TfIdentityManagerElement);

View File

@@ -0,0 +1,82 @@
import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
class TfPasswordFormElement extends LitElement {
static get properties() {
return {
//selected: {type: String},
};
}
constructor() {
super();
}
/**
* Checks a password against different requirements
* @param {string} password the password to validate
* @returns
*/
validatePassword(password) {
// TODO(tasiaiso)
return true;
}
submitPassword() {
const currentPwd = this.shadowRoot.getElementById('current').value;
const newPwd = this.shadowRoot.getElementById('new').value;
const repeatPwd = this.shadowRoot.getElementById('Repeat').value;
if (!(newPwd === repeatPwd)) {
return;
}
// TODO
// tfrpc.changePassword()
}
render() {
return html`
<link rel="stylesheet" href="/static/tildefriends-v1.css" />
<style>
.grid {
display: grid;
grid-template-columns: auto auto;
}
</style>
<div class="grid">
<label for="current">Current password:</label>
<input
type="password"
id="current"
name="current"
autocomplete="current-password"
/>
<label for="new">Enter new password:</label>
<input
type="password"
id="new"
name="new"
autocomplete="new-password"
/>
<label for="repeat">Repeat new password:</label>
<input
type="password"
id="repeat"
name="repeat"
autocomplete="new-password"
/>
</div>
<button @click=${this.submitPassword} class="red">
[Not implemented] Change my password
</button>
`;
}
}
customElements.define('tf-password-form', TfPasswordFormElement);

View File

@@ -0,0 +1,51 @@
import {LitElement, html, nothing} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
class TfThemePickerElement extends LitElement {
static get properties() {
return {
selected: {type: String},
themes: {type: Array},
};
}
constructor() {
super();
this.load();
}
async load() {
this.themes = await tfrpc.rpc.getThemes();
this.selected = await tfrpc.rpc.getTheme();
let select = this.renderRoot?.querySelector('#theme-select');
select.value = this.selected;
}
changed(event) {
this.selected = event.srcElement.value;
console.log('selected theme', this.selected);
// TODO
}
render() {
return html`
<link rel="stylesheet" href="/static/tildefriends-v1.css" />
<label for="theme">[Not implemented] Choose your theme:</label>
<select
name="theme"
id="theme-select"
?hidden=${!this.themes?.length}
@change=${this.changed}
>
${(this.themes ?? []).map(
(name) => html`<option value=${name}>${name}</option>`
)}
</select>
`;
}
}
customElements.define('tf-theme-picker', TfThemePickerElement);

View File

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

View File

@@ -2,8 +2,9 @@
<html>
<head>
<base target="_top" />
<link rel="stylesheet" href="tildefriends.css" />
</head>
<body style="color: #fff">
<body>
<tf-collections-app></tf-collections-app>
<script>
window.litDisableBundleWarning = true;

View File

@@ -5,6 +5,7 @@ class TfCollectionElement extends LitElement {
static get properties() {
return {
whoami: {type: String},
category: {type: String},
collection: {type: Object},
selected_id: {type: String},
is_creating: {type: Boolean},
@@ -75,9 +76,10 @@ class TfCollectionElement extends LitElement {
render() {
let self = this;
return html`
<span style="display: inline-flex; flex-direction: row">
<link rel="stylesheet" href="tildefriends.css"/>
<span class="inline-flex-row">
<select @change=${this.on_selected} id="select" value=${this.selected_id}>
<option value="" ?selected=${this.selected_id === ''} disabled hidden>(select)</option>
<option value="" ?selected=${this.selected_id === ''} disabled hidden>(select ${this.category})</option>
${Object.values(this.collection ?? {})
.sort((x, y) => x.name.localeCompare(y.name))
.map(
@@ -91,22 +93,22 @@ class TfCollectionElement extends LitElement {
)}
</select>
<span ?hidden=${!this.is_renaming || !this.whoami}>
<span style="display: inline-flex; flex-direction: row; margin-left: 8px; margin-right: 8px">
<span class="inline-flex-row" style="margin-left: 8px; margin-right: 8px">
<label for="rename_name">🏷Rename to:</label>
<input type="text" id="rename_name"></input>
<button @click=${this.on_rename}>Rename ${this.type}</button>
<button @click=${() => (self.is_renaming = false)}>x</button>
</span>
</span>
<button @click=${() => (self.is_renaming = true)} ?disabled=${this.is_renaming || !this.selected_id} ?hidden=${!this.whoami}>🏷</button>
<button @click=${self.on_tombstone} ?disabled=${!this.selected_id} ?hidden=${!this.whoami}>🪦</button>
<button class="yellow" @click=${() => (self.is_renaming = true)} ?disabled=${this.is_renaming || !this.selected_id} ?hidden=${!this.whoami}>🏷</button>
<button class="red" @click=${self.on_tombstone} ?disabled=${!this.selected_id} ?hidden=${!this.whoami}>🪦</button>
<span ?hidden=${!this.is_creating || !this.whoami}>
<label for="create_name">New ${this.type} name:</label>
<input type="text" id="create_name"></input>
<button @click=${this.on_create}>Create ${this.type}</button>
<button @click=${() => (self.is_creating = false)}>x</button>
</span>
<button @click=${() => (self.is_creating = true)} ?hidden=${this.is_creating || !this.whoami}>+</button>
<button class="green" @click=${() => (self.is_creating = true)} ?hidden=${this.is_creating || !this.whoami}>+</button>
</span>
`;
}

View File

@@ -28,6 +28,7 @@ class TfIdentityPickerElement extends LitElement {
render() {
return html`
<link rel="stylesheet" href="tildefriends.css" />
<select @change=${this.changed} style="max-width: 100%">
${(this.ids ?? []).map(
(id) =>

View File

@@ -255,12 +255,22 @@ class TfCollectionsAppElement extends LitElement {
render() {
let self = this;
return html`
<link rel="stylesheet" href="tildefriends.css"/>
<style>
.toc:hover {
.toc-item {
white-space: nowrap;
cursor: pointer;
}
.toc-item:hover {
background-color: #0cc;
}
.toc.selected {
.toc-item.selected {
background-color: #088;
font-weight: bold;
}
.table-of-contents {
flex: 0 0;
margin-right: 16px;
}
</style>
<div>
@@ -272,6 +282,7 @@ class TfCollectionsAppElement extends LitElement {
html`<tf-collection
.collection=${this.wikis}
whoami=${this.whoami}
category="wiki"
selected_id=${this.wiki?.id}
@create=${this.on_wiki_create}
@rename=${this.on_wiki_rename}
@@ -284,6 +295,7 @@ class TfCollectionsAppElement extends LitElement {
html`<tf-collection
.collection=${this.wiki_docs}
whoami=${this.whoami}
category="document"
selected_id=${this.wiki_doc &&
this.wiki_doc?.parent == this.wiki?.id
? this.wiki_doc?.id
@@ -298,9 +310,9 @@ class TfCollectionsAppElement extends LitElement {
<div ?hidden=${!this.wiki?.editors || !this.expand_editors}>
<div>
<ul>
${this.wiki?.editors.map((id) => html`<li><button ?hidden=${id == this.whoami} @click=${() => self.on_remove_editor(id)}>x</button> ${id}</li>`)}
${this.wiki?.editors.map((id) => html`<li><button class="red" ?hidden=${id == this.whoami} @click=${() => self.on_remove_editor(id)}>x</button> ${id}</li>`)}
<li>
<button @click=${() => (self.adding_editor = true)} ?hidden=${this.wiki?.editors?.indexOf(this.whoami) == -1 || this.adding_editor}>+</button>
<button class="green" @click=${() => (self.adding_editor = true)} ?hidden=${this.wiki?.editors?.indexOf(this.whoami) == -1 || this.adding_editor}>+</button>
<div ?hidden=${!this.adding_editor}>
<label for="add_editor">Add Editor:</label>
<input type="text" id="add_editor"></input>
@@ -312,18 +324,19 @@ class TfCollectionsAppElement extends LitElement {
</div>
</div>
</div>
<div style="display: flex; flex-direction: row">
<div style="flex: 0 0">
<div class="flex-row">
<div class="box table-of-contents">
${Object.values(this.wikis || {})
.sort((x, y) => x.name.localeCompare(y.name))
.map(
(wiki) => html`
<div
class="toc ${self.wiki?.id === wiki.id ? 'selected' : ''}"
style="white-space: nowrap; cursor: pointer"
class="toc-item ${self.wiki?.id === wiki.id
? 'selected'
: ''}"
@click=${() => self.on_wiki_changed({detail: {value: wiki}})}
>
${wiki.name}
${self.wiki?.id === wiki.id ? '' : '>'} ${wiki.name}
</div>
<ul>
${Object.values(self.wiki_docs || {})
@@ -332,14 +345,12 @@ class TfCollectionsAppElement extends LitElement {
.map(
(doc) => html`
<li
class="toc ${self.wiki_doc?.id === doc.id
class="toc-item ${self.wiki_doc?.id === doc.id
? 'selected'
: ''}"
style="white-space: nowrap; cursor: pointer; list-style: none; text-indent: -1rem"
style="list-style: none; text-indent: -1rem"
@click=${() =>
self.on_wiki_doc_changed({
detail: {value: doc},
})}
self.on_wiki_doc_changed({detail: {value: doc}})}
>
${doc?.private ? '🔒' : '📄'} ${doc.name}
</li>

View File

@@ -84,7 +84,11 @@ class TfWikiDocElement extends LitElement {
async load_blob() {
let blob = await tfrpc.rpc.get_blob(this.value?.blob);
if (blob.endsWith('.box')) {
if (!blob) {
console.warn(
"no blob found, we're going to assume the document is empty (load_blob())"
);
} else if (blob.endsWith('.box')) {
let d = await tfrpc.rpc.try_decrypt(this.whoami, blob);
if (d) {
blob = d;
@@ -253,85 +257,43 @@ class TfWikiDocElement extends LitElement {
let self = this;
let thumbnail_ref = this.thumbnail(this.blob);
return html`
<link rel="stylesheet" href="tildefriends.css"/>
<style>
a:link {
color: #268bd2;
}
a:visited {
color: #6c71c4;
}
a:hover {
color: #859900;
}
a:active {
color: #2aa198;
}
a:link { color: #268bd2 }
a:visited { color: #6c71c4 }
a:hover { color: #859900 }
a:active { color: #2aa198 }
#editor-text-area {
background-color: #00000040;
color: white;
style="flex: 1 1;
min-height: 10em;
font-size: larger;
${this.value?.private ? 'border: 4px solid #800' : ''}
</style>
<div style="display: inline-flex; flex-direction: row">
<button
?disabled=${!this.whoami || this.is_editing}
@click=${() => (self.is_editing = true)}
>
Edit
</button>
<button
?disabled=${this.blob == this.blob_original}
@click=${this.on_save_draft}
>
Save Draft
</button>
<button
?disabled=${this.blob == this.blob_original && !this.value?.draft}
@click=${this.on_publish}
>
Publish
</button>
<button ?disabled=${!this.is_editing} @click=${this.on_discard}>
Discard
</button>
<button
?disabled=${!this.is_editing}
@click=${() =>
(self.value = Object.assign({}, self.value, {
private: !self.value.private,
}))}
>
${this.value?.private ? 'Make Public' : 'Make Private'}
</button>
<button ?disabled=${!this.is_editing} @click=${this.on_blog_publish}>
Publish Blog
</button>
<div class="inline-flex-row">
<button ?disabled=${!this.whoami || this.is_editing} @click=${() => (self.is_editing = true)}>Edit</button>
<button ?disabled=${this.blob == this.blob_original} @click=${this.on_save_draft}>Save Draft</button>
<button ?disabled=${this.blob == this.blob_original && !this.value?.draft} @click=${this.on_publish}>Publish</button>
<button ?disabled=${!this.is_editing} @click=${this.on_discard}>Discard</button>
<button ?disabled=${!this.is_editing} @click=${() => (self.value = Object.assign({}, self.value, {private: !self.value.private}))}>${this.value?.private ? 'Make Public' : 'Make Private'}</button>
<button ?disabled=${!this.is_editing} @click=${this.on_blog_publish}>Publish Blog</button>
</div>
<div ?hidden=${!this.value?.private} style="color: #800">
🔒 document is private
</div>
<div
style="display: flex; flex-direction: row; ${this.value?.private
? 'border-top: 4px solid #800'
: ''}"
>
<div ?hidden=${!this.value?.private} style="color: #800">🔒 document is private</div>
<div class="flex-column" ${this.value?.private ? 'border-top: 4px solid #800' : ''}">
<textarea
rows="25"
?hidden=${!this.is_editing}
style="flex: 1 1; min-height: 10em; ${this.value?.private
? 'border: 4px solid #800'
: ''}"
id="editor-text-area"
@input=${this.on_edit}
@paste=${this.paste}
.value=${this.blob ?? ''}
></textarea>
<div style="flex: 1 1">
<div
?hidden=${!this.is_editing}
style="border: 1px solid #fff; border-radius: 1em; padding: 0.5em"
>
<img
?hidden=${!thumbnail_ref}
style="max-width: 128px; max-height: 128px; float: right"
src="/${thumbnail_ref}/view"
/>
<h1 ?hidden=${!this.title(this.blob)}>
${unsafeHTML(this.markdown(this.title(this.blob)))}
</h1>
.value=${this.blob ?? ''}></textarea>
<div style="flex: 1 1; margin-top: 16px">
<div ?hidden=${!this.is_editing} class="box">
Summary
<img ?hidden=${!thumbnail_ref} style="max-width: 128px; max-height: 128px; float: right" src="/${thumbnail_ref}/view">
<h1 ?hidden=${!this.title(this.blob)}>${unsafeHTML(this.markdown(this.title(this.blob)))}</h1>
${unsafeHTML(this.markdown(this.summary(this.blob)))}
</div>
${unsafeHTML(this.markdown(this.blob))}

115
apps/wiki/tildefriends.css Normal file
View File

@@ -0,0 +1,115 @@
/*
* Tilde Friends core stylesheet
* This is a prototype; things may change based on feedback.
*
* This Software is an external library that is part of
* Tilde Friends and is shared under the MIT license.
*
* Inject this file in your app at tildefriends.css
* and use this tag to import it:
* <link rel="stylesheet" href="tildefriends.css"/>
*
* Revision 0 / 2024 M02 19
*/
body {
color: white;
font-family: sans-serif;
}
button,
.button,
input[type='button'],
input[type='submit'],
select {
border: none;
border-radius: 8px;
padding: 8px 12px;
text-align: center;
text-decoration: none;
display: inline-block;
margin: 4px;
&.red {
background-color: #bd1e24;
color: white;
}
&.green {
background-color: #18922d;
color: white;
}
&.blue {
background-color: #0067a7;
color: white;
}
&.yellow {
background-color: #ee9600;
color: black;
}
&:hover {
filter: brightness(0.75);
}
}
a:link {
color: #268bd2;
}
a:visited {
color: #6c71c4;
}
a:hover {
color: #859900;
}
a:active {
color: #2aa198;
}
table {
border-collapse: collapse;
width: 100%;
}
td,
th {
border: 1px solid #ffffff40;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #ffffff20;
}
.flex {
display: flex;
}
.flex-column {
display: flex;
flex-direction: column;
}
.flex-row {
display: flex;
flex-direction: row;
}
.inline-flex-row {
display: inline-flex;
flex-direction: row;
}
.box {
background-color: #00000020;
border: 1px solid grey;
border-radius: 8px;
padding: 16px;
margin: 4px;
}

BIN
bleh.tar.xz Normal file

Binary file not shown.

View File

@@ -211,7 +211,6 @@ function isNameValid(name) {
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) {

View File

@@ -171,9 +171,7 @@ class TfNavigationElement extends LitElement {
let self = this;
return html`
<style>
/* prettier-ignore */
${k_global_style}
.tooltip {
${k_global_style} .tooltip {
position: absolute;
z-index: 1;
display: none;
@@ -1647,7 +1645,11 @@ async function sourcePretty() {
let formatted = await prettier.format(source, {
parser: 'babel',
plugins: [babel, estree],
trailingComma: 'es5',
useTabs: true,
semi: true,
singleQuote: true,
bracketSpacing: false,
});
if (source !== formatted) {
gEditor.dispatch({
@@ -1660,6 +1662,31 @@ async function sourcePretty() {
}
}
function toggleVisibleWhitespace() {
let editor_style = document.getElementById('editor_style');
/*
* There is likely a better way to do this, but stomping on the CSS was
* the easiest to wrangle at the time.
*/
if (editor_style.innerHTML.length) {
editor_style.innerHTML = '';
window.localStorage.setItem('visible_whitespace', '0');
} else {
editor_style.innerHTML = css`
.cm-trailingSpace {
background-color: unset !important;
}
.cm-highlightTab {
background-image: unset !important;
}
.cm-highlightSpace:before {
content: unset !important;
}
`;
window.localStorage.setItem('visible_whitespace', '1');
}
}
// TODOC
window.addEventListener('load', function () {
window.addEventListener('hashchange', hashChange);
@@ -1685,6 +1712,9 @@ window.addEventListener('load', function () {
document
.getElementById('pretty')
.addEventListener('click', () => sourcePretty());
document
.getElementById('whitespace')
.addEventListener('click', () => toggleVisibleWhitespace());
document
.getElementById('trace_button')
.addEventListener('click', function (event) {
@@ -1698,4 +1728,7 @@ window.addEventListener('load', function () {
} else {
closeEditor();
}
if (window.localStorage.getItem('visible_whitespace') == '1') {
toggleVisibleWhitespace();
}
});

View File

@@ -18,20 +18,104 @@ const k_mime_types = {
svg: 'image/svg+xml',
};
// prettier-ignore
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, 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: [
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: [
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: [
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'},
];
@@ -494,6 +578,7 @@ async function getProcessBlob(blobId, key, options) {
imports.ssb = Object.fromEntries(
Object.keys(ssb).map((key) => [key, ssb[key].bind(ssb)])
);
imports.ssb.port = tildefriends.ssb_port;
imports.ssb.createIdentity = function () {
if (
process.credentials &&

View File

@@ -57,7 +57,7 @@
class="w3-bar-item w3-button w3-blue"
id="icon"
name="icon"
accesskey="i"
accesskey="j"
onmouseover="set_access_key_title(event)"
data-tip="Set an icon/emoji for the app"
>
@@ -93,6 +93,16 @@
>
🧼
</button>
<button
class="w3-bar-item w3-button w3-blue"
id="whitespace"
name="whitespace"
accesskey="w"
onmouseover="set_access_key_title(event)"
data-tip="Toggle visible whitespace"
>
</button>
<input
class="w3-bar-item w3-input w3-border w3-blue"
type="text"
@@ -125,6 +135,7 @@
<tf-files-pane style="overflow: auto"></tf-files-pane>
</div>
<div style="flex: 1 1; overflow: auto">
<style id="editor_style"></style>
<div id="editor" style="width: 100%; height: 100%"></div>
</div>
</div>

114
core/tildefriends-v1.css Normal file
View File

@@ -0,0 +1,114 @@
/*
* Tilde Friends core stylesheet
*
* This Software is an external library that is part of
* Tilde Friends and is shared under the MIT license.
*
* Inject this file in your app at tildefriends.css
* and use this tag to import it:
* <link rel="stylesheet" href="/static/tildefriends-v1.css"/>
*
* v1.0 / 2024 M03 21
*/
body {
color: white;
font-family: sans-serif;
}
button,
.button,
input[type='button'],
input[type='submit'],
select {
border: none;
border-radius: 8px;
padding: 8px 12px;
text-align: center;
text-decoration: none;
display: inline-block;
margin: 4px;
&.red {
background-color: #bd1e24;
color: white;
}
&.green {
background-color: #18922d;
color: white;
}
&.blue {
background-color: #0067a7;
color: white;
}
&.yellow {
background-color: #ee9600;
color: black;
}
&:hover {
filter: brightness(0.75);
}
}
a:link {
color: #268bd2;
}
a:visited {
color: #6c71c4;
}
a:hover {
color: #859900;
}
a:active {
color: #2aa198;
}
table {
border-collapse: collapse;
width: 100%;
}
td,
th {
border: 1px solid #ffffff40;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #ffffff20;
}
.flex {
display: flex;
}
.flex-column {
display: flex;
flex-direction: column;
}
.flex-row {
display: flex;
flex-direction: row;
}
.inline-flex-row {
display: inline-flex;
flex-direction: row;
}
.box {
background-color: #00000020;
border: 1px solid grey;
border-radius: 8px;
padding: 16px;
margin: 4px;
}

File diff suppressed because one or more lines are too long

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

@@ -7,21 +7,21 @@
"dependencies": {
"@codemirror/lang-css": "^6.2.1",
"@codemirror/lang-html": "^6.4.8",
"@codemirror/lang-javascript": "^6.2.1",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/theme-one-dark": "^6.1.2",
"@rollup/plugin-node-resolve": "^15.2.3",
"codemirror": "^6.0.1",
"rollup": "^4.9.6"
"rollup": "^4.13.0"
},
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4"
}
},
"node_modules/@codemirror/autocomplete": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.12.0.tgz",
"integrity": "sha512-r4IjdYFthwbCQyvqnSlx0WBHRHi8nBvU+WjJxFUij81qsBfhNudf/XKKmmC2j3m0LaOYUQTf3qiEK1J8lO1sdg==",
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.15.0.tgz",
"integrity": "sha512-G2Zm0mXznxz97JhaaOdoEG2cVupn4JjPaS4AcNvZzhOsnnG9YVN68VzfoUw6dYTsIxT6a/cmoFEN47KAWhXaOg==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
@@ -75,9 +75,9 @@
}
},
"node_modules/@codemirror/lang-javascript": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.1.tgz",
"integrity": "sha512-jlFOXTejVyiQCW3EQwvKH0m99bUYIw40oPmFjSX2VS78yzfe0HELZ+NEo9Yfo1MkGRpGlj3Gnu4rdxV1EnAs5A==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz",
"integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.6.0",
@@ -88,24 +88,6 @@
"@lezer/javascript": "^1.0.0"
}
},
"node_modules/@codemirror/lang-javascript/node_modules/@lezer/javascript": {
"version": "1.4.13",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.13.tgz",
"integrity": "sha512-5IBr8LIO3xJdJH1e9aj/ZNLE4LSbdsx25wFmGRAZsj2zSmwAYjx26JyU/BYOCpRQlu1jcv1z3vy4NB9+UkfRow==",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.1.3",
"@lezer/lr": "^1.3.0"
}
},
"node_modules/@codemirror/lang-javascript/node_modules/@lezer/lr": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/lang-json": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
@@ -128,19 +110,6 @@
"style-mod": "^4.0.0"
}
},
"node_modules/@codemirror/language/node_modules/@lezer/lr": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/language/node_modules/style-mod": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz",
"integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA=="
},
"node_modules/@codemirror/lint": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.5.0.tgz",
@@ -151,11 +120,6 @@
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/lint/node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
},
"node_modules/@codemirror/search": {
"version": "6.5.6",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
@@ -166,11 +130,6 @@
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/search/node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
},
"node_modules/@codemirror/state": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
@@ -188,24 +147,72 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.24.0",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.24.0.tgz",
"integrity": "sha512-zK6m5pNkdhdJl8idPP1gA4N8JKTiSsOz8U/Iw+C1ChMwyLG7+MLiNXnH/wFuAk6KeGEe33/adOiAh5jMqee03w==",
"version": "6.25.1",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.25.1.tgz",
"integrity": "sha512-2LXLxsQnHDdfGzDvjzAwZh2ZviNJm7im6tGpa0IONIDnFd8RZ80D2SNi8PDi6YjKcMoMRK20v6OmKIdsrwsyoQ==",
"dependencies": {
"@codemirror/state": "^6.4.0",
"style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@codemirror/view/node_modules/style-mod": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz",
"integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA=="
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@codemirror/view/node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@lezer/common": {
"version": "1.2.1",
@@ -222,14 +229,6 @@
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/css/node_modules/@lezer/lr": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lezer/highlight": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz",
@@ -239,21 +238,23 @@
}
},
"node_modules/@lezer/html": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.8.tgz",
"integrity": "sha512-EXseJ3pUzWxE6XQBQdqWHZqqlGQRSuNMBcLb6mZWS2J2v+QZhOObD+3ZIKIcm59ntTzyor4LqFTb72iJc3k23Q==",
"version": "1.3.9",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.9.tgz",
"integrity": "sha512-MXxeCMPyrcemSLGaTQEZx0dBUH0i+RPl8RN5GwMAzo53nTsd/Unc/t5ZxACeQoyPUM5/GkPLRUs2WliOImzkRA==",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/html/node_modules/@lezer/lr": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
"node_modules/@lezer/javascript": {
"version": "1.4.13",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.13.tgz",
"integrity": "sha512-5IBr8LIO3xJdJH1e9aj/ZNLE4LSbdsx25wFmGRAZsj2zSmwAYjx26JyU/BYOCpRQlu1jcv1z3vy4NB9+UkfRow==",
"dependencies": {
"@lezer/common": "^1.0.0"
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.1.3",
"@lezer/lr": "^1.3.0"
}
},
"node_modules/@lezer/json": {
@@ -266,7 +267,7 @@
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/json/node_modules/@lezer/lr": {
"node_modules/@lezer/lr": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
@@ -341,31 +342,10 @@
}
}
},
"node_modules/@rollup/pluginutils/node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
},
"node_modules/@rollup/pluginutils/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/@rollup/pluginutils/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz",
"integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz",
"integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==",
"cpu": [
"arm"
],
@@ -375,9 +355,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz",
"integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz",
"integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==",
"cpu": [
"arm64"
],
@@ -387,9 +367,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz",
"integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz",
"integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==",
"cpu": [
"arm64"
],
@@ -399,9 +379,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz",
"integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz",
"integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==",
"cpu": [
"x64"
],
@@ -411,9 +391,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz",
"integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz",
"integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==",
"cpu": [
"arm"
],
@@ -423,9 +403,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz",
"integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz",
"integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==",
"cpu": [
"arm64"
],
@@ -435,9 +415,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz",
"integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz",
"integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==",
"cpu": [
"arm64"
],
@@ -447,9 +427,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz",
"integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz",
"integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==",
"cpu": [
"riscv64"
],
@@ -458,10 +438,34 @@
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz",
"integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz",
"integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz",
"integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz",
"integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==",
"cpu": [
"arm64"
],
@@ -471,9 +475,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz",
"integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz",
"integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==",
"cpu": [
"ia32"
],
@@ -483,9 +487,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz",
"integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz",
"integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==",
"cpu": [
"x64"
],
@@ -494,11 +498,45 @@
"win32"
]
},
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
},
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"node_modules/builtin-modules": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/codemirror": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
@@ -513,6 +551,17 @@
"@codemirror/view": "^6.0.0"
}
},
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@@ -521,6 +570,11 @@
"node": ">=0.10.0"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -534,6 +588,25 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/is-builtin-module": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
@@ -548,15 +621,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-builtin-module/node_modules/builtin-modules": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
"engines": {
"node": ">=6"
"node_modules/is-core-module": {
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
"dependencies": {
"hasown": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-module": {
@@ -564,6 +637,31 @@
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"dependencies": {
"safe-buffer": "^5.1.0"
}
},
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -580,56 +678,10 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve/node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve/node_modules/hasown": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
"integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/resolve/node_modules/is-core-module": {
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
"dependencies": {
"hasown": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve/node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/resolve/node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/rollup": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz",
"integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz",
"integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==",
"dependencies": {
"@types/estree": "1.0.5"
},
@@ -641,70 +693,23 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.12.0",
"@rollup/rollup-android-arm64": "4.12.0",
"@rollup/rollup-darwin-arm64": "4.12.0",
"@rollup/rollup-darwin-x64": "4.12.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.12.0",
"@rollup/rollup-linux-arm64-gnu": "4.12.0",
"@rollup/rollup-linux-arm64-musl": "4.12.0",
"@rollup/rollup-linux-riscv64-gnu": "4.12.0",
"@rollup/rollup-linux-x64-gnu": "4.12.0",
"@rollup/rollup-linux-x64-musl": "4.12.0",
"@rollup/rollup-win32-arm64-msvc": "4.12.0",
"@rollup/rollup-win32-ia32-msvc": "4.12.0",
"@rollup/rollup-win32-x64-msvc": "4.12.0",
"@rollup/rollup-android-arm-eabi": "4.13.0",
"@rollup/rollup-android-arm64": "4.13.0",
"@rollup/rollup-darwin-arm64": "4.13.0",
"@rollup/rollup-darwin-x64": "4.13.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.13.0",
"@rollup/rollup-linux-arm64-gnu": "4.13.0",
"@rollup/rollup-linux-arm64-musl": "4.13.0",
"@rollup/rollup-linux-riscv64-gnu": "4.13.0",
"@rollup/rollup-linux-x64-gnu": "4.13.0",
"@rollup/rollup-linux-x64-musl": "4.13.0",
"@rollup/rollup-win32-arm64-msvc": "4.13.0",
"@rollup/rollup-win32-ia32-msvc": "4.13.0",
"@rollup/rollup-win32-x64-msvc": "4.13.0",
"fsevents": "~2.3.2"
}
},
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz",
"integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz",
"integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/rollup/node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
},
"node_modules/serialize-javascript": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
"dev": true,
"dependencies": {
"randombytes": "^2.1.0"
}
},
"node_modules/serialize-javascript/node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"dependencies": {
"safe-buffer": "^5.1.0"
}
},
"node_modules/serialize-javascript/node_modules/safe-buffer": {
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
@@ -724,16 +729,60 @@
}
]
},
"node_modules/serialize-javascript": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
"dev": true,
"dependencies": {
"randombytes": "^2.1.0"
}
},
"node_modules/smob": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz",
"integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==",
"dev": true
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"node_modules/style-mod": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/terser": {
"version": "5.27.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.27.2.tgz",
"integrity": "sha512-sHXmLSkImesJ4p5apTeT63DsV4Obe1s37qT8qvwHRmVxKTBH7Rv9Wr26VcAMmLbmk9UliiwK8z+657NyJHHy/w==",
"version": "5.29.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz",
"integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
@@ -748,106 +797,10 @@
"node": ">=10"
}
},
"node_modules/terser/node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/terser/node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/terser/node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/terser/node_modules/@jridgewell/source-map": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"node_modules/terser/node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"node_modules/terser/node_modules/@jridgewell/trace-mapping": {
"version": "0.3.22",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
"integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/terser/node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/terser/node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"node_modules/terser/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/terser/node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
}
}
}

View File

@@ -5,12 +5,12 @@
"dependencies": {
"@codemirror/lang-css": "^6.2.1",
"@codemirror/lang-html": "^6.4.8",
"@codemirror/lang-javascript": "^6.2.1",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/theme-one-dark": "^6.1.2",
"@rollup/plugin-node-resolve": "^15.2.3",
"codemirror": "^6.0.1",
"rollup": "^4.9.6"
"rollup": "^4.13.0"
},
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4"

93
deps/sqlite/shell.c vendored
View File

@@ -580,6 +580,9 @@ zSkipValidUtf8(const char *z, int nAccept, long ccm);
#ifndef HAVE_CONSOLE_IO_H
# include "console_io.h"
#endif
#if defined(_MSC_VER)
# pragma warning(disable : 4204)
#endif
#ifndef SQLITE_CIO_NO_TRANSLATE
# if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT
@@ -678,6 +681,10 @@ static short streamOfConsole(FILE *pf, /* out */ PerStreamTags *ppst){
# endif
}
# ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
# define ENABLE_VIRTUAL_TERMINAL_PROCESSING (0x4)
# endif
# if CIO_WIN_WC_XLATE
/* Define console modes for use with the Windows Console API. */
# define SHELL_CONI_MODE \
@@ -1228,6 +1235,10 @@ SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn){
}
#endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */
#if defined(_MSC_VER)
# pragma warning(default : 4204)
#endif
#undef SHELL_INVALID_FILE_PTR
/************************* End ../ext/consio/console_io.c ********************/
@@ -20619,6 +20630,7 @@ static void exec_prepared_stmt_columnar(
rc = sqlite3_step(pStmt);
if( rc!=SQLITE_ROW ) return;
nColumn = sqlite3_column_count(pStmt);
if( nColumn==0 ) goto columnar_end;
nAlloc = nColumn*4;
if( nAlloc<=0 ) nAlloc = 1;
azData = sqlite3_malloc64( nAlloc*sizeof(char*) );
@@ -20704,7 +20716,6 @@ static void exec_prepared_stmt_columnar(
if( n>p->actualWidth[j] ) p->actualWidth[j] = n;
}
if( seenInterrupt ) goto columnar_end;
if( nColumn==0 ) goto columnar_end;
switch( p->cMode ){
case MODE_Column: {
colSep = " ";
@@ -25553,16 +25564,15 @@ static int do_meta_command(char *zLine, ShellState *p){
#ifndef SQLITE_SHELL_FIDDLE
if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){
char *zTable = 0; /* Insert data into this table */
char *zSchema = 0; /* within this schema (may default to "main") */
char *zSchema = 0; /* Schema of zTable */
char *zFile = 0; /* Name of file to extra content from */
sqlite3_stmt *pStmt = NULL; /* A statement */
int nCol; /* Number of columns in the table */
int nByte; /* Number of bytes in an SQL string */
i64 nByte; /* Number of bytes in an SQL string */
int i, j; /* Loop counters */
int needCommit; /* True to COMMIT or ROLLBACK at end */
int nSep; /* Number of bytes in p->colSeparator[] */
char *zSql; /* An SQL statement */
char *zFullTabName; /* Table name with schema if applicable */
char *zSql = 0; /* An SQL statement */
ImportCtx sCtx; /* Reader context */
char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
int eVerbose = 0; /* Larger for more console output */
@@ -25696,24 +25706,14 @@ static int do_meta_command(char *zLine, ShellState *p){
while( (nSkip--)>0 ){
while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
}
if( zSchema!=0 ){
zFullTabName = sqlite3_mprintf("\"%w\".\"%w\"", zSchema, zTable);
}else{
zFullTabName = sqlite3_mprintf("\"%w\"", zTable);
}
zSql = sqlite3_mprintf("SELECT * FROM %s", zFullTabName);
if( zSql==0 || zFullTabName==0 ){
import_cleanup(&sCtx);
shell_out_of_memory();
}
nByte = strlen30(zSql);
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */
if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) ){
/* Table does not exist. Create it. */
sqlite3 *dbCols = 0;
char *zRenames = 0;
char *zColDefs;
zCreate = sqlite3_mprintf("CREATE TABLE %s", zFullTabName);
zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"",
zSchema ? zSchema : "main", zTable);
while( xRead(&sCtx) ){
zAutoColumn(sCtx.z, &dbCols, 0);
if( sCtx.cTerm!=sCtx.cColSep ) break;
@@ -25728,34 +25728,50 @@ static int do_meta_command(char *zLine, ShellState *p){
assert(dbCols==0);
if( zColDefs==0 ){
eputf("%s: empty file\n", sCtx.zFile);
import_fail:
sqlite3_free(zCreate);
sqlite3_free(zSql);
sqlite3_free(zFullTabName);
import_cleanup(&sCtx);
rc = 1;
goto meta_command_exit;
}
zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
if( zCreate==0 ){
import_cleanup(&sCtx);
shell_out_of_memory();
}
if( eVerbose>=1 ){
oputf("%s\n", zCreate);
}
rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
if( rc ){
eputf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
goto import_fail;
}
sqlite3_free(zCreate);
zCreate = 0;
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
if( rc ){
eputf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
import_cleanup(&sCtx);
rc = 1;
goto meta_command_exit;
}
}
zSql = sqlite3_mprintf("SELECT count(*) FROM pragma_table_info(%Q,%Q);",
zTable, zSchema);
if( zSql==0 ){
import_cleanup(&sCtx);
shell_out_of_memory();
}
nByte = strlen(zSql);
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
zSql = 0;
if( rc ){
if (pStmt) sqlite3_finalize(pStmt);
eputf("Error: %s\n", sqlite3_errmsg(p->db));
goto import_fail;
import_cleanup(&sCtx);
rc = 1;
goto meta_command_exit;
}
if( sqlite3_step(pStmt)==SQLITE_ROW ){
nCol = sqlite3_column_int(pStmt, 0);
}else{
nCol = 0;
}
sqlite3_free(zSql);
nCol = sqlite3_column_count(pStmt);
sqlite3_finalize(pStmt);
pStmt = 0;
if( nCol==0 ) return 0; /* no columns, no error */
@@ -25764,7 +25780,12 @@ static int do_meta_command(char *zLine, ShellState *p){
import_cleanup(&sCtx);
shell_out_of_memory();
}
sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName);
if( zSchema ){
sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?",
zSchema, zTable);
}else{
sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\" VALUES(?", zTable);
}
j = strlen30(zSql);
for(i=1; i<nCol; i++){
zSql[j++] = ',';
@@ -25776,13 +25797,15 @@ static int do_meta_command(char *zLine, ShellState *p){
oputf("Insert using: %s\n", zSql);
}
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
zSql = 0;
if( rc ){
eputf("Error: %s\n", sqlite3_errmsg(p->db));
if (pStmt) sqlite3_finalize(pStmt);
goto import_fail;
import_cleanup(&sCtx);
rc = 1;
goto meta_command_exit;
}
sqlite3_free(zSql);
sqlite3_free(zFullTabName);
needCommit = sqlite3_get_autocommit(p->db);
if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
do{

295
deps/sqlite/sqlite3.c vendored
View File

@@ -1,6 +1,6 @@
/******************************************************************************
** This file is an amalgamation of many separate C source files from SQLite
** version 3.45.1. By combining all the individual C code files into this
** version 3.45.2. By combining all the individual C code files into this
** single large file, the entire code can be compiled as a single translation
** unit. This allows many compilers to do optimizations that would not be
** possible if the files were compiled separately. Performance improvements
@@ -18,7 +18,7 @@
** separate file. This file contains only code for the core SQLite library.
**
** The content in this amalgamation comes from Fossil check-in
** e876e51a0ed5c5b3126f52e532044363a014.
** d8cd6d49b46a395b13955387d05e9e1a2a47.
*/
#define SQLITE_CORE 1
#define SQLITE_AMALGAMATION 1
@@ -459,9 +459,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.45.1"
#define SQLITE_VERSION_NUMBER 3045001
#define SQLITE_SOURCE_ID "2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a"
#define SQLITE_VERSION "3.45.2"
#define SQLITE_VERSION_NUMBER 3045002
#define SQLITE_SOURCE_ID "2024-03-12 11:06:23 d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -733,6 +733,8 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running.
** <li> The application must not modify the SQL statement text passed into
** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running.
** <li> The application must not dereference the arrays or string pointers
** passed as the 3rd and 4th callback parameters after it returns.
** </ul>
*/
SQLITE_API int sqlite3_exec(
@@ -15097,6 +15099,7 @@ SQLITE_PRIVATE u32 sqlite3TreeTrace;
** 0x00010000 Beginning of DELETE/INSERT/UPDATE processing
** 0x00020000 Transform DISTINCT into GROUP BY
** 0x00040000 SELECT tree dump after all code has been generated
** 0x00080000 NOT NULL strength reduction
*/
/*
@@ -19346,6 +19349,7 @@ struct NameContext {
#define NC_InAggFunc 0x020000 /* True if analyzing arguments to an agg func */
#define NC_FromDDL 0x040000 /* SQL text comes from sqlite_schema */
#define NC_NoSelect 0x080000 /* Do not descend into sub-selects */
#define NC_Where 0x100000 /* Processing WHERE clause of a SELECT */
#define NC_OrderAgg 0x8000000 /* Has an aggregate other than count/min/max */
/*
@@ -19369,6 +19373,7 @@ struct Upsert {
Expr *pUpsertWhere; /* WHERE clause for the ON CONFLICT UPDATE */
Upsert *pNextUpsert; /* Next ON CONFLICT clause in the list */
u8 isDoUpdate; /* True for DO UPDATE. False for DO NOTHING */
u8 isDup; /* True if 2nd or later with same pUpsertIdx */
/* Above this point is the parse tree for the ON CONFLICT clauses.
** The next group of fields stores intermediate data. */
void *pToFree; /* Free memory when deleting the Upsert object */
@@ -21444,7 +21449,7 @@ SQLITE_PRIVATE With *sqlite3WithPush(Parse*, With*, u8);
SQLITE_PRIVATE Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*,Upsert*);
SQLITE_PRIVATE void sqlite3UpsertDelete(sqlite3*,Upsert*);
SQLITE_PRIVATE Upsert *sqlite3UpsertDup(sqlite3*,Upsert*);
SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*);
SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*,Upsert*);
SQLITE_PRIVATE void sqlite3UpsertDoUpdate(Parse*,Upsert*,Table*,Index*,int);
SQLITE_PRIVATE Upsert *sqlite3UpsertOfIndex(Upsert*,Index*);
SQLITE_PRIVATE int sqlite3UpsertNextIsIPK(Upsert*);
@@ -31309,6 +31314,7 @@ SQLITE_API void sqlite3_str_vappendf(
if( xtype==etFLOAT ){
iRound = -precision;
}else if( xtype==etGENERIC ){
if( precision==0 ) precision = 1;
iRound = precision;
}else{
iRound = precision+1;
@@ -35199,6 +35205,9 @@ do_atof_calc:
u64 s2;
rr[0] = (double)s;
s2 = (u64)rr[0];
#if defined(_MSC_VER) && _MSC_VER<1700
if( s2==0x8000000000000000LL ){ s2 = 2*(u64)(0.5*rr[0]); }
#endif
rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s);
if( e>0 ){
while( e>=100 ){
@@ -35641,7 +35650,7 @@ SQLITE_PRIVATE void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRou
assert( p->n>0 );
assert( p->n<sizeof(p->zBuf) );
p->iDP = p->n + exp;
if( iRound<0 ){
if( iRound<=0 ){
iRound = p->iDP - iRound;
if( iRound==0 && p->zBuf[i+1]>='5' ){
iRound = 1;
@@ -53262,6 +53271,14 @@ SQLITE_API unsigned char *sqlite3_serialize(
pOut = 0;
}else{
sz = sqlite3_column_int64(pStmt, 0)*szPage;
if( sz==0 ){
sqlite3_reset(pStmt);
sqlite3_exec(db, "BEGIN IMMEDIATE; COMMIT;", 0, 0, 0);
rc = sqlite3_step(pStmt);
if( rc==SQLITE_ROW ){
sz = sqlite3_column_int64(pStmt, 0)*szPage;
}
}
if( piSize ) *piSize = sz;
if( mFlags & SQLITE_SERIALIZE_NOCOPY ){
pOut = 0;
@@ -77088,7 +77105,10 @@ static int fillInCell(
n = nHeader + nPayload;
testcase( n==3 );
testcase( n==4 );
if( n<4 ) n = 4;
if( n<4 ){
n = 4;
pPayload[nPayload] = 0;
}
*pnSize = n;
assert( nSrc<=nPayload );
testcase( nSrc<nPayload );
@@ -79534,7 +79554,10 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
if( flags & BTREE_PREFORMAT ){
rc = SQLITE_OK;
szNew = p->pBt->nPreformatSize;
if( szNew<4 ) szNew = 4;
if( szNew<4 ){
szNew = 4;
newCell[3] = 0;
}
if( ISAUTOVACUUM(p->pBt) && szNew>pPage->maxLocal ){
CellInfo info;
pPage->xParseCell(pPage, newCell, &info);
@@ -88379,6 +88402,23 @@ static void serialGet(
pMem->flags = IsNaN(x) ? MEM_Null : MEM_Real;
}
}
static int serialGet7(
const unsigned char *buf, /* Buffer to deserialize from */
Mem *pMem /* Memory cell to write value into */
){
u64 x = FOUR_BYTE_UINT(buf);
u32 y = FOUR_BYTE_UINT(buf+4);
x = (x<<32) + y;
assert( sizeof(x)==8 && sizeof(pMem->u.r)==8 );
swapMixedEndianFloat(x);
memcpy(&pMem->u.r, &x, sizeof(x));
if( IsNaN(x) ){
pMem->flags = MEM_Null;
return 1;
}
pMem->flags = MEM_Real;
return 0;
}
SQLITE_PRIVATE void sqlite3VdbeSerialGet(
const unsigned char *buf, /* Buffer to deserialize from */
u32 serial_type, /* Serial type to deserialize */
@@ -89058,7 +89098,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
}else if( serial_type==0 ){
rc = -1;
}else if( serial_type==7 ){
sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1);
serialGet7(&aKey1[d1], &mem1);
rc = -sqlite3IntFloatCompare(pRhs->u.i, mem1.u.r);
}else{
i64 lhs = vdbeRecordDecodeInt(serial_type, &aKey1[d1]);
@@ -89083,14 +89123,18 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
}else if( serial_type==0 ){
rc = -1;
}else{
sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1);
if( serial_type==7 ){
if( mem1.u.r<pRhs->u.r ){
if( serialGet7(&aKey1[d1], &mem1) ){
rc = -1; /* mem1 is a NaN */
}else if( mem1.u.r<pRhs->u.r ){
rc = -1;
}else if( mem1.u.r>pRhs->u.r ){
rc = +1;
}else{
assert( rc==0 );
}
}else{
sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1);
rc = sqlite3IntFloatCompare(mem1.u.i, pRhs->u.r);
}
}
@@ -89160,7 +89204,14 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
/* RHS is null */
else{
serial_type = aKey1[idx1];
rc = (serial_type!=0 && serial_type!=10);
if( serial_type==0
|| serial_type==10
|| (serial_type==7 && serialGet7(&aKey1[d1], &mem1)!=0)
){
assert( rc==0 );
}else{
rc = 1;
}
}
if( rc!=0 ){
@@ -94858,7 +94909,9 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
}
}
}else if( affinity==SQLITE_AFF_TEXT && ((flags1 | flags3) & MEM_Str)!=0 ){
if( (flags1 & MEM_Str)==0 && (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){
if( (flags1 & MEM_Str)!=0 ){
pIn1->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal);
}else if( (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){
testcase( pIn1->flags & MEM_Int );
testcase( pIn1->flags & MEM_Real );
testcase( pIn1->flags & MEM_IntReal );
@@ -94867,7 +94920,9 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask);
if( NEVER(pIn1==pIn3) ) flags3 = flags1 | MEM_Str;
}
if( (flags3 & MEM_Str)==0 && (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){
if( (flags3 & MEM_Str)!=0 ){
pIn3->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal);
}else if( (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){
testcase( pIn3->flags & MEM_Int );
testcase( pIn3->flags & MEM_Real );
testcase( pIn3->flags & MEM_IntReal );
@@ -106212,6 +106267,8 @@ static void resolveAlias(
assert( iCol>=0 && iCol<pEList->nExpr );
pOrig = pEList->a[iCol].pExpr;
assert( pOrig!=0 );
assert( !ExprHasProperty(pExpr, EP_Reduced|EP_TokenOnly) );
if( pExpr->pAggInfo ) return;
db = pParse->db;
pDup = sqlite3ExprDup(db, pOrig, 0);
if( db->mallocFailed ){
@@ -107097,6 +107154,19 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
** resolved. This prevents "column" from being counted as having been
** referenced, which might prevent a SELECT from being erroneously
** marked as correlated.
**
** 2024-03-28: Beware of aggregates. A bare column of aggregated table
** can still evaluate to NULL even though it is marked as NOT NULL.
** Example:
**
** CREATE TABLE t1(a INT NOT NULL);
** SELECT a, a IS NULL, a IS NOT NULL, count(*) FROM t1;
**
** The "a IS NULL" and "a IS NOT NULL" expressions cannot be optimized
** here because at the time this case is hit, we do not yet know whether
** or not t1 is being aggregated. We have to assume the worst and omit
** the optimization. The only time it is safe to apply this optimization
** is within the WHERE clause.
*/
case TK_NOTNULL:
case TK_ISNULL: {
@@ -107107,19 +107177,36 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
anRef[i] = p->nRef;
}
sqlite3WalkExpr(pWalker, pExpr->pLeft);
if( 0==sqlite3ExprCanBeNull(pExpr->pLeft) && !IN_RENAME_OBJECT ){
testcase( ExprHasProperty(pExpr, EP_OuterON) );
assert( !ExprHasProperty(pExpr, EP_IntValue) );
pExpr->u.iValue = (pExpr->op==TK_NOTNULL);
pExpr->flags |= EP_IntValue;
pExpr->op = TK_INTEGER;
for(i=0, p=pNC; p && i<ArraySize(anRef); p=p->pNext, i++){
p->nRef = anRef[i];
}
sqlite3ExprDelete(pParse->db, pExpr->pLeft);
pExpr->pLeft = 0;
if( IN_RENAME_OBJECT ) return WRC_Prune;
if( sqlite3ExprCanBeNull(pExpr->pLeft) ){
/* The expression can be NULL. So the optimization does not apply */
return WRC_Prune;
}
for(i=0, p=pNC; p; p=p->pNext, i++){
if( (p->ncFlags & NC_Where)==0 ){
return WRC_Prune; /* Not in a WHERE clause. Unsafe to optimize. */
}
}
testcase( ExprHasProperty(pExpr, EP_OuterON) );
assert( !ExprHasProperty(pExpr, EP_IntValue) );
#if TREETRACE_ENABLED
if( sqlite3TreeTrace & 0x80000 ){
sqlite3DebugPrintf(
"NOT NULL strength reduction converts the following to %d:\n",
pExpr->op==TK_NOTNULL
);
sqlite3ShowExpr(pExpr);
}
#endif /* TREETRACE_ENABLED */
pExpr->u.iValue = (pExpr->op==TK_NOTNULL);
pExpr->flags |= EP_IntValue;
pExpr->op = TK_INTEGER;
for(i=0, p=pNC; p && i<ArraySize(anRef); p=p->pNext, i++){
p->nRef = anRef[i];
}
sqlite3ExprDelete(pParse->db, pExpr->pLeft);
pExpr->pLeft = 0;
return WRC_Prune;
}
@@ -108019,7 +108106,9 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
}
if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort;
}
sNC.ncFlags |= NC_Where;
if( sqlite3ResolveExprNames(&sNC, p->pWhere) ) return WRC_Abort;
sNC.ncFlags &= ~NC_Where;
/* Resolve names in table-valued-function arguments */
for(i=0; i<p->pSrc->nSrc; i++){
@@ -128947,13 +129036,13 @@ SQLITE_PRIVATE void sqlite3QuoteValue(StrAccum *pStr, sqlite3_value *pValue){
double r1, r2;
const char *zVal;
r1 = sqlite3_value_double(pValue);
sqlite3_str_appendf(pStr, "%!.15g", r1);
sqlite3_str_appendf(pStr, "%!0.15g", r1);
zVal = sqlite3_str_value(pStr);
if( zVal ){
sqlite3AtoF(zVal, &r2, pStr->nChar, SQLITE_UTF8);
if( r1!=r2 ){
sqlite3_str_reset(pStr);
sqlite3_str_appendf(pStr, "%!.20e", r1);
sqlite3_str_appendf(pStr, "%!0.20e", r1);
}
}
break;
@@ -129255,7 +129344,7 @@ static void replaceFunc(
}
if( zPattern[0]==0 ){
assert( sqlite3_value_type(argv[1])!=SQLITE_NULL );
sqlite3_result_value(context, argv[0]);
sqlite3_result_text(context, (const char*)zStr, nStr, SQLITE_TRANSIENT);
return;
}
nPattern = sqlite3_value_bytes(argv[1]);
@@ -133175,7 +133264,7 @@ SQLITE_PRIVATE void sqlite3Insert(
pNx->iDataCur = iDataCur;
pNx->iIdxCur = iIdxCur;
if( pNx->pUpsertTarget ){
if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx) ){
if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx, pUpsert) ){
goto insert_cleanup;
}
}
@@ -139474,31 +139563,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
int mxCol; /* Maximum non-virtual column number */
if( pObjTab && pObjTab!=pTab ) continue;
if( !IsOrdinaryTable(pTab) ){
#ifndef SQLITE_OMIT_VIRTUALTABLE
sqlite3_vtab *pVTab;
int a1;
if( !IsVirtual(pTab) ) continue;
if( pTab->nCol<=0 ){
const char *zMod = pTab->u.vtab.azArg[0];
if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue;
}
sqlite3ViewGetColumnNames(pParse, pTab);
if( pTab->u.vtab.p==0 ) continue;
pVTab = pTab->u.vtab.p->pVtab;
if( NEVER(pVTab==0) ) continue;
if( NEVER(pVTab->pModule==0) ) continue;
if( pVTab->pModule->iVersion<4 ) continue;
if( pVTab->pModule->xIntegrity==0 ) continue;
sqlite3VdbeAddOp3(v, OP_VCheck, i, 3, isQuick);
pTab->nTabRef++;
sqlite3VdbeAppendP4(v, pTab, P4_TABLEREF);
a1 = sqlite3VdbeAddOp1(v, OP_IsNull, 3); VdbeCoverage(v);
integrityCheckResultRow(v);
sqlite3VdbeJumpHere(v, a1);
#endif
continue;
}
if( !IsOrdinaryTable(pTab) ) continue;
if( isQuick || HasRowid(pTab) ){
pPk = 0;
r2 = 0;
@@ -139633,6 +139698,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
** is REAL, we have to load the actual data using OP_Column
** to reliably determine if the value is a NULL. */
sqlite3VdbeAddOp3(v, OP_Column, p1, p3, 3);
sqlite3ColumnDefault(v, pTab, j, 3);
jmp3 = sqlite3VdbeAddOp2(v, OP_NotNull, 3, labelOk);
VdbeCoverage(v);
}
@@ -139823,6 +139889,38 @@ SQLITE_PRIVATE void sqlite3Pragma(
}
}
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
/* Second pass to invoke the xIntegrity method on all virtual
** tables.
*/
for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){
Table *pTab = sqliteHashData(x);
sqlite3_vtab *pVTab;
int a1;
if( pObjTab && pObjTab!=pTab ) continue;
if( IsOrdinaryTable(pTab) ) continue;
if( !IsVirtual(pTab) ) continue;
if( pTab->nCol<=0 ){
const char *zMod = pTab->u.vtab.azArg[0];
if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue;
}
sqlite3ViewGetColumnNames(pParse, pTab);
if( pTab->u.vtab.p==0 ) continue;
pVTab = pTab->u.vtab.p->pVtab;
if( NEVER(pVTab==0) ) continue;
if( NEVER(pVTab->pModule==0) ) continue;
if( pVTab->pModule->iVersion<4 ) continue;
if( pVTab->pModule->xIntegrity==0 ) continue;
sqlite3VdbeAddOp3(v, OP_VCheck, i, 3, isQuick);
pTab->nTabRef++;
sqlite3VdbeAppendP4(v, pTab, P4_TABLEREF);
a1 = sqlite3VdbeAddOp1(v, OP_IsNull, 3); VdbeCoverage(v);
integrityCheckResultRow(v);
sqlite3VdbeJumpHere(v, a1);
continue;
}
#endif
}
{
static const int iLn = VDBE_OFFSET_LINENO(2);
@@ -153460,7 +153558,8 @@ SQLITE_PRIVATE Upsert *sqlite3UpsertNew(
SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(
Parse *pParse, /* The parsing context */
SrcList *pTabList, /* Table into which we are inserting */
Upsert *pUpsert /* The ON CONFLICT clauses */
Upsert *pUpsert, /* The ON CONFLICT clauses */
Upsert *pAll /* Complete list of all ON CONFLICT clauses */
){
Table *pTab; /* That table into which we are inserting */
int rc; /* Result code */
@@ -153563,6 +153662,14 @@ SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(
continue;
}
pUpsert->pUpsertIdx = pIdx;
if( sqlite3UpsertOfIndex(pAll,pIdx)!=pUpsert ){
/* Really this should be an error. The isDup ON CONFLICT clause will
** never fire. But this problem was not discovered until three years
** after multi-CONFLICT upsert was added, and so we silently ignore
** the problem to prevent breaking applications that might actually
** have redundant ON CONFLICT clauses. */
pUpsert->isDup = 1;
}
break;
}
if( pUpsert->pUpsertIdx==0 ){
@@ -153589,9 +153696,13 @@ SQLITE_PRIVATE int sqlite3UpsertNextIsIPK(Upsert *pUpsert){
Upsert *pNext;
if( NEVER(pUpsert==0) ) return 0;
pNext = pUpsert->pNextUpsert;
if( pNext==0 ) return 1;
if( pNext->pUpsertTarget==0 ) return 1;
if( pNext->pUpsertIdx==0 ) return 1;
while( 1 /*exit-by-return*/ ){
if( pNext==0 ) return 1;
if( pNext->pUpsertTarget==0 ) return 1;
if( pNext->pUpsertIdx==0 ) return 1;
if( !pNext->isDup ) return 0;
pNext = pNext->pNextUpsert;
}
return 0;
}
@@ -204785,6 +204896,7 @@ json_parse_restart:
case '[': {
/* Parse array */
iThis = pParse->nBlob;
assert( i<=(u32)pParse->nJson );
jsonBlobAppendNode(pParse, JSONB_ARRAY, pParse->nJson - i, 0);
iStart = pParse->nBlob;
if( pParse->oom ) return -1;
@@ -205183,6 +205295,10 @@ static void jsonReturnStringAsBlob(JsonString *pStr){
JsonParse px;
memset(&px, 0, sizeof(px));
jsonStringTerminate(pStr);
if( pStr->eErr ){
sqlite3_result_error_nomem(pStr->pCtx);
return;
}
px.zJson = pStr->zBuf;
px.nJson = pStr->nUsed;
px.db = sqlite3_context_db_handle(pStr->pCtx);
@@ -206508,8 +206624,9 @@ rebuild_from_cache:
}
p->zJson = (char*)sqlite3_value_text(pArg);
p->nJson = sqlite3_value_bytes(pArg);
if( db->mallocFailed ) goto json_pfa_oom;
if( p->nJson==0 ) goto json_pfa_malformed;
if( NEVER(p->zJson==0) ) goto json_pfa_oom;
assert( p->zJson!=0 );
if( jsonConvertTextToBlob(p, (flgs & JSON_KEEPERROR) ? 0 : ctx) ){
if( flgs & JSON_KEEPERROR ){
p->nErr = 1;
@@ -206675,10 +206792,10 @@ static void jsonDebugPrintBlob(
if( sz==0 && x<=JSONB_FALSE ){
sqlite3_str_append(pOut, "\n", 1);
}else{
u32 i;
u32 j;
sqlite3_str_appendall(pOut, ": \"");
for(i=iStart+n; i<iStart+n+sz; i++){
u8 c = pParse->aBlob[i];
for(j=iStart+n; j<iStart+n+sz; j++){
u8 c = pParse->aBlob[j];
if( c<0x20 || c>=0x7f ) c = '.';
sqlite3_str_append(pOut, (char*)&c, 1);
}
@@ -208086,6 +208203,9 @@ static int jsonEachColumn(
case JEACH_VALUE: {
u32 i = jsonSkipLabel(p);
jsonReturnFromBlob(&p->sParse, i, ctx, 1);
if( (p->sParse.aBlob[i] & 0x0f)>=JSONB_ARRAY ){
sqlite3_result_subtype(ctx, JSON_SUBTYPE);
}
break;
}
case JEACH_TYPE: {
@@ -208132,9 +208252,9 @@ static int jsonEachColumn(
case JEACH_JSON: {
if( p->sParse.zJson==0 ){
sqlite3_result_blob(ctx, p->sParse.aBlob, p->sParse.nBlob,
SQLITE_STATIC);
SQLITE_TRANSIENT);
}else{
sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_STATIC);
sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_TRANSIENT);
}
break;
}
@@ -209160,11 +209280,9 @@ static RtreeNode *nodeNew(Rtree *pRtree, RtreeNode *pParent){
** Clear the Rtree.pNodeBlob object
*/
static void nodeBlobReset(Rtree *pRtree){
if( pRtree->pNodeBlob && pRtree->inWrTrans==0 && pRtree->nCursor==0 ){
sqlite3_blob *pBlob = pRtree->pNodeBlob;
pRtree->pNodeBlob = 0;
sqlite3_blob_close(pBlob);
}
sqlite3_blob *pBlob = pRtree->pNodeBlob;
pRtree->pNodeBlob = 0;
sqlite3_blob_close(pBlob);
}
/*
@@ -209208,7 +209326,6 @@ static int nodeAcquire(
&pRtree->pNodeBlob);
}
if( rc ){
nodeBlobReset(pRtree);
*ppNode = 0;
/* If unable to open an sqlite3_blob on the desired row, that can only
** be because the shadow tables hold erroneous data. */
@@ -209268,6 +209385,7 @@ static int nodeAcquire(
}
*ppNode = pNode;
}else{
nodeBlobReset(pRtree);
if( pNode ){
pRtree->nNodeRef--;
sqlite3_free(pNode);
@@ -209412,6 +209530,7 @@ static void nodeGetCoord(
int iCoord, /* Which coordinate to extract */
RtreeCoord *pCoord /* OUT: Space to write result to */
){
assert( iCell<NCELL(pNode) );
readCoord(&pNode->zData[12 + pRtree->nBytesPerCell*iCell + 4*iCoord], pCoord);
}
@@ -209601,7 +209720,9 @@ static int rtreeClose(sqlite3_vtab_cursor *cur){
sqlite3_finalize(pCsr->pReadAux);
sqlite3_free(pCsr);
pRtree->nCursor--;
nodeBlobReset(pRtree);
if( pRtree->nCursor==0 && pRtree->inWrTrans==0 ){
nodeBlobReset(pRtree);
}
return SQLITE_OK;
}
@@ -210186,7 +210307,11 @@ static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){
int rc = SQLITE_OK;
RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc);
if( rc==SQLITE_OK && ALWAYS(p) ){
*pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell);
if( p->iCell>=NCELL(pNode) ){
rc = SQLITE_ABORT;
}else{
*pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell);
}
}
return rc;
}
@@ -210204,6 +210329,7 @@ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
if( rc ) return rc;
if( NEVER(p==0) ) return SQLITE_OK;
if( p->iCell>=NCELL(pNode) ) return SQLITE_ABORT;
if( i==0 ){
sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell));
}else if( i<=pRtree->nDim2 ){
@@ -211685,8 +211811,7 @@ constraint:
*/
static int rtreeBeginTransaction(sqlite3_vtab *pVtab){
Rtree *pRtree = (Rtree *)pVtab;
assert( pRtree->inWrTrans==0 );
pRtree->inWrTrans++;
pRtree->inWrTrans = 1;
return SQLITE_OK;
}
@@ -211700,6 +211825,9 @@ static int rtreeEndTransaction(sqlite3_vtab *pVtab){
nodeBlobReset(pRtree);
return SQLITE_OK;
}
static int rtreeRollback(sqlite3_vtab *pVtab){
return rtreeEndTransaction(pVtab);
}
/*
** The xRename method for rtree module virtual tables.
@@ -211818,7 +211946,7 @@ static sqlite3_module rtreeModule = {
rtreeBeginTransaction, /* xBegin - begin transaction */
rtreeEndTransaction, /* xSync - sync transaction */
rtreeEndTransaction, /* xCommit - commit transaction */
rtreeEndTransaction, /* xRollback - rollback transaction */
rtreeRollback, /* xRollback - rollback transaction */
0, /* xFindFunction - function overloading */
rtreeRename, /* xRename - rename the table */
rtreeSavepoint, /* xSavepoint */
@@ -245377,23 +245505,26 @@ static void fts5IterSetOutputsTokendata(Fts5Iter *pIter){
static void fts5TokendataIterNext(Fts5Iter *pIter, int bFrom, i64 iFrom){
int ii;
Fts5TokenDataIter *pT = pIter->pTokenDataIter;
Fts5Index *pIndex = pIter->pIndex;
for(ii=0; ii<pT->nIter; ii++){
Fts5Iter *p = pT->apIter[ii];
if( p->base.bEof==0
&& (p->base.iRowid==pIter->base.iRowid || (bFrom && p->base.iRowid<iFrom))
){
fts5MultiIterNext(p->pIndex, p, bFrom, iFrom);
fts5MultiIterNext(pIndex, p, bFrom, iFrom);
while( bFrom && p->base.bEof==0
&& p->base.iRowid<iFrom
&& p->pIndex->rc==SQLITE_OK
&& pIndex->rc==SQLITE_OK
){
fts5MultiIterNext(p->pIndex, p, 0, 0);
fts5MultiIterNext(pIndex, p, 0, 0);
}
}
}
fts5IterSetOutputsTokendata(pIter);
if( pIndex->rc==SQLITE_OK ){
fts5IterSetOutputsTokendata(pIter);
}
}
/*
@@ -250547,7 +250678,7 @@ static void fts5SourceIdFunc(
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
sqlite3_result_text(pCtx, "fts5: 2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a", -1, SQLITE_TRANSIENT);
sqlite3_result_text(pCtx, "fts5: 2024-03-12 11:06:23 d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77", -1, SQLITE_TRANSIENT);
}
/*

View File

@@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.45.1"
#define SQLITE_VERSION_NUMBER 3045001
#define SQLITE_SOURCE_ID "2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a"
#define SQLITE_VERSION "3.45.2"
#define SQLITE_VERSION_NUMBER 3045002
#define SQLITE_SOURCE_ID "2024-03-12 11:06:23 d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -420,6 +420,8 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running.
** <li> The application must not modify the SQL statement text passed into
** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running.
** <li> The application must not dereference the arrays or string pointers
** passed as the 3rd and 4th callback parameters after it returns.
** </ul>
*/
SQLITE_API int sqlite3_exec(

View File

@@ -1,17 +0,0 @@
FROM alpine:3.7 AS base
WORKDIR /src
RUN apk --update add alpine-sdk cmake bash
COPY . ./
WORKDIR /src/build
# Debug
FROM base
RUN cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=On
RUN cmake --build .
RUN ctest -VV
# Release
FROM base
RUN cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=On
RUN cmake --build .
RUN ctest -VV

View File

@@ -1,7 +0,0 @@
version: 2
jobs:
build:
machine: true
steps:
- checkout
- run: docker build -f .circleci/Dockerfile.test .

View File

@@ -1 +0,0 @@
.gitignore

View File

@@ -1,18 +0,0 @@
root = true
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.coffee]
indent_style = space
[{package.json,*.yml}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false

View File

@@ -1,6 +0,0 @@
*.sw[a-p]
/bin/
/build/
/.tup/
/*.o
/*.a

View File

@@ -1,43 +0,0 @@
cmake_minimum_required (VERSION 3.8)
project (xopt)
add_library (xopt STATIC "${CMAKE_CURRENT_SOURCE_DIR}/xopt.c")
if (APPLE OR UNIX)
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -pedantic -std=c11 -D_XOPEN_SOURCE=600 -fdiagnostics-color=always -fvisibility=hidden")
set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g3 -O0")
set (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Ofast")
endif ()
function (_xopt_test name)
if (NOT TARGET "xopt-test-${name}")
add_executable ("xopt-test-${name}" "${CMAKE_CURRENT_SOURCE_DIR}/test/${name}.c")
target_link_libraries ("xopt-test-${name}" xopt)
endif ()
set (testname "xopt-${name}-test")
set (testnum 1)
while (TEST "${testname}-${testnum}")
math (EXPR testnum "${testnum}+1")
endwhile ()
add_test (
NAME "${testname}-${testnum}"
COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/test/test-case.sh" $<TARGET_FILE:xopt-test-${name}> "${CMAKE_CURRENT_SOURCE_DIR}/test/${name}-${testnum}.out" ${ARGN}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test")
endfunction ()
if (BUILD_TESTING)
enable_testing ()
_xopt_test (simple --some-int=10 --some-double=14.5 foo bar -- --some-other=20)
_xopt_test (macro --some-int=10 --some-double=14.5 foo bar -- --some-other=20)
_xopt_test (required --some-int=10 --some-double=14.5 --some-required=1337 foo bar -- --some-other=20)
_xopt_test (optional-longarg -i 10 -d 14.5 foo bar -- --some-other=20)
_xopt_test (autohelp --help -- --is-not-passed ignoreme)
_xopt_test (sloppyshorts -i10 -d 14.5 "-ssome string" -m -mm -mmm foo bar -- --is-not-passed ignoreme)
_xopt_test (nocondense-sloppy -i 10 -d 14.5 -s "some string" -m -mm -mmm foo bar -- --is-not-passed ignoreme)
_xopt_test (nocondense-sloppy -i 10 -d 14.5 "-ssome string" -m -mm -mmm foo bar -- --is-not-passed ignoreme)
_xopt_test (nocondense-sloppy -i 10 -d 14.5 "-ssome string" -m -m -m -m -m -m foo bar -- --is-not-passed ignoreme)
_xopt_test (nocondense -i 10 -d 14.5 "-ssome string" -m -mm -mmm foo bar -- --is-not-passed ignoreme)
endif ()

9
deps/xopt/README.md vendored
View File

@@ -1,9 +0,0 @@
# XOpt [![CircleCI](https://circleci.com/gh/Qix-/xopt.svg?style=svg)](https://circleci.com/gh/Qix-/xopt)
The sane answer to POpt.
XOpt is a command line argument parsing library written in ANSI C. XOpt
accepts arguments in GNU format and focuses on clean definition, taking stress
off the implementation.
# License
Originally by Josh Junon, released under [CC0](https://creativecommons.org/publicdomain/zero/1.0/). Go nuts.

2099
deps/xopt/snprintf.c vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
/*-test
/*.o

View File

@@ -1,12 +0,0 @@
args: «--help» «--» «--is-not-passed» «ignoreme»
usage: autohelp-test-case [opts...] [--] [extras...]
Tests the simple parser macro
-i, --some-int=n Some integer value. Can set to whatever number you like.
-f Some float value.
--some-double=n Some double value.
-?, --help Shows this help message
[end of arguments]

View File

@@ -1,100 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
bool help;
} SimpleConfig;
xoptOption options[] = {
{
"some-int",
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
0,
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
"some-double",
0,
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int exit_code = 1;
const char *err = NULL;
SimpleConfig config;
const char **extras = NULL;
int extraCount = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.help = 0;
XOPT_SIMPLE_PARSE(
"autohelp-test-case",
0,
&options[0], &config,
argc, argv,
&extraCount, &extras,
&err,
stderr,
"[opts...] [--] [extras...]",
"Tests the simple parser macro",
"[end of arguments]",
15);
if (!err) {
err = config.help
? "--help was passed but autohelp didn't fire"
: "--help was not passed - it is required for this test case";
}
fprintf(stderr, "Error: %s\n", err);
exit:
free(extras); /* DO NOT free individual strings */
return exit_code;
xopt_help:
exit_code = 0;
goto exit;
}

View File

@@ -1,11 +0,0 @@
args: «--some-int=10» «--some-double=14.5» «foo» «bar» «--» «--some-other=20»
someInt: 10
someFloat: 0.000000
someDouble: 14.500000
help: 0
extra count: 3
- foo
- bar
- --some-other=20

116
deps/xopt/test/macro.c vendored
View File

@@ -1,116 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
bool help;
} SimpleConfig;
xoptOption options[] = {
{
"some-int",
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
"some-float",
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
"some-double",
'd',
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int exit_code = 1;
const char *err = NULL;
SimpleConfig config;
const char **extras = NULL;
const char **extrasPtr = NULL;
int extraCount = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.help = 0;
XOPT_SIMPLE_PARSE(
argv[0],
0,
&options[0], &config,
argc, argv,
&extraCount, &extras,
&err,
stderr,
"[opts...] [--] [extras...]",
"Tests the simple parser macro",
"[end of arguments]",
15);
if (err) {
fprintf(stderr, "Error: %s\n", err);
goto exit;
}
/* print */
#define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \
config.field)
P(someInt, d);
P(someFloat, f);
P(someDouble, f);
P(help, d);
fprintf(stderr, "\nextra count: %d\n", extraCount);
extrasPtr = extras;
while (extraCount--) {
fprintf(stderr, "- %s\n", *extrasPtr++);
}
#undef P
exit_code = 0;
exit:
free(extras); /* DO NOT free individual strings */
return exit_code;
xopt_help:
exit_code = 2;
goto exit;
}

View File

@@ -1,3 +0,0 @@
args: «-i» «10» «-d» «14.5» «-ssome string» «-m» «-mm» «-mmm» «foo» «bar» «--» «--is-not-passed» «ignoreme»
Error: short option parameters must be separated, not condensed: -ssome string

View File

@@ -1,3 +0,0 @@
args: «-i» «10» «-d» «14.5» «-s» «some string» «-m» «-mm» «-mmm» «foo» «bar» «--» «--is-not-passed» «ignoreme»
Error: short options cannot be combined: -mm

View File

@@ -1,3 +0,0 @@
args: «-i» «10» «-d» «14.5» «-ssome string» «-m» «-mm» «-mmm» «foo» «bar» «--» «--is-not-passed» «ignoreme»
Error: short options cannot be combined: -mm

View File

@@ -1,14 +0,0 @@
args: «-i» «10» «-d» «14.5» «-ssome string» «-m» «-m» «-m» «-m» «-m» «-m» «foo» «bar» «--» «--is-not-passed» «ignoreme»
someInt: 10
someFloat: 0.000000
someDouble: 14.500000
someString: some string
multiCount: 6
help: 0
extra count: 4
- foo
- bar
- --is-not-passed
- ignoreme

View File

@@ -1,149 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
const char *someString;
int multiCount;
bool help;
} SimpleConfig;
static void on_verbose(const char *v, void *data, const struct xoptOption *option, bool longArg, const char **err) {
(void) v;
(void) longArg;
(void) err;
int *count = (int *)(((char *) data) + option->offset);
++*count;
}
xoptOption options[] = {
{
"some-int",
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
"some-float",
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
"some-double",
'd',
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"some-string",
's',
offsetof(SimpleConfig, someString),
0,
XOPT_TYPE_STRING,
"s",
"Some string value."
},
{
0,
'm',
offsetof(SimpleConfig, multiCount),
&on_verbose,
XOPT_TYPE_BOOL,
0,
"Specify multiple times to increase count"
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int exit_code = 1;
const char *err = NULL;
SimpleConfig config;
const char **extras = NULL;
const char **extrasPtr = NULL;
int extraCount = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.someFloat = 0.0f;
config.someString = 0;
config.multiCount = 0;
config.help = 0;
XOPT_SIMPLE_PARSE(
argv[0],
XOPT_CTX_SLOPPYSHORTS | XOPT_CTX_NOCONDENSE,
&options[0], &config,
argc, argv,
&extraCount, &extras,
&err,
stderr,
"[opts...] [--] [extras...]",
"Tests the simple parser macro",
"[end of arguments]",
15);
if (err) {
fprintf(stderr, "Error: %s\n", err);
goto exit;
}
/* print */
#define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \
config.field)
P(someInt, d);
P(someFloat, f);
P(someDouble, f);
P(someString, s);
P(multiCount, d);
P(help, d);
fprintf(stderr, "\nextra count: %d\n", extraCount);
extrasPtr = extras;
while (extraCount--) {
fprintf(stderr, "- %s\n", *extrasPtr++);
}
#undef P
exit_code = 0;
exit:
free(extras); /* DO NOT free individual strings */
return exit_code;
xopt_help:
exit_code = 2;
goto exit;
}

View File

@@ -1,149 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
const char *someString;
int multiCount;
bool help;
} SimpleConfig;
static void on_verbose(const char *v, void *data, const struct xoptOption *option, bool longArg, const char **err) {
(void) v;
(void) longArg;
(void) err;
int *count = (int *)(((char *) data) + option->offset);
++*count;
}
xoptOption options[] = {
{
"some-int",
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
"some-float",
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
"some-double",
'd',
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"some-string",
's',
offsetof(SimpleConfig, someString),
0,
XOPT_TYPE_STRING,
"s",
"Some string value."
},
{
0,
'm',
offsetof(SimpleConfig, multiCount),
&on_verbose,
XOPT_TYPE_BOOL,
0,
"Specify multiple times to increase count"
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int exit_code = 1;
const char *err = NULL;
SimpleConfig config;
const char **extras = NULL;
const char **extrasPtr = NULL;
int extraCount = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.someFloat = 0.0f;
config.someString = 0;
config.multiCount = 0;
config.help = 0;
XOPT_SIMPLE_PARSE(
argv[0],
XOPT_CTX_NOCONDENSE,
&options[0], &config,
argc, argv,
&extraCount, &extras,
&err,
stderr,
"[opts...] [--] [extras...]",
"Tests the simple parser macro",
"[end of arguments]",
15);
if (err) {
fprintf(stderr, "Error: %s\n", err);
goto exit;
}
/* print */
#define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \
config.field)
P(someInt, d);
P(someFloat, f);
P(someDouble, f);
P(someString, s);
P(multiCount, d);
P(help, d);
fprintf(stderr, "\nextra count: %d\n", extraCount);
extrasPtr = extras;
while (extraCount--) {
fprintf(stderr, "- %s\n", *extrasPtr++);
}
#undef P
exit_code = 0;
exit:
free(extras); /* DO NOT free individual strings */
return exit_code;
xopt_help:
exit_code = 2;
goto exit;
}

View File

@@ -1,11 +0,0 @@
args: «-i» «10» «-d» «14.5» «foo» «bar» «--» «--some-other=20»
someInt: 10
someFloat: 0.000000
someDouble: 14.500000
help: 0
extra count: 3
- foo
- bar
- --some-other=20

View File

@@ -1,116 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
bool help;
} SimpleConfig;
xoptOption options[] = {
{
0,
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
0,
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
0,
'd',
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int exit_code = 1;
const char *err = NULL;
SimpleConfig config;
const char **extras = NULL;
const char **extrasPtr = NULL;
int extraCount = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.help = 0;
XOPT_SIMPLE_PARSE(
argv[0],
0,
&options[0], &config,
argc, argv,
&extraCount, &extras,
&err,
stderr,
"macro-test [opts...] [--] [extras...]",
"Tests the simple parser macro",
"[end of arguments]",
15);
if (err) {
fprintf(stderr, "Error: %s\n", err);
goto exit;
}
/* print */
#define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \
config.field)
P(someInt, d);
P(someFloat, f);
P(someDouble, f);
P(help, d);
fprintf(stderr, "\nextra count: %d\n", extraCount);
extrasPtr = extras;
while (extraCount--) {
fprintf(stderr, "- %s\n", *extrasPtr++);
}
#undef P
exit_code = 0;
exit:
free(extras); /* DO NOT free individual strings */
return exit_code;
xopt_help:
exit_code = 2;
goto exit;
}

View File

@@ -1,12 +0,0 @@
args: «--some-int=10» «--some-double=14.5» «--some-required=1337» «foo» «bar» «--» «--some-other=20»
someInt: 10
someFloat: 0.000000
someDouble: 14.500000
someRequired: 1337
help: 0
extra count: 3
- foo
- bar
- --some-other=20

View File

@@ -1,128 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
int someRequired;
bool help;
} SimpleConfig;
xoptOption options[] = {
{
"some-int",
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
"some-float",
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
"some-double",
'd',
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"some-required",
'r',
offsetof(SimpleConfig, someRequired),
0,
XOPT_TYPE_INT | XOPT_REQUIRED,
"n",
"Some value"
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int exit_code = 1;
const char *err = NULL;
SimpleConfig config;
const char **extras = NULL;
const char **extrasPtr = NULL;
int extraCount = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.someRequired = 0;
config.help = 0;
XOPT_SIMPLE_PARSE(
argv[0],
0,
&options[0], &config,
argc, argv,
&extraCount, &extras,
&err,
stderr,
"[opts...] [--] [extras...]",
"Tests the simple parser macro",
"[end of arguments]",
15);
if (err) {
fprintf(stderr, "Error: %s\n", err);
goto exit;
}
/* print */
#define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \
config.field)
P(someInt, d);
P(someFloat, f);
P(someDouble, f);
P(someRequired, d);
P(help, d);
fprintf(stderr, "\nextra count: %d\n", extraCount);
extrasPtr = extras;
while (extraCount--) {
fprintf(stderr, "- %s\n", *extrasPtr++);
}
#undef P
exit_code = 0;
exit:
free(extras); /* DO NOT free individual strings */
return exit_code;
xopt_help:
exit_code = 2;
goto exit;
}

View File

@@ -1,11 +0,0 @@
args: «--some-int=10» «--some-double=14.5» «foo» «bar» «--» «--some-other=20»
someInt: 10
someFloat: 0.000000
someDouble: 14.500000
help: 0
extra count: 3
- foo
- bar
- --some-other=20

View File

@@ -1,128 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
bool help;
} SimpleConfig;
xoptOption options[] = {
{
"some-int",
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
"some-float",
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
"some-double",
'd',
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int result;
const char *err;
xoptContext *ctx;
SimpleConfig config;
const char **extras = 0;
const char **extrasPtr = 0;
int extraCount;
result = 0;
err = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.help = 0;
/* create context */
ctx = xopt_context("xopt-test", options,
XOPT_CTX_POSIXMEHARDER | XOPT_CTX_STRICT, &err);
if (err) {
fprintf(stderr, "Error: %s\n", err);
result = 1;
goto exit;
}
/* parse */
extraCount = xopt_parse(ctx, argc, argv, &config, &extras, &err);
if (err) {
fprintf(stderr, "Error: %s\n", err);
result = 2;
goto exit;
}
/* help? */
if (config.help) {
xoptAutohelpOptions opts;
opts.usage = "[options] [extras...]";
opts.prefix = "A simple demonstration of the XOpt options parser library.";
opts.suffix = "End argument list.";
opts.spacer = 10;
xopt_autohelp(ctx, stderr, &opts, &err);
goto exit;
}
/* print */
#define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \
config.field)
P(someInt, d);
P(someFloat, f);
P(someDouble, f);
P(help, d);
fprintf(stderr, "\nextra count: %d\n", extraCount);
extrasPtr = extras;
while (extraCount--) {
fprintf(stderr, "- %s\n", *extrasPtr++);
}
#undef P
exit:
if (extras) free(extras); /* DO NOT free individual strings */
if (ctx) free(ctx); /* they point to argv strings */
return result;
}

View File

@@ -1,14 +0,0 @@
args: «-i10» «-d» «14.5» «-ssome string» «-m» «-mm» «-mmm» «foo» «bar» «--» «--is-not-passed» «ignoreme»
someInt: 10
someFloat: 0.000000
someDouble: 14.500000
someString: some string
multiCount: 6
help: 0
extra count: 4
- foo
- bar
- --is-not-passed
- ignoreme

View File

@@ -1,149 +0,0 @@
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "../xopt.h"
typedef struct {
int someInt;
float someFloat;
double someDouble;
const char *someString;
int multiCount;
bool help;
} SimpleConfig;
static void on_verbose(const char *v, void *data, const struct xoptOption *option, bool longArg, const char **err) {
(void) v;
(void) longArg;
(void) err;
int *count = (int *)(((char *) data) + option->offset);
++*count;
}
xoptOption options[] = {
{
"some-int",
'i',
offsetof(SimpleConfig, someInt),
0,
XOPT_TYPE_INT,
"n",
"Some integer value. Can set to whatever number you like."
},
{
"some-float",
'f',
offsetof(SimpleConfig, someFloat),
0,
XOPT_TYPE_FLOAT,
"n",
"Some float value."
},
{
"some-double",
'd',
offsetof(SimpleConfig, someDouble),
0,
XOPT_TYPE_DOUBLE,
"n",
"Some double value."
},
{
"some-string",
's',
offsetof(SimpleConfig, someString),
0,
XOPT_TYPE_STRING,
"s",
"Some string value."
},
{
0,
'm',
offsetof(SimpleConfig, multiCount),
&on_verbose,
XOPT_TYPE_BOOL,
0,
"Specify multiple times to increase count"
},
{
"help",
'?',
offsetof(SimpleConfig, help),
0,
XOPT_TYPE_BOOL,
0,
"Shows this help message"
},
XOPT_NULLOPTION
};
int main(int argc, const char **argv) {
int exit_code = 1;
const char *err = NULL;
SimpleConfig config;
const char **extras = NULL;
const char **extrasPtr = NULL;
int extraCount = 0;
/* show arguments */
fputs("args:", stderr);
for (int i = 1; i < argc; i++) {
fprintf(stderr, " «%s»", argv[i]);
}
fputs("\n\n", stderr);
/* setup defaults */
config.someInt = 0;
config.someDouble = 0.0;
config.someFloat = 0.0f;
config.someString = 0;
config.multiCount = 0;
config.help = 0;
XOPT_SIMPLE_PARSE(
argv[0],
XOPT_CTX_SLOPPYSHORTS,
&options[0], &config,
argc, argv,
&extraCount, &extras,
&err,
stderr,
"[opts...] [--] [extras...]",
"Tests the simple parser macro",
"[end of arguments]",
15);
if (err) {
fprintf(stderr, "Error: %s\n", err);
goto exit;
}
/* print */
#define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \
config.field)
P(someInt, d);
P(someFloat, f);
P(someDouble, f);
P(someString, s);
P(multiCount, d);
P(help, d);
fprintf(stderr, "\nextra count: %d\n", extraCount);
extrasPtr = extras;
while (extraCount--) {
fprintf(stderr, "- %s\n", *extrasPtr++);
}
#undef P
exit_code = 0;
exit:
free(extras); /* DO NOT free individual strings */
return exit_code;
xopt_help:
exit_code = 2;
goto exit;
}

View File

@@ -1,46 +0,0 @@
#!/bin/bash
#
# To be used with CMake builds located in <xopt>/build/
#
set -uo pipefail
exec >&2
casebin="$1"
caseout="$2"
shift 2
function die {
echo -e "error: $*"
exit 1
}
cd "$(dirname "$0")"
if [ -z "$casebin" ]; then
die 'no test case executable specified'
fi
if [ -z "$caseout" ]; then
die 'no test case output (some-case.out) specified'
fi
if [ ! -x "$casebin" ]; then
die "test case does not exist or is not executable: $casebin"
fi
if [ ! -f "$caseout" ]; then
die "test case expected output file does not exist: $caseout"
fi
output="$("$casebin" "$@" 2>&1)"
r=$?
if [ $r -eq 139 ]; then
die "xopt test case failed with SEGFAULT ($r)"
fi
diff="$(diff -U0 -d -t "$caseout" - <<< "$output" 2>&1)"
if [ ! -z "$diff" ]; then
die "xopt test case didn't match expected output: '$caseout'\n$diff"
fi

584
deps/xopt/xopt.c vendored
View File

@@ -1,584 +0,0 @@
/**
* XOpt - command line parsing library
*
* Copyright (c) 2015-2019 Josh Junon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef XOPT_NOSTANDARD
# define HAVE_STDARG_H 1
# define HAVE_STDLIB_H 1
# define HAVE_ASPRINTF_H 1
# define vasprintf rpl_vasprintf
# ifndef _GNU_SOURCE
# define _GNU_SOURCE
# endif
#endif
#include <assert.h>
#include <setjmp.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "./xopt.h"
#include "./snprintf.c"
#define EXTRAS_INIT 10
#define ERRBUF_SIZE 1024 * 4
static char errbuf[ERRBUF_SIZE];
struct xoptContext {
const xoptOption *options;
long flags;
const char *name;
bool doubledash;
size_t options_count;
bool *required;
jmp_buf *jmp;
};
static void _xopt_set_err(xoptContext *ctx, const char **err, const char *const fmt, ...);
static int _xopt_parse_arg(xoptContext *ctx, int argc, const char **argv,
int *argi, void *data, const char **err);
static void _xopt_assert_increment(xoptContext *ctx, const char ***extras, int extrasCount,
size_t *extrasCapac, const char **err);
static int _xopt_get_size(const char *arg);
static int _xopt_get_arg(const xoptContext *ctx, const char *arg, size_t len,
int size, const xoptOption **option, size_t *option_index);
static void _xopt_set(xoptContext *ctx, void *data, const xoptOption *option, const char *val,
bool longArg, const char **err);
static void _xopt_default_callback(const char *value, void *data,
const xoptOption *option, bool longArg, const char **err);
xoptContext* xopt_context(const char *name, const xoptOption *options, long flags,
const char **err) {
xoptContext* ctx;
*err = 0;
/* malloc context and check */
ctx = malloc(sizeof(xoptContext));
if (!ctx) {
ctx = 0;
_xopt_set_err(NULL, err, "could not allocate context");
} else {
const xoptOption *cur;
ctx->options = options;
ctx->flags = flags;
ctx->name = name;
ctx->doubledash = false;
ctx->required = NULL;
ctx->jmp = NULL;
ctx->options_count = 0;
cur = options;
for (; cur->longArg || cur->shortArg; cur++) ++ctx->options_count;
}
return ctx;
}
static int _xopt_parse_impl(xoptContext *ctx, int argc, const char **argv, void *data,
const char ***inextras, const char **err, int *extrasCount, size_t *extrasCapac,
const char ***extras, int *argi) {
int parseResult;
size_t i;
*err = 0;
*argi = 0;
*extrasCount = 0;
*extrasCapac = EXTRAS_INIT;
*extras = malloc(sizeof(**extras) * EXTRAS_INIT);
jmp_buf jmp;
ctx->jmp = &jmp;
if (setjmp(jmp)) {
goto end;
}
/* check if extras malloc'd okay */
if (!*extras) {
_xopt_set_err(ctx, err, "could not allocate extras array");
}
/* increment argument counter if we aren't
instructed to check argv[0] */
if (!(ctx->flags & XOPT_CTX_KEEPFIRST)) {
++(*argi);
}
/* set up required parameters list */
ctx->required = malloc(sizeof(*ctx->required) * ctx->options_count);
for (i = 0; i < ctx->options_count; i++) {
ctx->required[i] = (ctx->options[i].options & XOPT_REQUIRED) > 0;
}
/* iterate over passed command line arguments */
for (; *argi < argc; (*argi)++) {
/* parse, breaking if there was a failure
parseResult is 0 if option, 1 if extra, or 2 if double-dash was encountered */
parseResult = _xopt_parse_arg(ctx, argc, argv, argi, data, err);
/* is the argument an extra? */
switch (parseResult) {
case 0: /* option */
/* make sure we're super-posix'd if specified to be
(check that no extras have been specified when an option is parsed,
enforcing options to be specific before [extra] arguments */
if ((ctx->flags & XOPT_CTX_POSIXMEHARDER) && *extrasCount) {
_xopt_set_err(ctx, err, "options cannot be specified after arguments: %s", argv[*argi]);
goto end;
}
break;
case 1: /* extra */
/* make sure we have enough room, or realloc if we don't -
check that it succeeded */
_xopt_assert_increment(ctx, extras, *extrasCount, extrasCapac, err);
/* add extra to list */
(*extras)[(*extrasCount)++] = argv[*argi];
break;
case 2: /* "--" was encountered */
/* nothing to do here - "--" was already handled for us */
break;
}
}
end:
if (!*err) {
for (i = 0; i < ctx->options_count; i++) {
if (ctx->required[i]) {
const xoptOption *opt = &ctx->options[i];
if (opt->longArg) {
_xopt_set_err(ctx, err, "missing required option: --%s", opt->longArg);
} else {
_xopt_set_err(ctx, err, "missing required option: -%c", opt->shortArg);
}
break;
}
}
}
free(ctx->required);
if (!*err) {
/* append null terminator to extras */
_xopt_assert_increment(ctx, extras, *extrasCount, extrasCapac, err);
if (!*err) {
(*extras)[*extrasCount] = 0;
}
}
if (*err) {
free(*extras);
*inextras = 0;
return 0;
}
*inextras = *extras;
return *extrasCount;
}
int xopt_parse(xoptContext *ctx, int argc, const char **argv, void *data,
const char ***inextras, const char **err) {
/* avoid longjmp clobbering */
int extrasCount;
size_t extrasCapac;
const char **extras;
int argi;
return _xopt_parse_impl(ctx, argc, argv, data, inextras, err, &extrasCount, &extrasCapac, &extras, &argi);
}
void xopt_autohelp(xoptContext *ctx, FILE *stream, const xoptAutohelpOptions *options,
const char **err) {
const xoptOption *o;
size_t i, width = 0, twidth;
const char *nl = "";
size_t spacer = options ? options->spacer : 2;
*err = 0;
/* make sure that if we ever write a call to _set_err() in the future here,
that we won't accidentally cause segfaults - we have an assertion in place
for ctx->jmp != NULL, so we make sure we'd trigger that assertion */
ctx->jmp = NULL;
if (options && options->usage) {
fprintf(stream, "%susage: %s %s\n", nl, ctx->name, options->usage);
nl = "\n";
}
if (options && options->prefix) {
fprintf(stream, "%s%s\n\n", nl, options->prefix);
nl = "\n";
}
/* find max width */
for (i = 0; ctx->options[i].longArg || ctx->options[i].shortArg; i++) {
o = &ctx->options[i];
twidth = 0;
if (o->longArg) {
twidth += 2 + strlen(o->longArg);
if (o->argDescrip) {
twidth += 1 + strlen(o->argDescrip);
}
}
if (ctx->options[i].shortArg) {
twidth += 2;
}
if (ctx->options[i].shortArg && ctx->options[i].longArg) {
twidth += 2; /* `, ` */
}
width = width > twidth ? width : twidth;
}
/* print */
for (i = 0; ctx->options[i].longArg || ctx->options[i].shortArg; i++) {
o = &ctx->options[i];
twidth = 0;
if (o->shortArg) {
fprintf(stream, "-%c", o->shortArg);
twidth += 2;
}
if (o->shortArg && o->longArg) {
fprintf(stream, ", ");
twidth += 2;
}
if (o->longArg) {
fprintf(stream, "--%s", o->longArg);
twidth += 2 + strlen(o->longArg);
if (o->argDescrip) {
fprintf(stream, "=%s", o->argDescrip);
twidth += 1 + strlen(o->argDescrip);
}
}
if (o->descrip) {
for (; twidth < (width + spacer); twidth++) {
fprintf(stream, " ");
}
if (o->options & XOPT_REQUIRED) {
fprintf(stream, "(Required) %s\n", o->descrip);
} else {
fprintf(stream, "%s\n", o->descrip);
}
}
}
if (options && options->suffix) {
fprintf(stream, "%s%s\n", nl, options->suffix);
}
}
static void _xopt_set_err(xoptContext *ctx, const char **err, const char *const fmt, ...) {
va_list list;
va_start(list, fmt);
rpl_vsnprintf(&errbuf[0], ERRBUF_SIZE, fmt, list);
va_end(list);
*err = &errbuf[0];
if (ctx != NULL) {
assert(ctx->jmp != NULL);
longjmp(*ctx->jmp, 1);
}
}
static int _xopt_parse_arg(xoptContext *ctx, int argc, const char **argv,
int *argi, void *data, const char **err) {
int size;
size_t length;
bool isExtra = false;
const xoptOption *option = NULL;
size_t option_index = 0;
const char* arg = argv[*argi];
/* are we in doubledash mode? */
if (ctx->doubledash) {
return 1;
}
/* get argument 'size' (long/short/extra) */
size = _xopt_get_size(arg);
/* adjust to parse from beginning of actual content */
arg += size;
length = strlen(arg);
if (size == 1 && length == 0) {
/* it's just a singular dash - treat it as an extra arg */
return 1;
}
if (size == 2 && length == 0) {
/* double-dash - everything after this is an extra */
ctx->doubledash = 1;
return 2;
}
switch (size) {
int argRequirement;
char *valStart;
case 1: /* short */
/* parse all */
while (length--) {
/* get argument or error if not found and strict mode enabled. */
argRequirement = _xopt_get_arg(ctx, arg++, 1, size, &option, &option_index);
if (!option) {
if (ctx->flags & XOPT_CTX_STRICT) {
_xopt_set_err(ctx, err, "invalid option: -%c", arg[-1]);
}
break;
}
if (argRequirement > 0 && length > 0 && !(ctx->flags & XOPT_CTX_SLOPPYSHORTS)) {
_xopt_set_err(ctx, err, "short option parameters must be separated, not condensed: %s", argv[*argi]);
}
switch (argRequirement) {
case 0: /* flag; doesn't take an argument */
if (length > 0 && (ctx->flags & XOPT_CTX_NOCONDENSE)) {
_xopt_set_err(ctx, err, "short options cannot be combined: %s", argv[*argi]);
}
_xopt_set(ctx, data, option, 0, false, err);
break;
case 1: /* argument is optional */
/* is there another argument, and is it a non-option? */
if (*argi + 1 < argc && _xopt_get_size(argv[*argi + 1]) == 0) {
_xopt_set(ctx, data, option, argv[++*argi], false, err);
} else {
_xopt_set(ctx, data, option, 0, false, err);
}
break;
case 2: /* requires an argument */
/* is it the last in a set of condensed options? */
if (length == 0) {
/* is there another argument? */
if (*argi + 1 < argc) {
/* is the next argument actually an option?
this indicates no value was passed */
if (_xopt_get_size(argv[*argi + 1])) {
_xopt_set_err(ctx, err, "missing option value: -%c",
option->shortArg);
} else {
_xopt_set(ctx, data, option, argv[++*argi], false, err);
}
} else {
_xopt_set_err(ctx, err, "missing option value: -%c",
option->shortArg);
}
} else {
_xopt_set(ctx, data, option, arg, false, err);
length = 0;
}
break;
}
}
break;
case 2: /* long */
/* find first equals sign */
valStart = strchr(arg, '=');
/* is there a value? */
if (valStart) {
/* we also increase valStart here in order to lop off
the equals sign */
length = valStart++ - arg;
/* but not really, if it's null */
if (!*valStart) {
valStart = 0;
}
}
/* get the option */
argRequirement = _xopt_get_arg(ctx, arg, length, size, &option, &option_index);
if (!option) {
_xopt_set_err(ctx, err, "invalid option: --%.*s", length, arg);
} else {
switch (argRequirement) {
case 0: /* flag; doesn't take an argument */
if (valStart) {
_xopt_set_err(ctx, err, "option doesn't take a value: --%s", arg);
}
_xopt_set(ctx, data, option, valStart, true, err);
break;
case 2: /* requires an argument */
if (!valStart) {
_xopt_set_err(ctx, err, "missing option value: --%s", arg);
}
break;
}
_xopt_set(ctx, data, option, valStart, true, err);
}
break;
case 0: /* extra */
isExtra = true;
break;
}
if (option) {
/* indicate that we've seen this option and thus is no longer required */
ctx->required[option_index] = false;
}
return isExtra ? 1 : 0;
}
static void _xopt_assert_increment(xoptContext *ctx, const char ***extras, int extrasCount,
size_t *extrasCapac, const char **err) {
/* have we hit the list size limit? */
if ((size_t) extrasCount == *extrasCapac) {
/* increase capcity, realloc, and check for success */
*extrasCapac += EXTRAS_INIT;
*extras = realloc(*extras, sizeof(**extras) * *extrasCapac);
if (!*extras) {
_xopt_set_err(ctx, err, "could not realloc arguments array");
}
}
}
static int _xopt_get_size(const char *arg) {
int size;
for (size = 0; size < 2; size++) {
if (arg[size] != '-') {
break;
}
}
return size;
}
static int _xopt_get_arg(const xoptContext *ctx, const char *arg, size_t len,
int size, const xoptOption **option, size_t *option_index) {
size_t i;
*option = 0;
/* find the argument */
for (i = 0; i < ctx->options_count; i++) {
const xoptOption *opt = &ctx->options[i];
if ((size == 1 && opt->shortArg == arg[0])
|| (opt->longArg && strlen(opt->longArg) == len && !strncmp(opt->longArg, arg, len))) {
*option_index = i;
*option = opt;
break;
}
}
/* determine the optionality of a value */
if (!*option || (*option)->options & XOPT_TYPE_BOOL) {
return 0;
} else if ((*option)->options & XOPT_PARAM_OPTIONAL) {
return 1;
} else {
return 2;
}
}
static void _xopt_set(xoptContext *ctx, void *data, const xoptOption *option, const char *val,
bool longArg, const char **err) {
/* determine callback */
xoptCallback callback = option->callback ? option->callback : &_xopt_default_callback;
/* dispatch callback */
callback(val, data, option, longArg, err);
/* we check err here instead of relying upon longjmp()
since we can't call _set_err() with a context */
if (*err) {
assert(ctx->jmp != NULL);
longjmp(*ctx->jmp, 1);
}
}
static void _xopt_default_callback(const char *value, void *data,
const xoptOption *option, bool longArg, const char **err) {
void *target;
char *parsePtr = 0;
/* is a value specified? */
if ((!value || !strlen(value)) && !(option->options & XOPT_TYPE_BOOL)) {
/* we reach this point when they specified an optional, non-boolean
option but didn't specify a custom handler (therefore, it's not
optional).
to fix, just remove the optional flag or specify a callback to handle
it yourself.
*/
return;
}
/* get location */
target = ((char*) data) + option->offset;
/* switch on the type */
switch (option->options & 0x3F) {
case XOPT_TYPE_BOOL:
/* booleans are special in that they won't have an argument passed
into this callback */
*((_Bool*) target) = true;
break;
case XOPT_TYPE_STRING:
/* lifetime here works out fine; argv can usually be assumed static-like
in nature */
*((const char**) target) = value;
break;
case XOPT_TYPE_INT:
*((int*) target) = (int) strtol(value, &parsePtr, 0);
break;
case XOPT_TYPE_LONG:
*((long*) target) = strtol(value, &parsePtr, 0);
break;
case XOPT_TYPE_FLOAT:
*((float*) target) = (float) strtod(value, &parsePtr);
break;
case XOPT_TYPE_DOUBLE:
*((double*) target) = strtod(value, &parsePtr);
break;
default: /* something wonky, or the implementation specifies two types */
fprintf(stderr, "warning: XOpt argument type invalid: %ld\n",
option->options & 0x2F);
break;
}
/* check that our parsing functions worked */
if (parsePtr && *parsePtr) {
if (longArg) {
_xopt_set_err(NULL, err, "value isn't a valid number: --%s=%s",
(void*) option->longArg, value);
} else {
_xopt_set_err(NULL, err, "value isn't a valid number: -%c %s",
option->shortArg, value);
}
}
}

225
deps/xopt/xopt.h vendored
View File

@@ -1,225 +0,0 @@
/**
* XOpt - command line parsing library
*
* Copyright (c) 2015 Josh Junon.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef XOPT_H__
#define XOPT_H__
#pragma once
#include <stdio.h>
#include <stdbool.h>
struct xoptOption;
#ifndef offsetof
# define offsetof(T, member) (size_t)(&(((T*)0)->member))
#endif
/**
* Callback type for handling values.
* Called when a command line argument has been
* processed.
*/
typedef void (*xoptCallback)(
const char *value, /* string cmd line option */
void *data, /* custom data structure */
const struct xoptOption *option, /* detected option */
bool longArg, /* true if the long-arg version
was used */
const char **err); /* err output */
enum xoptOptionFlag {
XOPT_TYPE_STRING = 0x1, /* const char* type */
XOPT_TYPE_INT = 0x2, /* int type */
XOPT_TYPE_LONG = 0x4, /* long type */
XOPT_TYPE_FLOAT = 0x8, /* float type */
XOPT_TYPE_DOUBLE = 0x10, /* double type */
XOPT_TYPE_BOOL = 0x20, /* boolean (int) type */
XOPT_PARAM_OPTIONAL = 0x40, /* whether the argument value is
optional */
XOPT_REQUIRED = 0x80 /* indicates the flag must be
present on the command line */
};
enum xoptContextFlag {
XOPT_CTX_KEEPFIRST = 0x1, /* don't ignore argv[0] */
XOPT_CTX_POSIXMEHARDER = 0x2, /* options cannot come after
extra arguments */
XOPT_CTX_NOCONDENSE = 0x4, /* don't allow short args to be
condensed (i.e. `ls -laF') */
XOPT_CTX_SLOPPYSHORTS = 0x8, /* allow short arg values to be
directly after the character */
XOPT_CTX_STRICT = 0x10 /* fails on invalid arguments */
};
typedef struct xoptOption {
const char *longArg; /* --long-arg-name, or 0 for short
arg only */
const char shortArg; /* -s hort arg character, or '\0'
for long arg only */
size_t offset; /* offsetof(type, property) for
automatic configuration handler */
xoptCallback callback; /* callback for resolved option
handling */
long options; /* xoptOptionFlag options */
const char *argDescrip; /* --argument=argDescrip (autohelp) */
const char *descrip; /* argument explanation (autohelp) */
} xoptOption;
/* option list terminator */
#define XOPT_NULLOPTION {0, 0, 0, 0, 0, 0, 0}
typedef struct xoptContext xoptContext;
typedef struct xoptAutohelpOptions {
const char *usage; /* usage string, or null */
const char *prefix; /* printed before options, or null */
const char *suffix; /* printed after options, or null */
size_t spacer; /* number of spaces between option and
description */
} xoptAutohelpOptions;
#ifdef __cplusplus
extern "C" {
#endif
/**
* Creates an XOpt context to be used with
* subsequent calls to XOpt functions
*/
xoptContext*
xopt_context(
const char *name, /* name of the argument set (usually
name of the cli binary file/cmd */
const xoptOption *options, /* list of xoptOption objects,
terminated with XOPT_NULLOPTION */
long flags, /* xoptContextFlag flags */
const char **err); /* pointer to a const char* that
receives an err should one occur -
set to 0 if command completed
successfully */
/**
* Parses the command line of a program
* and returns the number of non-options
* returned to the `extras' pointer (see
* below)
*/
int
xopt_parse(
xoptContext *ctx, /* previously created XOpt context */
int argc, /* argc, from int main() */
const char **argv, /* argv, from int main() */
void *data, /* a custom data object whos type
corresponds to `.offset' values
specified in the options list;
populated with values interpreted
from the command line */
const char ***extras, /* receives a list of extra non-option
arguments (i.e. files, subcommands,
etc.) - length of which is returned
by the function call */
const char **err); /* pointer to a const char* that
receives an err should one occur -
set to 0 if command completed
successfully */
/**
* Generates and prints a help message
* and prints it to a FILE stream.
* If `defaults' is supplied, uses
* offsets (values) defined by the options
* list to show default options
*/
void
xopt_autohelp(
xoptContext *ctx, /* previously created XOpt context */
FILE *stream, /* a stream to print to - if 0,
defaults to `stderr'. */
const xoptAutohelpOptions *options, /* configuration options to tailor
autohelp output */
const char **err); /* pointer to a const char* that
receives an err should one occur -
set to 0 if command completed
successfully */
/**
* Generates a default option parser that's sane for most cases.
*
* Assumes there's a `help` property that is boolean-checkable that exists on the
* config pointer passed to `config_ptr` (i.e. does a lookup of `config_ptr->help`).
*
* In the event help is invoked, xopt will `goto xopt_help`. It is up to you to define such
* a label in order to recover. In this case, extrav will still be allocated and will still need to be
* freed.
*
* To be extra clear, you need to free `extrav_ptr` is if `*err_ptr` is not `NULL`.
*
* `name` is the name of the binary you'd like to pass to the context (welcome to use `argv[0]` here),
* `options` is a reference to the xoptOptions array you've specified,
* `config_ptr` is a *pointer* to your configuration instance,
* `argc` and `argv` are the int/const char ** passed into main,
* `extrac_ptr` and `extrav_ptr` are pointers to an `int`/`const char **`
* (so `int*` and `const char ***`, respectively) that receive the parsed extra args
* (note that, unless there is an error, `extrav_ptr` is owned by your program and must
* be `free()`'d when you're done using it, even if there are zero extra arguments),
* and `err_ptr` is a pointer to a `const char *` (so a `const char **`) that receives any error
* strings in the event of a problem. These errors are statically allocated so no need to
* free them. This variable should be initialized to NULL and checked after calling
* `XOPT_SIMPLE_PARSE()`.
*
* `autohelp_file`, `autohelp_usage`, `autohelp_prefix`, `autohelp_suffix` and `autohelp_spacer` are all
* parameters to the `xoptAutohelpOptions` struct (with the exception of `autohelp_file`, which must be a
* `FILE*` reference (e.g. `stdout` or `stderr`) which receives the rendered autohelp text). Consult the
* `xoptAutohelpOptions` struct above for documentation as to valid values for each of these properties.
*/
#define XOPT_SIMPLE_PARSE(name, flags, options, config_ptr, argc, argv, extrac_ptr, extrav_ptr, err_ptr, autohelp_file, autohelp_usage, autohelp_prefix, autohelp_suffix, autohelp_spacer) do { \
xoptContext *_xopt_ctx; \
*(err_ptr) = NULL; \
_xopt_ctx = xopt_context((name), (options), ((flags) ^ XOPT_CTX_POSIXMEHARDER ^ XOPT_CTX_STRICT), (err_ptr)); \
if (*(err_ptr)) break; \
*extrac_ptr = xopt_parse(_xopt_ctx, (argc), (argv), (config_ptr), (extrav_ptr), (err_ptr)); \
if ((config_ptr)->help) { \
xoptAutohelpOptions __xopt_autohelp_opts; \
__xopt_autohelp_opts.usage = (autohelp_usage); \
__xopt_autohelp_opts.prefix = (autohelp_prefix); \
__xopt_autohelp_opts.suffix = (autohelp_suffix); \
__xopt_autohelp_opts.spacer = (autohelp_spacer); \
xopt_autohelp(_xopt_ctx, (autohelp_file), &__xopt_autohelp_opts, (err_ptr)); \
if (*(err_ptr)) goto __xopt_end_free_extrav; \
goto xopt_help; \
} \
if (*(err_ptr)) goto __xopt_end_free_ctx; \
__xopt_end_free_ctx: \
free(_xopt_ctx); \
break; \
__xopt_end_free_extrav: \
free(*(extrav_ptr)); \
free(_xopt_ctx); \
break; \
} while (false)
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -2,93 +2,114 @@
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.
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.
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.
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.
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.
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.
Service processes are identical to session processes, but they are not tied to
a user session.
## Communication
In the same way that web browsers expose APIs for scripts running in the browser to modify the document, play sounds and video, and draw, Tilde Friends exposes APIs for scripts running on a Tilde Friends server to interact with a visitor's web browser, read and write files on the server, and otherwise interact with the world.
In the same way that web browsers expose APIs for scripts running in the
browser to modify the document, play sounds and video, and draw, Tilde Friends
exposes APIs for scripts running on a Tilde Friends server to interact with a
visitor's web browser, read and write files on the server, and otherwise
interact with the world.
There are several distinct classes of APIs.
First, there are low-level functions exposed from C++ to JavaScript. Most of these are only available to the core process. These typically only go through a basic JavaScript to C++ transition and are relatively fast and immediate.
First, there are low-level functions exposed from C++ to JavaScript. Most of
these are only available to the core process. These typically only go through
a basic JavaScript to C++ transition and are relatively fast and immediate.
```js
// Displays some text to the server's console.
print('Hello, world!');
```
// Displays some text to the server's console.
print("Hello, world!");
There is a mechanism for communicating between processes. Functions can be exported and called across process boundaries. When this is done, any arguments are serialized to a network protocol, deserialized by the other process, the function called, and finally any return value is passed back in the same way. Any functions referenced by the arguments or return value are also exported and can be subsequently called across process boundaries. Functions called across process boundaries are always asynchronous, returning a Promise. Care must be taken for security reasons to not pass dangerous functions ("deleteAllMydata()") to untrusted processes, and it is best for performance reasons to minimize the data size transferred between processes.
There is a mechanism for communicating between processes. Functions can be
exported and called across process boundaries. When this is done, any
arguments are serialized to a network protocol, deserialized by the other
process, the function called, and finally any return value is passed back in
the same way. Any functions referenced by the arguments or return value are
also exported and can be subsequently called across process boundaries.
Functions called across process boundaries are always asynchronous, returning a
Promise. Care must be taken for security reasons to not pass dangerous
functions ("deleteAllMydata()") to untrusted processes, and it is best for
performance reasons to minimize the data size transferred between processes.
```js
// Send an "add" function to any other running processes. When called, it
// will run in this process.
core.broadcast({
add: function (x, y) {
return x + y;
},
});
// Send an "add" function to any other running processes. When called, it
// will run in this process.
core.broadcast({add: function(x, y) { return x + y; }});
// Receive the above message and call the function.
core.register('onMessage', function (sender, message) {
message.add(3, 4).then((x) => terminal.print(x.toString()));
});
```
// Receive the above message and call the function.
core.register("onMessage", function(sender, message) {
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
extends access to a running Tilde Friends script.
```js
// Displays a message in the client's browser.
terminal.print('Hello, world!');
```
// Displays a message in the client's browser.
terminal.print("Hello, world!");
## API Documentation
The Tilde Friends API is very much evolving.
All currently registered methods can be explored in the [documentation](https://www.tildefriends.net/~cory/documentation) app.
All currently registered methods can be explored in the
[documentation](https://www.tildefriends.net/~cory/documentation) app.
All browser-facing methods are implemented in [client.js](core/client.js). Most process-related methods are implemented in [core.js](core/core.js).
All browser-facing methods are implemented in [client.js](core/client.js).
Most process-related methods are implemented in [core.js](core/core.js).
Higher-level behaviors are often implemented within library-style apps themselves and are beyond the scope of this document.
Higher-level behaviors are often implemented within library-style apps
themselves and are beyond the scope of this document.
### Terminal
All interaction with a human user is through a terminal-like interface. Though it is somewhat limiting, it makes simple things easy, and it is possible to construct complicated interfaces by creating and interacting with an iframe.
All interaction with a human user is through a terminal-like interface. Though
it is somewhat limiting, it makes simple things easy, and it is possible to
construct complicated interfaces by creating and interacting with an iframe.
#### terminal.print(arguments...)
Print to the terminal. Arguments and lists are recursively expanded. Numerous special values are supported as implemented in client.cs.
Print to the terminal. Arguments and lists are recursively expanded. Numerous
special values are supported as implemented in client.cs.
```js
// Create a link.
terminal.print({href: 'http://www.tildefriends.net/', value: 'Tilde Friends!'});
// Create a link.
terminal.print({href: "http://www.tildefriends.net/", value: "Tilde Friends!"});
// Create an iframe.
terminal.print({
iframe: '&lt;b&gt;Hello, world!&lt;/b&gt;',
width: 640,
height: 480,
});
// Create an iframe.
terminal.print({iframe: "&lt;b&gt;Hello, world!&lt;/b&gt;", width: 640, height: 480});
// Use style.
terminal.print({style: 'color: #f00', value: 'Hello, world!'});
// Use style.
terminal.print({style: "color: #f00", value: "Hello, world!"});
// Create a link that when clicked will act as if the user typed a command.
terminal.print({command: 'exit', value: 'Get out of here.'});
```
// Create a link that when clicked will act as if the user typed a command.
terminal.print({command: "exit", value: "Get out of here."});
#### terminal.clear()
@@ -114,18 +135,16 @@ Sets the browser window/tab title.
Reconfigures the terminal layout, potentially into multiple split panes.
```js
terminal.split([
{
type: 'horizontal',
children: [
{name: 'left', basis: '2in', grow: 0, shrink: 0},
{name: 'middle', grow: 1},
{name: 'right', basis: '2in', grow: 0, shrink: 0},
],
},
]);
```
terminal.split([
{
type: "horizontal",
children: [
{name: "left", basis: "2in", grow: 0, shrink: 0},
{name: "middle", grow: 1},
{name: "right", basis: "2in", grow: 0, shrink: 0},
],
},
]);
#### terminal.select(name)
@@ -133,11 +152,14 @@ Directs subsequent output to the named terminal.
#### terminal.postMessageToIframe(iframeName, message)
Sends a message to the iframe that was created with the given name, using the browser's window.postMessage.
Sends a message to the iframe that was created with the given name, using the
browser's window.postMessage.
### Database
Tilde Friends uses lmdb as a basic key value store. Keys and values are all expected to be of type String. Each application gets its own isolated database.
Tilde Friends uses lmdb as a basic key value store. Keys and values are all
expected to be of type String. Each application gets its own isolated
database.
#### database.get(key)
@@ -159,13 +181,12 @@ Retrieve a list of all key names.
Network access is generally not extended to untrusted users.
It is necessary to grant network permissions to an app owner through the administration app.
It is necessary to grant network permissions to an app owner through the
administration app.
Apps that require network access must declare it like this:
```json
{"permissions": ["network"]}
```
//! { "permissions": ["network"] }
#### network.newConnection()

3
package-lock.json generated
View File

@@ -12,8 +12,7 @@
},
"node_modules/prettier": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unprompted.tildefriends"
android:versionCode="16"
android:versionName="0.0.16-wip">
android:versionCode="17"
android:versionName="0.0.17-wip">
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application

View File

@@ -7,8 +7,13 @@
** @{
*/
/** A JS context. */
typedef struct JSContext JSContext;
/**
** Register the bcrypt script interface.
** @param context The JS context.
*/
void tf_bcrypt_register(JSContext* context);
/** @} */

View File

@@ -11,7 +11,23 @@
#include <stddef.h>
#include <stdint.h>
/**
** Convert a key from bytes to words.
** @param bytes A raw binary representation of a key.
** @param bytes_size The size of bytes.
** @param[out] out_words A human-readable English word representation of a key.
** @param words_size The size of the out_words buffer.
** @return True if the key was successfully converted.
*/
bool tf_bip39_bytes_to_words(const uint8_t* bytes, size_t bytes_size, char* out_words, size_t words_size);
/**
** Convert a key from words to bytes.
** @param words A space-separated list of English words forming a key.
** @param[out] out_bytes A buffer to receive the raw binary form of the key.
** @param bytes_size The size of the out_bytes buffer.
** @return True if the key was successfully converted.
*/
bool tf_bip39_words_to_bytes(const char* words, uint8_t* out_bytes, size_t bytes_size);
/** @} */

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