Compare commits
363 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33fb96b120 | |||
| 28a4accabf | |||
| 31c7394c17 | |||
| e2974d34e2 | |||
| 4a06c84511 | |||
| 4960a1d9d6 | |||
| 75dd8889e9 | |||
| 111a6c3c6e | |||
| 775fdafa63 | |||
| dae38bbd83 | |||
| 35f374047a | |||
| aea4a14a62 | |||
| 98f7504a4c | |||
| bb52cdd7c2 | |||
| 07b660a0d6 | |||
| 2b9d712d48 | |||
| 3c1f60b62d | |||
| bb75edfd42 | |||
| c2b61cec2c | |||
| 05c3107b27 | |||
| bb67df7846 | |||
| 89ec523ea2 | |||
| f30458d953 | |||
| 42df0d830e | |||
| 50b2c0c7f4 | |||
| 0edb76b678 | |||
| 2d71af3243 | |||
| b571cd213b | |||
| b52c79ac4e | |||
| 63c6a5ab07 | |||
| 4447ea63e2 | |||
| 61200c4a7d | |||
| 62dc9d6cc0 | |||
| a28d41e1ee | |||
| 6a05e3770b | |||
| 53a93e510c | |||
| 1cb3ecf1ea | |||
| 687665cd6b | |||
| 7879ab1d50 | |||
| 24f0cdb398 | |||
| 6d5555e596 | |||
| 4052e3235f | |||
| 13302ad1c7 | |||
| 0f8687e473 | |||
| 9399ccd684 | |||
| b3604039fa | |||
| 732089da2c | |||
| b3e7e4b196 | |||
| 09a0cfd349 | |||
| 5bf7346321 | |||
| dd558c57e0 | |||
| 5647196924 | |||
| 49b1834bc6 | |||
| d111647ea8 | |||
| d7580dab9b | |||
| 28db8a8d5f | |||
| e64d5617e7 | |||
| acd114650a | |||
| 7a47ffaa61 | |||
| 42f7f66f35 | |||
| b2b4ffeeae | |||
| 26de1f7daa | |||
| 07605933dc | |||
| 4bdc7ec616 | |||
| 8ca64550e5 | |||
| 25dbac804c | |||
| 6ab3fd168b | |||
| 94858e2371 | |||
| 6d13502e94 | |||
| 77001e595c | |||
| 6fad20ffa3 | |||
| 00fb6c9839 | |||
| 97fcf72d63 | |||
| 5d8d02515d | |||
| 859fe1feb0 | |||
| 8f61d83f41 | |||
| 6423b3e479 | |||
| 2bc8cec8a2 | |||
| b49a6cd685 | |||
| 2885380f40 | |||
| 2ec3b6a249 | |||
| 3ef795452d | |||
| 479d87c8b8 | |||
| a56077dcc7 | |||
| d3f4587c3b | |||
| 623705b7a1 | |||
| 8f87f4751d | |||
| 2ac6dfde9d | |||
| 81ade7a400 | |||
| 63f7ff9f27 | |||
| 8a0fa17a79 | |||
| 0ead5ed967 | |||
| 53261a6fbc | |||
| c60ff86a4d | |||
| 83a0b017c5 | |||
| 3746622a11 | |||
| ccd50cf59f | |||
| 93680eb43d | |||
| 3ae4b7086a | |||
| 446b1f8600 | |||
| 00fd208a2c | |||
| e574d03716 | |||
| c1f3116c9d | |||
| 3aec7e6c14 | |||
| 9f0020dec8 | |||
| 6e78ad9729 | |||
| 44d84a9b2a | |||
| ac7809415c | |||
| 675cecaa20 | |||
| 5d179cc088 | |||
| d905618590 | |||
| 3fd9bc0b18 | |||
| 39abee7f73 | |||
| b770619111 | |||
| 1c44857da4 | |||
| bca4440867 | |||
| 4855543961 | |||
| cb3d6a98b9 | |||
| ada67a13d3 | |||
| f4c928f26e | |||
| 91fd515d39 | |||
| be6e841d3d | |||
| af6afa6903 | |||
| 6ab5d2a28d | |||
| 4be033f288 | |||
| c550f92003 | |||
| ed836b3ee0 | |||
| ac7a43abf4 | |||
| 49f19fce91 | |||
| b2197eb8e9 | |||
| 5fbc2cae1c | |||
| 730abb49ce | |||
| edccab054a | |||
| e8210c6fdd | |||
| 55d69d7c13 | |||
| 50fb18d4ff | |||
| 77b1ea1fc8 | |||
| 61501a9b64 | |||
| c1507adac5 | |||
| 45fb9eda1c | |||
| 982a61f4bf | |||
| 18e5b41663 | |||
| 910c39cbd0 | |||
| 9952dfd49d | |||
| 00f75d5382 | |||
| d78828554b | |||
| b84b561109 | |||
| a618815500 | |||
| e1f3dc6ae4 | |||
| f378db6c6f | |||
| 7cec0f7d61 | |||
| f902d0374c | |||
| b5f0a0c4f7 | |||
| 00623cea09 | |||
| ed4f1d6f2c | |||
| 73f4a3407f | |||
| 6f11318e84 | |||
| e88ee91f0e | |||
| 3f8daf257c | |||
| dc387acadc | |||
| 68aa41ab96 | |||
| 85b23437b3 | |||
| c59fba817d | |||
| c3415ab75c | |||
| f1d0151d71 | |||
| 3c5c1756d1 | |||
| 6a6b65d1b3 | |||
| 81bd54dbe6 | |||
| 6a1bb0d3bc | |||
| 705e8b553f | |||
| e4729b22f2 | |||
| 662112551a | |||
| 38fe88aab8 | |||
| 578c51faa0 | |||
| a3ccc73b81 | |||
| 7312f4d43a | |||
| 8b546c7e02 | |||
| c0b6ff2e64 | |||
| 638b7cc1e5 | |||
| 05e54e1be0 | |||
| 4c3299ead0 | |||
| 1ef56b35ad | |||
| 061e79c295 | |||
| 5edfe732b1 | |||
| a8f9b67f71 | |||
| de7fbf1eb7 | |||
| a51a3d7e43 | |||
| 433b3b1003 | |||
| 6703c5b584 | |||
| 5f729efabe | |||
| b2085b3f28 | |||
| 2f893494b0 | |||
| e26af21f63 | |||
| 7e1d738f8d | |||
| 199448e11e | |||
| fdaabab807 | |||
| ca4560c5c9 | |||
| 2478f3064d | |||
| e9b8b43e7c | |||
| 951155f1b6 | |||
| 1b678175ef | |||
| 8eb1f40eec | |||
| 235887b3bf | |||
| 0b3d66dd48 | |||
| beb9ef3754 | |||
| 9f6a480736 | |||
| b3bac2927d | |||
| ef389f2ba2 | |||
| ef21dc6ae8 | |||
| 6e55b6b49e | |||
| db115ef1bd | |||
| 678838dbd5 | |||
| 586f87625d | |||
| 1542370f9b | |||
| 1f7d5968c7 | |||
| 39e51f7790 | |||
| 052663efbe | |||
| 8f84ff2611 | |||
| 37e1c5d97b | |||
| cef526bcf3 | |||
| 6af36cafa9 | |||
| fca859d93d | |||
| 2178300d8d | |||
| 636bdcce6b | |||
| 94b7703ca9 | |||
| a391dd1316 | |||
| b6ba5211b7 | |||
| 8e8e130045 | |||
| 1f40bc1a0f | |||
| 5437212222 | |||
| a8ab845cd2 | |||
| 8cee6dc98b | |||
| 70c2b73414 | |||
| 98013c4422 | |||
| e9e22b762d | |||
| 620db19936 | |||
| 94a79dd62c | |||
| b56c3efde0 | |||
| 066827f8f1 | |||
| c3b65d9cd8 | |||
| a15b916b06 | |||
| 31d0a5c233 | |||
| 140179e80a | |||
| 53cba2d7e4 | |||
| e54312d3b8 | |||
| cadc27b7b5 | |||
| 388b829ec1 | |||
| 67861f0f33 | |||
| a1f1eb34d5 | |||
| 2a6789063e | |||
| cbf1273a55 | |||
| 8143a23ced | |||
| 3c17810747 | |||
| bea7a2e9ed | |||
| 2f0a2ac6b0 | |||
| 18908b6b56 | |||
| b135a210cc | |||
| 3a2a829940 | |||
| e56dd2dd2d | |||
| 3f41a48bc7 | |||
| 65ed53281a | |||
| 1121557a2e | |||
| d4a7b86ee7 | |||
| 626c18b04e | |||
| bfa97ed7c7 | |||
| deae4d5367 | |||
| 899605c860 | |||
| dc9a279991 | |||
| 2a53892581 | |||
| 6bef0eb764 | |||
| 462b40640c | |||
| 72e1b2025c | |||
| fc7c4b1257 | |||
| 6c22c59056 | |||
| 94c2b1184f | |||
| 45231d703d | |||
| 7882fcbe8f | |||
| 3bbc8c4d35 | |||
| 8ae10dc80b | |||
| 9b11c2c629 | |||
| e2a231fb4a | |||
| 8a9502d1f2 | |||
| 534438df63 | |||
| 45a4feec96 | |||
| aa7a32395e | |||
| ab9f57f044 | |||
| 4040d6aa08 | |||
| 1c96f5c35e | |||
| 4d3e42812d | |||
| f7b3711d4f | |||
| 2408e076ff | |||
| 6f71ffb477 | |||
| 214433f36a | |||
| 309b22732e | |||
| 6fe7687b2a | |||
| a8cbf757ff | |||
| 4a4bedfe2b | |||
| 051291f725 | |||
| d2b338095f | |||
| 899827a8f2 | |||
| 5fcbe3d6a9 | |||
| a0a40e6cb2 | |||
| bb1190e3f8 | |||
| 0a3baed1da | |||
| 4931c489ed | |||
| 996f9abaa2 | |||
| 08c097e176 | |||
| daa861a98b | |||
| a25d08fd76 | |||
| 392d31cc53 | |||
| 92926fa8df | |||
| 61ae9ae465 | |||
| 89622697d5 | |||
| 17694f5646 | |||
| 5a1303149f | |||
| 8a0e190a86 | |||
| 0d7dfd8c9e | |||
| f979ff7050 | |||
| e3fcdea362 | |||
| 476fec2757 | |||
| 53c215399b | |||
| 2c330802da | |||
| 851d7046ea | |||
| c0019d7246 | |||
| 7688e4d3a8 | |||
| ef58749ce3 | |||
| 35941a7ddc | |||
| 1f2664e5a8 | |||
| 35656a5c34 | |||
| 799f22e989 | |||
| e226a37251 | |||
| 8e3bc9d700 | |||
| 58c3e6c2ab | |||
| 0dc148bfea | |||
| 3eff1b08a9 | |||
| 02d789471f | |||
| d367d47c4d | |||
| c93b8fc045 | |||
| eb9377e21d | |||
| a1764eee42 | |||
| 86ef74e20d | |||
| 4de53b9926 | |||
| 99a195a3fd | |||
| f1ced31f69 | |||
| b3cedf2baa | |||
| 3bf19fabda | |||
| cf81ebe8ad | |||
| 278b5566a1 | |||
| e8c1390f09 | |||
| 3c04abda45 | |||
| 2597f99ccf | |||
| 9d3a07c1cf | |||
| bdfd8925b5 | |||
| 1a4d1985f4 | |||
| 6273f3ea53 | |||
| 5bdc6fa471 | |||
| 3ba41291db | |||
| 0867811952 | |||
| 8d961cd805 | |||
| 97cea7b40b | |||
| 4106834db8 | |||
| a4a8f7cab2 | |||
| 9e209ee800 |
@@ -48,7 +48,7 @@ jobs:
|
||||
- name: Build documentation
|
||||
run: |
|
||||
mkdir -p out/html/ ~/.ssh/
|
||||
make docs
|
||||
make -j`nproc` docs
|
||||
echo 'pildefriends ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKD3Kde5vDO0TrMBDK0IGGeNGe/XinWAZkSQ/rXxwUjt' >> ~/.ssh/known_hosts
|
||||
rsync -avP --delete -e "ssh -i /opt/keys/ssh.ed25519" out/html/ tfdocs@pildefriends:docs/html/
|
||||
- name: Setup JDK
|
||||
@@ -59,11 +59,11 @@ jobs:
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
with:
|
||||
packages: 'tools platform-tools build-tools;34.0.0 platforms;android-34 ndk;26.3.11579264'
|
||||
packages: 'tools platform-tools build-tools;35.0.0 platforms;android-35 ndk;27.2.12479018'
|
||||
- name: Docker build
|
||||
run: DOCKER_BUILDKIT=1 docker build .
|
||||
- name: Build
|
||||
run: ANDROID_SDK=$HOME/.android/sdk make -j`nproc` all dist docs
|
||||
run: ANDROID_SDK=$HOME/.android/sdk make -j`nproc` all dist
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
4
.gitmodules
vendored
@@ -19,10 +19,6 @@
|
||||
[submodule "deps/picohttpparser"]
|
||||
path = deps/picohttpparser
|
||||
url = https://github.com/h2o/picohttpparser.git
|
||||
[submodule "deps/openssl_src"]
|
||||
path = deps/openssl_src
|
||||
url = https://github.com/openssl/openssl.git
|
||||
shallow = true
|
||||
[submodule "deps/c-ares"]
|
||||
path = deps/c-ares
|
||||
url = https://github.com/c-ares/c-ares.git
|
||||
|
||||
@@ -4,7 +4,6 @@ RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
libc6-dev \
|
||||
perl \
|
||||
make
|
||||
|
||||
COPY . /app
|
||||
|
||||
366
Doxyfile
@@ -1,4 +1,4 @@
|
||||
# Doxyfile 1.9.8
|
||||
# Doxyfile 1.9.4
|
||||
|
||||
# This file describes the settings to be used by the documentation system
|
||||
# doxygen (www.doxygen.org) for a project.
|
||||
@@ -19,8 +19,7 @@
|
||||
# configuration file:
|
||||
# doxygen -x [configFile]
|
||||
# Use doxygen to compare the used configuration file with the template
|
||||
# configuration file without replacing the environment variables or CMake type
|
||||
# replacement variables:
|
||||
# configuration file without replacing the environment variables:
|
||||
# doxygen -x_noenv [configFile]
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -86,7 +85,7 @@ CREATE_SUBDIRS = NO
|
||||
# level increment doubles the number of directories, resulting in 4096
|
||||
# directories at level 8 which is the default and also the maximum value. The
|
||||
# sub-directories are organized in 2 levels, the first level always has a fixed
|
||||
# number of 16 directories.
|
||||
# numer of 16 directories.
|
||||
# Minimum value: 0, maximum value: 8, default value: 8.
|
||||
# This tag requires that the tag CREATE_SUBDIRS is set to YES.
|
||||
|
||||
@@ -342,7 +341,7 @@ OPTIMIZE_OUTPUT_SLICE = NO
|
||||
#
|
||||
# Note see also the list of default file extension mappings.
|
||||
|
||||
EXTENSION_MAPPING =
|
||||
EXTENSION_MAPPING = js=javascript
|
||||
|
||||
# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
|
||||
# according to the Markdown format, which allows for more readable
|
||||
@@ -363,17 +362,6 @@ MARKDOWN_SUPPORT = YES
|
||||
|
||||
TOC_INCLUDE_HEADINGS = 5
|
||||
|
||||
# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to
|
||||
# generate identifiers for the Markdown headings. Note: Every identifier is
|
||||
# unique.
|
||||
# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a
|
||||
# sequence number starting at 0 and GITHUB use the lower case version of title
|
||||
# with any whitespace replaced by '-' and punctuation characters removed.
|
||||
# The default value is: DOXYGEN.
|
||||
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
|
||||
|
||||
MARKDOWN_ID_STYLE = DOXYGEN
|
||||
|
||||
# When enabled doxygen tries to link words that correspond to documented
|
||||
# classes, or namespaces to their corresponding documentation. Such a link can
|
||||
# be prevented in individual cases by putting a % sign in front of the word or
|
||||
@@ -498,14 +486,6 @@ LOOKUP_CACHE_SIZE = 0
|
||||
|
||||
NUM_PROC_THREADS = 1
|
||||
|
||||
# If the TIMESTAMP tag is set different from NO then each generated page will
|
||||
# contain the date or date and time when the page was generated. Setting this to
|
||||
# NO can help when comparing the output of multiple runs.
|
||||
# Possible values are: YES, NO, DATETIME and DATE.
|
||||
# The default value is: NO.
|
||||
|
||||
TIMESTAMP = NO
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Build related configuration options
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -587,8 +567,7 @@ HIDE_UNDOC_MEMBERS = NO
|
||||
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
|
||||
# undocumented classes that are normally visible in the class hierarchy. If set
|
||||
# to NO, these classes will be included in the various overviews. This option
|
||||
# will also hide undocumented C++ concepts if enabled. This option has no effect
|
||||
# if EXTRACT_ALL is enabled.
|
||||
# has no effect if EXTRACT_ALL is enabled.
|
||||
# The default value is: NO.
|
||||
|
||||
HIDE_UNDOC_CLASSES = NO
|
||||
@@ -626,8 +605,7 @@ INTERNAL_DOCS = NO
|
||||
# Windows (including Cygwin) and MacOS, users should typically set this option
|
||||
# to NO, whereas on Linux or other Unix flavors it should typically be set to
|
||||
# YES.
|
||||
# Possible values are: SYSTEM, NO and YES.
|
||||
# The default value is: SYSTEM.
|
||||
# The default value is: system dependent.
|
||||
|
||||
CASE_SENSE_NAMES = YES
|
||||
|
||||
@@ -879,26 +857,11 @@ WARN_IF_INCOMPLETE_DOC = YES
|
||||
|
||||
WARN_NO_PARAMDOC = NO
|
||||
|
||||
# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about
|
||||
# undocumented enumeration values. If set to NO, doxygen will accept
|
||||
# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag
|
||||
# will automatically be disabled.
|
||||
# The default value is: NO.
|
||||
|
||||
WARN_IF_UNDOC_ENUM_VAL = NO
|
||||
|
||||
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
|
||||
# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
|
||||
# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
|
||||
# at the end of the doxygen process doxygen will return with a non-zero status.
|
||||
# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves
|
||||
# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not
|
||||
# write the warning messages in between other messages but write them at the end
|
||||
# of a run, in case a WARN_LOGFILE is defined the warning messages will be
|
||||
# besides being in the defined file also be shown at the end of a run, unless
|
||||
# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case
|
||||
# the behavior will remain as with the setting FAIL_ON_WARNINGS.
|
||||
# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT.
|
||||
# Possible values are: NO, YES and FAIL_ON_WARNINGS.
|
||||
# The default value is: NO.
|
||||
|
||||
WARN_AS_ERROR = NO
|
||||
@@ -944,6 +907,10 @@ WARN_LOGFILE =
|
||||
# Note: If this tag is empty the current directory is searched.
|
||||
|
||||
INPUT = README.md \
|
||||
core/app.js \
|
||||
core/client.js \
|
||||
core/core.js \
|
||||
core/tfrpc.js \
|
||||
docs/ \
|
||||
src/
|
||||
|
||||
@@ -952,21 +919,10 @@ INPUT = README.md \
|
||||
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
|
||||
# documentation (see:
|
||||
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
|
||||
# See also: INPUT_FILE_ENCODING
|
||||
# The default value is: UTF-8.
|
||||
|
||||
INPUT_ENCODING = UTF-8
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify
|
||||
# character encoding on a per file pattern basis. Doxygen will compare the file
|
||||
# name with each pattern and apply the encoding instead of the default
|
||||
# INPUT_ENCODING) if there is a match. The character encodings are a list of the
|
||||
# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding
|
||||
# "INPUT_ENCODING" for further information on supported encodings.
|
||||
|
||||
INPUT_FILE_ENCODING =
|
||||
|
||||
# If the value of the INPUT tag contains directories, you can use the
|
||||
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
|
||||
# *.h) to filter out the source-files in the directories.
|
||||
@@ -978,14 +934,15 @@ INPUT_FILE_ENCODING =
|
||||
# Note the list of default checked file patterns might differ from the list of
|
||||
# default file extension mappings.
|
||||
#
|
||||
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm,
|
||||
# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl,
|
||||
# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php,
|
||||
# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be
|
||||
# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
|
||||
# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
|
||||
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
|
||||
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
|
||||
# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
|
||||
# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
|
||||
# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
|
||||
# *.vhdl, *.ucf, *.qsf and *.ice.
|
||||
|
||||
FILE_PATTERNS = *.h \
|
||||
*.js \
|
||||
*.md
|
||||
|
||||
# The RECURSIVE tag can be used to specify whether or not subdirectories should
|
||||
@@ -1024,6 +981,9 @@ EXCLUDE_PATTERNS =
|
||||
# output. The symbol name can be a fully qualified name, a word, or if the
|
||||
# wildcard * is used, a substring. Examples: ANamespace, AClass,
|
||||
# ANamespace::AClass, ANamespace::*Test
|
||||
#
|
||||
# Note that the wildcards are matched against the file with absolute path, so to
|
||||
# exclude all test directories use the pattern */test/*
|
||||
|
||||
EXCLUDE_SYMBOLS =
|
||||
|
||||
@@ -1051,7 +1011,7 @@ EXAMPLE_RECURSIVE = NO
|
||||
# that contain images that are to be included in the documentation (see the
|
||||
# \image command).
|
||||
|
||||
IMAGE_PATH =
|
||||
IMAGE_PATH = docs/images/
|
||||
|
||||
# The INPUT_FILTER tag can be used to specify a program that doxygen should
|
||||
# invoke to filter for each input file. Doxygen will invoke the filter program
|
||||
@@ -1068,11 +1028,6 @@ IMAGE_PATH =
|
||||
# code is scanned, but not when the output code is generated. If lines are added
|
||||
# or removed, the anchors will not be placed correctly.
|
||||
#
|
||||
# Note that doxygen will use the data processed and written to standard output
|
||||
# for further processing, therefore nothing else, like debug statements or used
|
||||
# commands (so in case of a Windows batch file always use @echo OFF), should be
|
||||
# written to standard output.
|
||||
#
|
||||
# Note that for custom extensions or not directly supported extensions you also
|
||||
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
|
||||
# properly processed by doxygen.
|
||||
@@ -1114,15 +1069,6 @@ FILTER_SOURCE_PATTERNS =
|
||||
|
||||
USE_MDFILE_AS_MAINPAGE = README.md
|
||||
|
||||
# The Fortran standard specifies that for fixed formatted Fortran code all
|
||||
# characters from position 72 are to be considered as comment. A common
|
||||
# extension is to allow longer lines before the automatic comment starts. The
|
||||
# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can
|
||||
# be processed before the automatic comment starts.
|
||||
# Minimum value: 7, maximum value: 10000, default value: 72.
|
||||
|
||||
FORTRAN_COMMENT_AFTER = 72
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to source browsing
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -1260,11 +1206,10 @@ CLANG_DATABASE_PATH =
|
||||
|
||||
ALPHABETICAL_INDEX = YES
|
||||
|
||||
# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes)
|
||||
# that should be ignored while generating the index headers. The IGNORE_PREFIX
|
||||
# tag works for classes, function and member names. The entity will be placed in
|
||||
# the alphabetical list under the first letter of the entity name that remains
|
||||
# after removing the prefix.
|
||||
# In case all classes in a project start with a common prefix, all classes will
|
||||
# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
|
||||
# can be used to specify a prefix (or a list of prefixes) that should be ignored
|
||||
# while generating the index headers.
|
||||
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
|
||||
|
||||
IGNORE_PREFIX =
|
||||
@@ -1343,12 +1288,7 @@ HTML_STYLESHEET =
|
||||
# Doxygen will copy the style sheet files to the output directory.
|
||||
# Note: The order of the extra style sheet files is of importance (e.g. the last
|
||||
# style sheet in the list overrules the setting of the previous ones in the
|
||||
# list).
|
||||
# Note: Since the styling of scrollbars can currently not be overruled in
|
||||
# Webkit/Chromium, the styling will be left out of the default doxygen.css if
|
||||
# one or more extra stylesheets have been specified. So if scrollbar
|
||||
# customization is desired it has to be added explicitly. For an example see the
|
||||
# documentation.
|
||||
# list). For an example see the documentation.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_EXTRA_STYLESHEET =
|
||||
@@ -1363,19 +1303,6 @@ HTML_EXTRA_STYLESHEET =
|
||||
|
||||
HTML_EXTRA_FILES =
|
||||
|
||||
# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output
|
||||
# should be rendered with a dark or light theme.
|
||||
# Possible values are: LIGHT always generate light mode output, DARK always
|
||||
# generate dark mode output, AUTO_LIGHT automatically set the mode according to
|
||||
# the user preference, use light mode if no preference is set (the default),
|
||||
# AUTO_DARK automatically set the mode according to the user preference, use
|
||||
# dark mode if no preference is set and TOGGLE allow to user to switch between
|
||||
# light and dark mode via a button.
|
||||
# The default value is: AUTO_LIGHT.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_COLORSTYLE = AUTO_LIGHT
|
||||
|
||||
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
|
||||
# will adjust the colors in the style sheet and background images according to
|
||||
# this color. Hue is specified as an angle on a color-wheel, see
|
||||
@@ -1406,6 +1333,15 @@ HTML_COLORSTYLE_SAT = 100
|
||||
|
||||
HTML_COLORSTYLE_GAMMA = 80
|
||||
|
||||
# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
|
||||
# page will contain the date and time when the page was generated. Setting this
|
||||
# to YES can help to show when doxygen was last run and thus if the
|
||||
# documentation is up to date.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
#HTML_TIMESTAMP = NO
|
||||
|
||||
# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
|
||||
# documentation will contain a main index with vertical navigation menus that
|
||||
# are dynamically created via JavaScript. If disabled, the navigation index will
|
||||
@@ -1425,13 +1361,6 @@ HTML_DYNAMIC_MENUS = YES
|
||||
|
||||
HTML_DYNAMIC_SECTIONS = NO
|
||||
|
||||
# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be
|
||||
# dynamically folded and expanded in the generated HTML source code.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_CODE_FOLDING = YES
|
||||
|
||||
# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
|
||||
# shown in the various tree structured indices initially; the user can expand
|
||||
# and collapse entries dynamically later on. Doxygen will expand the tree to
|
||||
@@ -1562,16 +1491,6 @@ BINARY_TOC = NO
|
||||
|
||||
TOC_EXPAND = NO
|
||||
|
||||
# The SITEMAP_URL tag is used to specify the full URL of the place where the
|
||||
# generated documentation will be placed on the server by the user during the
|
||||
# deployment of the documentation. The generated sitemap is called sitemap.xml
|
||||
# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL
|
||||
# is specified no sitemap is generated. For information about the sitemap
|
||||
# protocol see https://www.sitemaps.org
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
SITEMAP_URL =
|
||||
|
||||
# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
|
||||
# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
|
||||
# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
|
||||
@@ -1747,6 +1666,17 @@ HTML_FORMULA_FORMAT = png
|
||||
|
||||
FORMULA_FONTSIZE = 10
|
||||
|
||||
# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
|
||||
# generated for formulas are transparent PNGs. Transparent PNGs are not
|
||||
# supported properly for IE 6.0, but are supported on all modern browsers.
|
||||
#
|
||||
# Note that when changing this option you need to delete any form_*.png files in
|
||||
# the HTML output directory before the changes have effect.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
#FORMULA_TRANSPARENT = YES
|
||||
|
||||
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
|
||||
# to create new LaTeX commands to be used in formulas as building blocks. See
|
||||
# the section "Including formulas" for details.
|
||||
@@ -1808,8 +1738,8 @@ MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2
|
||||
|
||||
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
|
||||
# extension names that should be enabled during MathJax rendering. For example
|
||||
# for MathJax version 2 (see
|
||||
# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
|
||||
# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html
|
||||
# #tex-and-latex-extensions):
|
||||
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
|
||||
# For example for MathJax version 3 (see
|
||||
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
|
||||
@@ -2060,16 +1990,9 @@ PDF_HYPERLINKS = YES
|
||||
|
||||
USE_PDFLATEX = YES
|
||||
|
||||
# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error.
|
||||
# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch
|
||||
# mode nothing is printed on the terminal, errors are scrolled as if <return> is
|
||||
# hit at every error; missing files that TeX tries to input or request from
|
||||
# keyboard input (\read on a not open input stream) cause the job to abort,
|
||||
# NON_STOP In nonstop mode the diagnostic message will appear on the terminal,
|
||||
# but there is no possibility of user interaction just like in batch mode,
|
||||
# SCROLL In scroll mode, TeX will stop only for missing files to input or if
|
||||
# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at
|
||||
# each error, asking for user intervention.
|
||||
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
|
||||
# command to the generated LaTeX files. This will instruct LaTeX to keep running
|
||||
# if errors occur, instead of asking the user for help.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
@@ -2090,6 +2013,14 @@ LATEX_HIDE_INDICES = NO
|
||||
|
||||
LATEX_BIB_STYLE = plain
|
||||
|
||||
# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
|
||||
# page will contain the date and time when the page was generated. Setting this
|
||||
# to NO can help when comparing the output of multiple runs.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
#LATEX_TIMESTAMP = NO
|
||||
|
||||
# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
|
||||
# path from which the emoji images will be read. If a relative path is entered,
|
||||
# it will be relative to the LATEX_OUTPUT directory. If left blank the
|
||||
@@ -2255,39 +2186,13 @@ DOCBOOK_OUTPUT = docbook
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
|
||||
# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures
|
||||
# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
|
||||
# the structure of the code including all documentation. Note that this feature
|
||||
# is still experimental and incomplete at the moment.
|
||||
# The default value is: NO.
|
||||
|
||||
GENERATE_AUTOGEN_DEF = NO
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to Sqlite3 output
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3
|
||||
# database with symbols found by doxygen stored in tables.
|
||||
# The default value is: NO.
|
||||
|
||||
#GENERATE_SQLITE3 = NO
|
||||
|
||||
# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be
|
||||
# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put
|
||||
# in front of it.
|
||||
# The default directory is: sqlite3.
|
||||
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
|
||||
|
||||
#SQLITE3_OUTPUT = sqlite3
|
||||
|
||||
# The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db
|
||||
# database file will be recreated with each doxygen run. If set to NO, doxygen
|
||||
# will warn if an a database file is already found and not modify it.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
|
||||
|
||||
#SQLITE3_RECREATE_DB = YES
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the Perl module output
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -2430,15 +2335,15 @@ TAGFILES =
|
||||
|
||||
GENERATE_TAGFILE =
|
||||
|
||||
# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces
|
||||
# will be listed in the class and namespace index. If set to NO, only the
|
||||
# inherited external classes will be listed.
|
||||
# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
|
||||
# the class index. If set to NO, only the inherited external classes will be
|
||||
# listed.
|
||||
# The default value is: NO.
|
||||
|
||||
ALLEXTERNALS = NO
|
||||
|
||||
# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
|
||||
# in the topic index. If set to NO, only the current project's groups will be
|
||||
# in the modules index. If set to NO, only the current project's groups will be
|
||||
# listed.
|
||||
# The default value is: YES.
|
||||
|
||||
@@ -2452,9 +2357,16 @@ EXTERNAL_GROUPS = YES
|
||||
EXTERNAL_PAGES = YES
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to diagram generator tools
|
||||
# Configuration options related to the dot tool
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# You can include diagrams made with dia in doxygen documentation. Doxygen will
|
||||
# then run dia to produce the diagram and insert it in the documentation. The
|
||||
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
|
||||
# If left empty dia is assumed to be found in the default search path.
|
||||
|
||||
DIA_PATH =
|
||||
|
||||
# If set to YES the inheritance and collaboration graphs will hide inheritance
|
||||
# and usage relations if the target is undocumented or is not a class.
|
||||
# The default value is: YES.
|
||||
@@ -2463,10 +2375,10 @@ HIDE_UNDOC_RELATIONS = YES
|
||||
|
||||
# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
|
||||
# available from the path. This tool is part of Graphviz (see:
|
||||
# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
|
||||
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
|
||||
# Bell Labs. The other options in this section have no effect if this option is
|
||||
# set to NO
|
||||
# The default value is: YES.
|
||||
# The default value is: NO.
|
||||
|
||||
HAVE_DOT = YES
|
||||
|
||||
@@ -2480,51 +2392,37 @@ HAVE_DOT = YES
|
||||
|
||||
DOT_NUM_THREADS = 0
|
||||
|
||||
# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of
|
||||
# subgraphs. When you want a differently looking font in the dot files that
|
||||
# doxygen generates you can specify fontname, fontcolor and fontsize attributes.
|
||||
# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node,
|
||||
# Edge and Graph Attributes specification</a> You need to make sure dot is able
|
||||
# to find the font, which can be done by putting it in a standard location or by
|
||||
# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
|
||||
# directory containing the font. Default graphviz fontsize is 14.
|
||||
# The default value is: fontname=Helvetica,fontsize=10.
|
||||
# When you want a differently looking font in the dot files that doxygen
|
||||
# generates you can specify the font name using DOT_FONTNAME. You need to make
|
||||
# sure dot is able to find the font, which can be done by putting it in a
|
||||
# standard location or by setting the DOTFONTPATH environment variable or by
|
||||
# setting DOT_FONTPATH to the directory containing the font.
|
||||
# The default value is: Helvetica.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10"
|
||||
#DOT_FONTNAME = Helvetica
|
||||
|
||||
# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can
|
||||
# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a
|
||||
# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about
|
||||
# arrows shapes.</a>
|
||||
# The default value is: labelfontname=Helvetica,labelfontsize=10.
|
||||
# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
|
||||
# dot graphs.
|
||||
# Minimum value: 4, maximum value: 24, default value: 10.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10"
|
||||
#DOT_FONTSIZE = 10
|
||||
|
||||
# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes
|
||||
# around nodes set 'shape=plain' or 'shape=plaintext' <a
|
||||
# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a>
|
||||
# The default value is: shape=box,height=0.2,width=0.4.
|
||||
# By default doxygen will tell dot to use the default font as specified with
|
||||
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
|
||||
# the path where dot can find it using this tag.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
|
||||
#DOT_FONTPATH =
|
||||
|
||||
# You can set the path where dot can find font specified with fontname in
|
||||
# DOT_COMMON_ATTR and others dot attributes.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_FONTPATH =
|
||||
|
||||
# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will
|
||||
# generate a graph for each documented class showing the direct and indirect
|
||||
# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and
|
||||
# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case
|
||||
# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the
|
||||
# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used.
|
||||
# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance
|
||||
# relations will be shown as texts / links.
|
||||
# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN.
|
||||
# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
|
||||
# graph for each documented class showing the direct and indirect inheritance
|
||||
# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
|
||||
# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
|
||||
# to TEXT the direct and indirect inheritance relations will be shown as texts /
|
||||
# links.
|
||||
# Possible values are: NO, YES, TEXT and GRAPH.
|
||||
# The default value is: YES.
|
||||
|
||||
CLASS_GRAPH = YES
|
||||
@@ -2532,21 +2430,15 @@ CLASS_GRAPH = YES
|
||||
# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
|
||||
# graph for each documented class showing the direct and indirect implementation
|
||||
# dependencies (inheritance, containment, and class references variables) of the
|
||||
# class with other documented classes. Explicit enabling a collaboration graph,
|
||||
# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the
|
||||
# command \collaborationgraph. Disabling a collaboration graph can be
|
||||
# accomplished by means of the command \hidecollaborationgraph.
|
||||
# class with other documented classes.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
COLLABORATION_GRAPH = YES
|
||||
|
||||
# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
|
||||
# groups, showing the direct groups dependencies. Explicit enabling a group
|
||||
# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means
|
||||
# of the command \groupgraph. Disabling a directory graph can be accomplished by
|
||||
# means of the command \hidegroupgraph. See also the chapter Grouping in the
|
||||
# manual.
|
||||
# groups, showing the direct groups dependencies. See also the chapter Grouping
|
||||
# in the manual.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
@@ -2606,9 +2498,7 @@ TEMPLATE_RELATIONS = NO
|
||||
# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
|
||||
# YES then doxygen will generate a graph for each documented file showing the
|
||||
# direct and indirect include dependencies of the file with other documented
|
||||
# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO,
|
||||
# can be accomplished by means of the command \includegraph. Disabling an
|
||||
# include graph can be accomplished by means of the command \hideincludegraph.
|
||||
# files.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
@@ -2617,10 +2507,7 @@ INCLUDE_GRAPH = YES
|
||||
# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
|
||||
# set to YES then doxygen will generate a graph for each documented file showing
|
||||
# the direct and indirect include dependencies of the file with other documented
|
||||
# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set
|
||||
# to NO, can be accomplished by means of the command \includedbygraph. Disabling
|
||||
# an included by graph can be accomplished by means of the command
|
||||
# \hideincludedbygraph.
|
||||
# files.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
@@ -2660,10 +2547,7 @@ GRAPHICAL_HIERARCHY = YES
|
||||
# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
|
||||
# dependencies a directory has on other directories in a graphical way. The
|
||||
# dependency relations are determined by the #include relations between the
|
||||
# files in the directories. Explicit enabling a directory graph, when
|
||||
# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command
|
||||
# \directorygraph. Disabling a directory graph can be accomplished by means of
|
||||
# the command \hidedirectorygraph.
|
||||
# files in the directories.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
@@ -2679,13 +2563,12 @@ DIR_GRAPH_MAX_DEPTH = 1
|
||||
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
|
||||
# generated by dot. For an explanation of the image formats see the section
|
||||
# output formats in the documentation of the dot tool (Graphviz (see:
|
||||
# https://www.graphviz.org/)).
|
||||
# http://www.graphviz.org/)).
|
||||
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
|
||||
# to make the SVG files visible in IE 9+ (other browsers do not have this
|
||||
# requirement).
|
||||
# Possible values are: png, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd,
|
||||
# gif, gif:cairo, gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd,
|
||||
# png:cairo, png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
|
||||
# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
|
||||
# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
|
||||
# png:gdiplus:gdiplus.
|
||||
# The default value is: png.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
@@ -2717,12 +2600,11 @@ DOT_PATH =
|
||||
|
||||
DOTFILE_DIRS =
|
||||
|
||||
# You can include diagrams made with dia in doxygen documentation. Doxygen will
|
||||
# then run dia to produce the diagram and insert it in the documentation. The
|
||||
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
|
||||
# If left empty dia is assumed to be found in the default search path.
|
||||
# The MSCFILE_DIRS tag can be used to specify one or more directories that
|
||||
# contain msc files that are included in the documentation (see the \mscfile
|
||||
# command).
|
||||
|
||||
DIA_PATH =
|
||||
MSCFILE_DIRS =
|
||||
|
||||
# The DIAFILE_DIRS tag can be used to specify one or more directories that
|
||||
# contain dia files that are included in the documentation (see the \diafile
|
||||
@@ -2772,6 +2654,18 @@ DOT_GRAPH_MAX_NODES = 50
|
||||
|
||||
MAX_DOT_GRAPH_DEPTH = 0
|
||||
|
||||
# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
|
||||
# background. This is disabled by default, because dot on Windows does not seem
|
||||
# to support this out of the box.
|
||||
#
|
||||
# Warning: Depending on the platform used, enabling this option may lead to
|
||||
# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
|
||||
# read).
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
#DOT_TRANSPARENT = NO
|
||||
|
||||
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
|
||||
# files in one run (i.e. multiple -o and -T options on the command line). This
|
||||
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
|
||||
@@ -2799,19 +2693,3 @@ GENERATE_LEGEND = YES
|
||||
# The default value is: YES.
|
||||
|
||||
DOT_CLEANUP = YES
|
||||
|
||||
# You can define message sequence charts within doxygen comments using the \msc
|
||||
# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will
|
||||
# use a built-in version of mscgen tool to produce the charts. Alternatively,
|
||||
# the MSCGEN_TOOL tag can also specify the name an external tool. For instance,
|
||||
# specifying prog as the value, doxygen will call the tool as prog -T
|
||||
# <outfile_format> -o <outputfile> <inputfile>. The external tool should support
|
||||
# output file formats "png", "eps", "svg", and "ismap".
|
||||
|
||||
MSCGEN_TOOL =
|
||||
|
||||
# The MSCFILE_DIRS tag can be used to specify one or more directories that
|
||||
# contain msc files that are included in the documentation (see the \mscfile
|
||||
# command).
|
||||
|
||||
MSCFILE_DIRS =
|
||||
|
||||
217
GNUmakefile
@@ -16,15 +16,15 @@ MAKEFLAGS += --no-builtin-rules
|
||||
## LD := Linker.
|
||||
## ANDROID_SDK := Path to the Android SDK.
|
||||
|
||||
VERSION_CODE := 35
|
||||
VERSION_CODE_IOS := 12
|
||||
VERSION_NUMBER := 0.0.30
|
||||
VERSION_CODE := 47
|
||||
VERSION_CODE_IOS := 25
|
||||
VERSION_NUMBER := 0.2025.10
|
||||
VERSION_NAME := This program kills fascists.
|
||||
|
||||
IPHONEOS_VERSION_MIN=14.0
|
||||
IPHONEOS_VERSION_MIN=14.5
|
||||
|
||||
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3490100.zip
|
||||
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar
|
||||
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3510000.zip
|
||||
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.18.2/bundletool-all-1.18.2.jar
|
||||
APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
|
||||
|
||||
@@ -38,7 +38,6 @@ BUNDLETOOL = out/bundletool.jar
|
||||
|
||||
HAVE_WIN :=
|
||||
HAVE_CROSS_AARCH64 :=
|
||||
USE_SYSTEM_SSL :=
|
||||
|
||||
export SOURCE_DATE_EPOCH=1
|
||||
export TZ=UTC
|
||||
@@ -65,7 +64,6 @@ LDFLAGS += \
|
||||
-lbsd \
|
||||
-lnetwork \
|
||||
-Wno-stringop-overflow
|
||||
USE_SYSTEM_SSL := 1
|
||||
HAVE_ANDROID = 0
|
||||
HAVE_LINUX_IOS = 0
|
||||
HAVE_LINUX_MACOS = 0
|
||||
@@ -80,13 +78,12 @@ LDFLAGS += \
|
||||
HAVE_ANDROID :=
|
||||
HAVE_LINUX_IOS :=
|
||||
HAVE_LINUX_MACOS :=
|
||||
USE_SYSTEM_SSL := 1
|
||||
else
|
||||
$(error Unexpected host platform $(UNAME_S).)
|
||||
endif
|
||||
|
||||
# Everything is set above.
|
||||
$(info Building Tilde Friends $(VERSION_NUMBER) android=$(if $(HAVE_ANDROID),1,0) win=$(if $(HAVE_WIN),1,0) cross_aarch64=$(if $(HAVE_CROSS_AARCH64),1,0) cross_ios=$(if $(HAVE_LINUX_IOS),1,0) cross_macos=$(if $(HAVE_LINUX_MACOS),1,0) system_ssl=$(if $(USE_SYSTEM_SSL),1,0))
|
||||
$(info Building Tilde Friends $(VERSION_NUMBER) android=$(if $(HAVE_ANDROID),1,0) win=$(if $(HAVE_WIN),1,0) cross_aarch64=$(if $(HAVE_CROSS_AARCH64),1,0) cross_ios=$(if $(HAVE_LINUX_IOS),1,0) cross_macos=$(if $(HAVE_LINUX_MACOS),1,0))
|
||||
|
||||
CFLAGS += \
|
||||
-std=gnu11 \
|
||||
@@ -106,10 +103,10 @@ LDFLAGS += \
|
||||
-Wno-aggressive-loop-optimizations
|
||||
|
||||
ANDROID_MIN_SDK_VERSION := 24
|
||||
ANDROID_TARGET_SDK_VERSION := 34
|
||||
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0
|
||||
ANDROID_TARGET_SDK_VERSION := 35
|
||||
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/35.0.0
|
||||
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-$(ANDROID_TARGET_SDK_VERSION)
|
||||
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.3.11579264
|
||||
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/27.2.12479018
|
||||
|
||||
ANDROID_ARMV7A_TARGETS := \
|
||||
out/androiddebug-armv7a/tildefriends \
|
||||
@@ -253,7 +250,10 @@ $(ANDROID_TARGETS): CFLAGS += \
|
||||
-fno-asynchronous-unwind-tables \
|
||||
-funwind-tables \
|
||||
-Wno-unknown-warning-option
|
||||
$(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC
|
||||
$(ANDROID_TARGETS): LDFLAGS += \
|
||||
--sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
|
||||
-Wl,-z,max-page-size=16384 \
|
||||
-fPIC
|
||||
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
|
||||
$(DEBUG_TARGETS): LDFLAGS += -Og
|
||||
$(RELEASE_TARGETS): CFLAGS += \
|
||||
@@ -267,16 +267,12 @@ $(WINDOWS_TARGETS): AS = $(CC)
|
||||
$(WINDOWS_TARGETS): CFLAGS += \
|
||||
-D_WIN32_WINNT=0x0A00 \
|
||||
-DWINVER=0x0A00 \
|
||||
-DNTDDI_VERSION=NTDDI_WIN10 \
|
||||
-Iout/openssl/$(UNAME_S)/mingw64/usr/local/include
|
||||
-DNTDDI_VERSION=NTDDI_WIN10
|
||||
$(WINDOWS_TARGETS): LDFLAGS += \
|
||||
-static \
|
||||
-lm \
|
||||
-Lout/openssl/$(UNAME_S)/mingw64/usr/local/lib
|
||||
-lm
|
||||
$(AARCH64_TARGETS): CC = aarch64-linux-gnu-gcc
|
||||
$(AARCH64_TARGETS): AS = $(CC)
|
||||
$(AARCH64_TARGETS): CFLAGS += -Iout/openssl/Linux/aarch64/usr/local/include
|
||||
$(AARCH64_TARGETS): LDFLAGS += -Lout/openssl/Linux/aarch64/usr/local/lib
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
$(HOST_TARGETS): CC = xcrun clang
|
||||
$(IOS_TARGETS): IOS_SYSROOT := $(shell xcrun --sdk iphoneos --show-sdk-path)
|
||||
@@ -301,39 +297,12 @@ $(ANDROID_TARGETS): AS = $(CC)
|
||||
$(ANDROID_TARGETS): CFLAGS += \
|
||||
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \
|
||||
-Wno-unknown-warning-option
|
||||
$(ANDROID_ARMV7A_TARGETS): CFLAGS += -Iout/openssl/android/armeabi-v7a/usr/local/include
|
||||
$(ANDROID_ARMV7A_TARGETS): LDFLAGS += -Lout/openssl/android/armeabi-v7a/usr/local/lib
|
||||
$(ANDROID_ARM64_TARGETS): CFLAGS += -Iout/openssl/android/arm64-v8a/usr/local/include
|
||||
$(ANDROID_ARM64_TARGETS): LDFLAGS += -Lout/openssl/android/arm64-v8a/usr/local/lib
|
||||
$(ANDROID_X86_TARGETS): CFLAGS += -Iout/openssl/android/x86/usr/local/include
|
||||
$(ANDROID_X86_TARGETS): CFLAGS += -Wno-atomic-alignment
|
||||
$(ANDROID_X86_TARGETS): LDFLAGS += -Lout/openssl/android/x86/usr/local/lib
|
||||
$(ANDROID_X86_64_TARGETS): CFLAGS += -Iout/openssl/android/x86_64/usr/local/include
|
||||
$(ANDROID_X86_64_TARGETS): LDFLAGS += -Lout/openssl/android/x86_64/usr/local/lib
|
||||
$(NONMACOS_TARGETS): CFLAGS += -Wno-cast-function-type
|
||||
$(MACOS_TARGETS): LDFLAGS += -Wl,-dead_strip
|
||||
$(NONMACOS_TARGETS): LDFLAGS += -Wl,--gc-sections -Wl,--as-needed
|
||||
$(IOS_TARGETS): CFLAGS += -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)
|
||||
$(IOS_TARGETS): LDFLAGS += -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
$(IOS_TARGETS): CFLAGS += -Iout/openssl/ios/ios64-xcrun/usr/local/include
|
||||
$(IOS_TARGETS): LDFLAGS += -Lout/openssl/ios/ios64-xcrun/usr/local/lib
|
||||
else
|
||||
$(IOS_TARGETS): CFLAGS += -Iout/openssl/$(UNAME_S)/ios64-cross/usr/local/include
|
||||
$(IOS_TARGETS): LDFLAGS += -Lout/openssl/$(UNAME_S)/ios64-cross/usr/local/lib
|
||||
$(filter $(BUILD_DIR)/macosdebug-x86_64/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-x86_64/usr/local/include
|
||||
$(filter $(BUILD_DIR)/macosdebug-arm/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-arm/usr/local/include
|
||||
$(filter $(BUILD_DIR)/macosdebug-x86_64/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-x86_64/usr/local/lib
|
||||
$(filter $(BUILD_DIR)/macosdebug-arm/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-arm/usr/local/lib
|
||||
$(filter $(BUILD_DIR)/macosrelease-x86_64/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-x86_64/usr/local/include
|
||||
$(filter $(BUILD_DIR)/macosrelease-arm/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-arm/usr/local/include
|
||||
$(filter $(BUILD_DIR)/macosrelease-x86_64/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-x86_64/usr/local/lib
|
||||
$(filter $(BUILD_DIR)/macosrelease-arm/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-arm/usr/local/lib
|
||||
endif
|
||||
$(IOSSIM_TARGETS): CFLAGS += -Iout/openssl/ios/iossimulator-xcrun/usr/local/include
|
||||
$(IOSSIM_TARGETS): LDFLAGS += -Lout/openssl/ios/iossimulator-xcrun/usr/local/lib
|
||||
$(HOST_TARGETS): CFLAGS += -Iout/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/include
|
||||
$(HOST_TARGETS): LDFLAGS += -Lout/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/lib
|
||||
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
@@ -614,15 +583,16 @@ $(UV_OBJS): CFLAGS += \
|
||||
-Ideps/libuv/include \
|
||||
-Ideps/libuv/src \
|
||||
-Wno-dangling-pointer \
|
||||
-Wno-format-truncation \
|
||||
-Wno-incompatible-pointer-types \
|
||||
-Wno-maybe-uninitialized \
|
||||
-Wno-nonnull \
|
||||
-Wno-sign-compare \
|
||||
-Wno-unknown-attributes \
|
||||
-Wno-unused-but-set-parameter \
|
||||
-Wno-unused-but-set-variable \
|
||||
-Wno-unused-result \
|
||||
-Wno-unused-variable \
|
||||
-Wno-nonnull
|
||||
-Wno-unused-variable
|
||||
$(filter out/win%,$(UV_OBJS)): \
|
||||
CFLAGS += \
|
||||
-Wno-cast-function-type \
|
||||
@@ -649,6 +619,7 @@ SODIUM_SOURCES := \
|
||||
deps/libsodium/src/libsodium/crypto_core/hsalsa20/ref2/core_hsalsa20_ref2.c \
|
||||
deps/libsodium/src/libsodium/crypto_core/salsa/ref/core_salsa_ref.c \
|
||||
deps/libsodium/src/libsodium/crypto_core/softaes/softaes.c \
|
||||
deps/libsodium/src/libsodium/crypto_generichash/crypto_generichash.c \
|
||||
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-compress-ref.c \
|
||||
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c \
|
||||
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/generichash_blake2b.c \
|
||||
@@ -742,7 +713,7 @@ $(SQLITE_OBJS): CFLAGS += \
|
||||
|
||||
QUICKJS_SOURCES := \
|
||||
deps/quickjs/cutils.c \
|
||||
deps/quickjs/libbf.c \
|
||||
deps/quickjs/dtoa.c \
|
||||
deps/quickjs/libregexp.c \
|
||||
deps/quickjs/libunicode.c \
|
||||
deps/quickjs/quickjs.c
|
||||
@@ -819,9 +790,6 @@ $(MINIUNZIP_OBJS): CFLAGS += \
|
||||
LDFLAGS += \
|
||||
-pthread \
|
||||
-lm
|
||||
$(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS) $(AARCH64_TARGETS) $(filter-out $(HOST_TARGETS),$(MACOS_TARGETS)): LDFLAGS += \
|
||||
-lssl \
|
||||
-lcrypto
|
||||
ifneq ($(UNAME_S),Haiku)
|
||||
ifneq ($(UNAME_S),OpenBSD)
|
||||
$(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
||||
@@ -829,8 +797,6 @@ $(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
||||
endif
|
||||
endif
|
||||
$(WINDOWS_TARGETS): LDFLAGS += \
|
||||
-lssl \
|
||||
-lcrypto \
|
||||
-lcrypt32 \
|
||||
-ldbghelp \
|
||||
-liphlpapi \
|
||||
@@ -843,15 +809,15 @@ $(WINDOWS_TARGETS): LDFLAGS += \
|
||||
$(ANDROID_TARGETS): LDFLAGS += \
|
||||
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \
|
||||
-ldl \
|
||||
-llog \
|
||||
-lssl \
|
||||
-lcrypto
|
||||
-llog
|
||||
$(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): CFLAGS += \
|
||||
-Wno-unknown-warning-option
|
||||
$(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
||||
-framework Foundation \
|
||||
-framework CoreFoundation \
|
||||
-framework CoreSpotlight \
|
||||
-framework UIKit \
|
||||
-framework UniformTypeIdentifiers \
|
||||
-framework WebKit
|
||||
|
||||
##
|
||||
@@ -1015,7 +981,7 @@ $(BUNDLETOOL):
|
||||
@curl -q -L --create-dirs -o $@ $(BUNDLETOOL_URL)
|
||||
|
||||
out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGETS)) $(RAW_FILES) out/apk/res.apk src/android/AndroidManifest.xml $(BUNDLETOOL)
|
||||
@rm -rf out/aab/staging/
|
||||
@rm -rf out/aab/staging/ out/aab/base.zip
|
||||
@mkdir -p out/aab/staging
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 link --proto-format -o out/aab/temporary.apk \
|
||||
-I $(ANDROID_PLATFORM)/android.jar \
|
||||
@@ -1035,14 +1001,11 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
|
||||
@cp out/apk/classes.dex out/aab/staging/dex/
|
||||
@rm -fv out/base.zip
|
||||
@mkdir -p out/aab/staging/lib/arm64-v8a out/aab/staging/lib/armeabi-v7a out/aab/staging/lib/x86_64 out/aab/staging/lib/x86
|
||||
@cp out/androidrelease/tildefriends out/aab/staging/lib/arm64-v8a/libtildefriends.so
|
||||
@cp out/androidrelease-armv7a/tildefriends out/aab/staging/lib/armeabi-v7a/libtildefriends.so
|
||||
@cp out/androidrelease-x86_64/tildefriends out/aab/staging/lib/x86_64/libtildefriends.so
|
||||
@cp out/androidrelease-x86/tildefriends out/aab/staging/lib/x86/libtildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/arm64-v8a/libtildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/armeabi-v7a/libtildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86_64/libtildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86/libtildefriends.so
|
||||
@mkdir -p out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64 out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease/tildefriends -o out/aab/staging/lib/arm64-v8a/libtildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-armv7a/tildefriends -o out/aab/staging/lib/armeabi-v7a/libtildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86_64/tildefriends -o out/aab/staging/lib/x86_64/libtildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86/tildefriends -o out/aab/staging/lib/x86/libtildefriends.so
|
||||
@cp -r apps/ out/aab/staging/root/
|
||||
@rm -rf out/aab/staging/root/apps/welcome*
|
||||
@cp -r core/ out/aab/staging/root/
|
||||
@@ -1051,7 +1014,12 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
|
||||
@cp -r deps/codemirror/ out/aab/staging/root/deps/
|
||||
@cd out/aab/staging/; zip -r ../base.zip *; cd ../../../
|
||||
@java -jar $(BUNDLETOOL) build-bundle --overwrite --config=src/android/BundleConfig.json --modules=out/aab/base.zip --output=$@
|
||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) $@
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-armv7a/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86_64/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym
|
||||
@cd out/aab/staging; zip -u ../../../$@ BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym; cd ../../../
|
||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) --alignment-preserved $@
|
||||
|
||||
aab: out/TildeFriends.aab ## Build an Android App Bundle.
|
||||
.PHONY: aab
|
||||
@@ -1113,12 +1081,12 @@ out/apk/TildeFriends-%.fdroid.unsigned.apk:
|
||||
|
||||
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 --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --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 $@ --alignment-preserved $<
|
||||
|
||||
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
|
||||
@$(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 $@ --alignment-preserved $@.zopfli
|
||||
|
||||
release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-release.zopfli.apk ## Build an Android release APK.
|
||||
.PHONY: release-apk
|
||||
@@ -1136,6 +1104,11 @@ releaseapkgo: out/TildeFriends-arm-release.apk ## Build, install, and run a rele
|
||||
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
|
||||
.PHONY: releaseapkgo
|
||||
|
||||
x86releaseapkgo: out/TildeFriends-x86-release.apk ## Build, install, and run an x86 release Android APK.
|
||||
@adb install -r $<
|
||||
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
|
||||
.PHONY: x86releaseapkgo
|
||||
|
||||
apklog: ## Display Android log output.
|
||||
@adb logcat *:S tildefriends
|
||||
.PHONY: apklog
|
||||
@@ -1181,6 +1154,7 @@ out/tildefriends-%.ipa: out/tildefriends-ios%.app/tildefriends
|
||||
@echo "[ipa] $@"
|
||||
@rm -rf $@.tmp $@
|
||||
@mkdir -p $@.tmp/Payload/tildefriends.app/
|
||||
@cp src/ios/tildefriends512.png $@.tmp/iTunesArtwork
|
||||
@cp -R $(dir $<)/* $@.tmp/Payload/tildefriends.app/
|
||||
@cd $@.tmp/ && zip -u ../../$@ -q -9 -r ./
|
||||
@rm -rf $@.tmp/
|
||||
@@ -1208,98 +1182,11 @@ ios%go: out/tildefriends-ios%.app/tildefriends
|
||||
ideviceinstaller -i $(realpath $(dir $<))
|
||||
|
||||
iossimdebuggo: out/tildefriends-iossimdebug.app/tildefriends ## Build, install, and run an iOS debug build.
|
||||
xcrun -sdk iphoneos codesign -f -s 'Apple Distribution' --entitlements src/ios/Entitlements.plist --generate-entitlement-der out/tildefriends-iossimdebug.app
|
||||
xcrun simctl install booted out/tildefriends-iossimdebug.app/
|
||||
xcrun simctl launch booted com.unprompted.tildefriends
|
||||
xcrun simctl launch --console booted com.unprompted.tildefriends
|
||||
.PHONY: iossimdebuggo
|
||||
|
||||
ANDROID_DEPS := out/openssl/android/arm64-v8a/usr/local/lib/libssl.a
|
||||
$(ANDROID_DEPS):
|
||||
+@export ANDROID_NDK_ROOT=$(ANDROID_NDK)
|
||||
+@export BUILD_PLATFORM=android
|
||||
+@export TOOLCHAIN=$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64
|
||||
+@PATH="$$TOOLCHAIN/x86_64-linux-android/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=x86_64 SSL_TARGET=android-x86_64 OPTIONS="-D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local
|
||||
+@PATH="$$TOOLCHAIN/i686-linux-android/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=x86 SSL_TARGET=android-x86 OPTIONS="-D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local
|
||||
+@PATH="$$TOOLCHAIN/arm-linux-androideabi/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=armeabi-v7a SSL_TARGET=android-arm OPTIONS="--target=armv7a-linux-androideabi -Wl,--fix-cortex-a8 -D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local
|
||||
+@PATH="$$TOOLCHAIN/aarch64-linux-android/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=arm64-v8a SSL_TARGET=android-arm64 OPTIONS="-D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/android%,$(APP_OBJS)): | $(ANDROID_DEPS)
|
||||
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
ifneq ($(USE_SYSTEM_SSL),1)
|
||||
LOCAL_DEPS := out/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/lib/libssl.a
|
||||
$(LOCAL_DEPS):
|
||||
+@tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/debug/%,$(APP_OBJS)) $(filter $(BUILD_DIR)/release/%,$(APP_OBJS)): | $(LOCAL_DEPS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_CROSS_AARCH64),1)
|
||||
LOCAL_DEPS := out/openssl/$(UNAME_S)/aarch64/usr/local/lib/libssl.a
|
||||
$(LOCAL_DEPS):
|
||||
+@OPTIONS="--cross-compile-prefix=aarch64-linux-gnu-" BUILD_TARGET=aarch64 SSL_TARGET=linux-aarch64 tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/armdebug/%,$(APP_OBJS)) $(filter $(BUILD_DIR)/armrelease/%,$(APP_OBJS)): | $(LOCAL_DEPS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_LINUX_IOS),1)
|
||||
LOCAL_DEPS := out/openssl/$(UNAME_S)/ios64-cross/usr/local/lib/libssl.a
|
||||
$(LOCAL_DEPS):
|
||||
+@PATH=deps/ios_toolchain/target/bin:$$PATH \
|
||||
BUILD_TARGET=ios64-cross \
|
||||
SSL_TARGET=ios64-cross \
|
||||
CROSS_COMPILE=../../deps/ios_toolchain/target/bin/arm-apple-darwin11- \
|
||||
CROSS_TOP=../../deps/ios_toolchain/target \
|
||||
CROSS_SDK=iPhoneOS18.2.sdk \
|
||||
CC=clang \
|
||||
OPTIONS=-miphoneos-version-min=$(IPHONEOS_VERSION_MIN) \
|
||||
tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(LOCAL_DEPS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_LINUX_MACOS),1)
|
||||
LOCAL_DEPS := out/openssl/$(UNAME_S)/macos-arm/usr/local/lib/libssl.a
|
||||
$(LOCAL_DEPS):
|
||||
+@PATH=../../deps/macos_toolchain/bin:$$PATH \
|
||||
BUILD_TARGET=macos-arm \
|
||||
SSL_TARGET=darwin64-arm64 \
|
||||
CC=../../deps/macos_toolchain/bin/oa64-clang \
|
||||
RANLIB=../../deps/macos_toolchain/bin/x86_64-apple-darwin24-ranlib \
|
||||
AR=../../deps/macos_toolchain/bin/arm64-apple-darwin24-ar \
|
||||
tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/macosrelease-arm/% $(BUILD_DIR)/macosdebug-arm/%,$(APP_OBJS)): | $(LOCAL_DEPS)
|
||||
|
||||
LOCAL_DEPS := out/openssl/$(UNAME_S)/macos-x86_64/usr/local/lib/libssl.a
|
||||
$(LOCAL_DEPS):
|
||||
+@PATH=../../deps/macos_toolchain/bin:$$PATH \
|
||||
BUILD_TARGET=macos-x86_64 \
|
||||
SSL_TARGET=darwin64-x86_64 \
|
||||
CC=../../deps/macos_toolchain/bin/o64-clang \
|
||||
RANLIB=../../deps/macos_toolchain/bin/x86_64-apple-darwin24-ranlib \
|
||||
AR=../../deps/macos_toolchain/bin/x86_64-apple-darwin24-ar \
|
||||
tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/macosrelease-x86_64/% $(BUILD_DIR)/macosdebug-x86_64/%,$(APP_OBJS)): | $(LOCAL_DEPS)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
LOCAL_DEPS := out/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/lib/libssl.a
|
||||
$(LOCAL_DEPS):
|
||||
+@tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/debug/%,$(APP_OBJS)) $(filter $(BUILD_DIR)/release/%,$(APP_OBJS)): | $(LOCAL_DEPS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_WIN),1)
|
||||
WINDOWS_DEPS := out/openssl/$(UNAME_S)/mingw64/usr/local/lib/libssl.a
|
||||
$(WINDOWS_DEPS):
|
||||
+@BUILD_TARGET=mingw64 SSL_TARGET=mingw64 OPTIONS="--cross-compile-prefix=x86_64-w64-mingw32-" tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/win%,$(APP_OBJS)): | $(WINDOWS_DEPS)
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
IOS_DEPS := out/openssl/ios/ios64-xcrun/usr/local/lib/libssl.a
|
||||
$(IOS_DEPS):
|
||||
+@BUILD_PLATFORM=ios BUILD_TARGET=ios64-xcrun SSL_TARGET=ios64-xcrun OPTIONS="-fPIC -Wno-macro-redefined -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)" tools/ssl-local
|
||||
+@BUILD_PLATFORM=ios BUILD_TARGET=iossimulator-xcrun SSL_TARGET=iossimulator-xcrun OPTIONS="-fPIC -Wno-macro-redefined" tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(IOS_DEPS)
|
||||
endif
|
||||
|
||||
out/macos%/tildefriends: out/macos%-arm/tildefriends out/macos%-x86_64/tildefriends
|
||||
@echo [lipo] $@
|
||||
@mkdir -p $(@D)
|
||||
@@ -1480,6 +1367,18 @@ help: ## Display this help message.
|
||||
.PHONY: help
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
docs: debug
|
||||
docs: ## Build HTML docs.
|
||||
@echo '# CLI Usage\n' > docs/usage.md
|
||||
@echo "## tildefriends -h" >> docs/usage.md
|
||||
@echo '\n```' >> docs/usage.md
|
||||
@out/debug/tildefriends -h >> docs/usage.md
|
||||
@echo '```' >> docs/usage.md
|
||||
@for command in $$(out/debug/tildefriends -h | grep -Po '[A-Za-z_]*(?= - )'); do
|
||||
@ echo "\n## tildefriends $$command -h" >> docs/usage.md
|
||||
@ echo '\n```' >> docs/usage.md
|
||||
@ out/debug/tildefriends $$command -h >> docs/usage.md
|
||||
@ echo '```' >> docs/usage.md
|
||||
@done
|
||||
@doxygen
|
||||
.PHONY: docs
|
||||
|
||||
@@ -38,8 +38,6 @@ dependencies in the right places.
|
||||
|
||||
### Requirements
|
||||
|
||||
System OpenSSL libraries are assumed to be available on Haiku and OpenBSD.
|
||||
|
||||
On MacOS, Xcode's command-line tools are expected to be available.
|
||||
|
||||
### Build Commands
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@@ -108,10 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@@ -152,10 +150,11 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
@@ -170,24 +169,28 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
|
||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "📜",
|
||||
"previous": "&BEf0nraBdHk/+PWqx6tOSu5rheWVaxaL7orAOz3285M=.sha256"
|
||||
"previous": "&sJqeyYjHys6Z8IqqtZ2ij2ZC1E2xieu/FU/u2hE+O1U=.sha256"
|
||||
}
|
||||
|
||||
@@ -55,6 +55,9 @@ app.setDocument(`<head>
|
||||
</head>
|
||||
<body style="color:#fff">
|
||||
${markdown(docs.docs.global)}
|
||||
<!--
|
||||
${Object.keys(docs.docs).filter(x => [...treeify('', globalThis)].indexOf(x) == -1).map(x => `<p>STALE: ${x}</p>`).join('')}
|
||||
-->
|
||||
${[...treeify('', globalThis)].map(x => document(x)).join('\n')}
|
||||
<a id="Database"></a>
|
||||
${markdown(docs.docs.database)}
|
||||
|
||||
@@ -195,51 +195,6 @@ Call a function after some delay.
|
||||
* *Number* **timeout** Number of milliseconds to wait before calling the callback function.
|
||||
`;
|
||||
|
||||
docs['parseHttpRequest()'] = `
|
||||
Parses an HTTP request.
|
||||
### Parameters
|
||||
* *Uint8Array* **request** The request data. Maybe be partial or contain extra data. The return value will
|
||||
indicate when and where it is complete.
|
||||
* *Number* **last_length** The length of the data passed on a previous attempt for the same request, or 0 initially.
|
||||
### Returns
|
||||
* *Integer* **-2** if the request is incomplete.
|
||||
* *Integer* **-1** if the request could not be parsed.
|
||||
* *Object* An object with **bytes_parsed**, **minor_version**, **path**, and **headers** fields on successful parse.
|
||||
`;
|
||||
|
||||
docs['parseHttpResponse()'] = `
|
||||
Parses an HTTP response.
|
||||
### Parameters
|
||||
* *Uint8Array* **response** The response data. Maybe be partial or contain extra data. The return value will
|
||||
indicate when and where it is complete.
|
||||
* *Number* **last_length** The length of the data passed on a previous attempt for the same response, or 0 initially.
|
||||
### Returns
|
||||
* *Integer* **-2** if the response is incomplete.
|
||||
* *Integer* **-1** if the response could not be parsed.
|
||||
* *Object* An object with **bytes_parsed**, **minor_version**, **status**, **message**, and **headers** fields on successful parse.
|
||||
`;
|
||||
|
||||
docs['sha1Digest()'] = `
|
||||
Calculates a SHA1 digest.
|
||||
|
||||
Completes synchronously.
|
||||
### Parameters
|
||||
* *String* **value** The value for which to calculate the digest.
|
||||
### Returns
|
||||
*String* The SHA1 digest of UTF-8 encoded \`value\`.
|
||||
`;
|
||||
|
||||
docs['maskBytes()'] = `
|
||||
Masks bytes for WebSocket communication.
|
||||
|
||||
Completes synchronously.
|
||||
### Parameters
|
||||
* *Uint8Array* **bytes** The byte array of data to mask.
|
||||
* *Uint32* **mask** The mask to apply.
|
||||
### Returns
|
||||
*Uint32Array* The masked bytes.
|
||||
`;
|
||||
|
||||
docs['exit()'] = `
|
||||
Exits the app. But why would you want to do that?
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@@ -108,10 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@@ -152,10 +150,11 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
@@ -170,24 +169,28 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
|
||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
|
||||
8
apps/blog/lit-all.min.js
vendored
@@ -1,4 +1,4 @@
|
||||
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@@ -108,10 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@@ -152,10 +150,11 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
@@ -170,24 +169,28 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
|
||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
|
||||
5
apps/intro.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "💡",
|
||||
"previous": "&eN6DNPpQUNhGvxneLuLPgsOXR6qyFZ7u+MAz0b4fa7k=.sha256"
|
||||
}
|
||||
16
apps/intro/app.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import * as tfrpc from '/tfrpc.js';
|
||||
|
||||
async function main() {
|
||||
await app.setDocument(utf8Decode(getFile('index.html')));
|
||||
}
|
||||
|
||||
tfrpc.register(async function complete() {
|
||||
if (
|
||||
core.user?.credentials?.permissions?.administration &&
|
||||
(await core.globalSettingsGet('index')) == '/~core/intro/'
|
||||
) {
|
||||
return await core.globalSettingsSet('index', '/~core/ssb/');
|
||||
}
|
||||
});
|
||||
|
||||
main();
|
||||
286
apps/intro/index.html
Normal file
@@ -0,0 +1,286 @@
|
||||
<!doctype html>
|
||||
<html style="height: 100%; margin: 0; padding: 0; box-sizing: border-box">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" type="text/css" href="w3.css" />
|
||||
<style>
|
||||
.slide {
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.dot {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.w3-left,
|
||||
.w3-right {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body
|
||||
style="
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
"
|
||||
class="w3-flex w3-dark-gray w3-center"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
contain: content;
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
"
|
||||
>
|
||||
<div class="slide">
|
||||
<div
|
||||
class="w3-content w3-xlarge w3-card-4 w3-blue w3-panel w3-padding-32 w3-round-xlarge"
|
||||
style="margin: 32px"
|
||||
>
|
||||
<div>
|
||||
<div>Welcome to</div>
|
||||
<div>~😎 Tilde Friends.</div>
|
||||
</div>
|
||||
<footer>
|
||||
<button class="w3-button w3-yellow proceed">Next</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide w3-card-4 w3-gray" style="width: 90%">
|
||||
<header class="w3-container w3-blue w3-xlarge">
|
||||
<h1>This brief tutorial will introduce:</h1>
|
||||
</header>
|
||||
<ul class="w3-large w3-left-align">
|
||||
<li><b>Secure Scuttlebutt</b>, a decentralized social network.</li>
|
||||
<li>
|
||||
<b>Tilde Friends</b>, the application platform that you are using
|
||||
right now.
|
||||
</li>
|
||||
<li>
|
||||
<b>How to get started</b> if you want to get gossiping right away.
|
||||
</li>
|
||||
</ul>
|
||||
<footer class="w3-center w3-xlarge w3-padding">
|
||||
<button class="w3-button w3-yellow proceed">Onward</button>
|
||||
</footer>
|
||||
</div>
|
||||
<div class="slide w3-gray" style="width: 90%">
|
||||
<div class="w3-card-4 w3-xlarge">
|
||||
<header class="w3-container w3-blue">
|
||||
<h1>💻Secure Scuttlebutt in a Nutshell🦀</h1>
|
||||
</header>
|
||||
<div class="w3-container w3-large w3-left-align">
|
||||
<p>
|
||||
Secure Scuttlebutt is a social network whose technical operation
|
||||
attempts to mirror human social interaction.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
You can create your own account and post to your own feed on
|
||||
your own device. This is all <b>local</b> with no external
|
||||
communication. This puts you fully in control of your own words
|
||||
and actions.
|
||||
</li>
|
||||
<li>
|
||||
Before you can interact with others, you need to
|
||||
<b>connect over the network</b>, either directly to your friends
|
||||
(i.e., peer-to-peer between your phones on coffee shop Wi-Fi) or
|
||||
to 🚪<i>rooms</i> and 🍻<i>pubs</i> (hint: search the web for
|
||||
<i>#ssbroom</i>).
|
||||
</li>
|
||||
<li>
|
||||
Who you choose to <b>follow</b> determines what you see, with
|
||||
most people choosing to see messages from friends and friends of
|
||||
those friends. If you encounter content you'd rather not see,
|
||||
<b>block</b> the offending account to improve the experience for
|
||||
you and your followers.
|
||||
</li>
|
||||
<li>
|
||||
Your feed is an <b>immutable</b> log of your activity. Post with
|
||||
care, because like your words in real life, posts can't be taken
|
||||
back.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<footer class="w3-center w3-xlarge w3-padding">
|
||||
<a
|
||||
class="w3-button w3-light-gray"
|
||||
href="https://scuttlebutt.nz/"
|
||||
target="_blank"
|
||||
>See scuttlebutt.nz</a
|
||||
>
|
||||
<button class="w3-button w3-yellow proceed">Got It</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide w3-gray" style="width: 90%">
|
||||
<div class="w3-card-4 w3-xlarge">
|
||||
<header class="w3-container w3-blue w3-center">
|
||||
<h1>~😎 Let's Talk Tilde Friends ~😎</h1>
|
||||
</header>
|
||||
<div class="w3-container w3-large w3-left-align">
|
||||
<p>
|
||||
Tilde Friends is an application platform that is an application of
|
||||
its own.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
This intro is a Tilde Friends app. You can click <b>edit</b> at
|
||||
the top to look under the hood and make changes.
|
||||
</li>
|
||||
<li>
|
||||
It is already possible to make and share new applications using
|
||||
only Tilde Friends and Secure Scuttlebutt without having to set
|
||||
up development environments, configure web servers, register
|
||||
domain names, or pay for hosting services.
|
||||
</li>
|
||||
<li>
|
||||
But it's also set up so that you can't just break an app that
|
||||
everybody is using or do malicious things with personal content.
|
||||
There are <b>protections</b> in place like an operating system.
|
||||
The intent is also for it to be <b>safe</b> to run strange apps
|
||||
without worrying about adverse effects.
|
||||
</li>
|
||||
<li>
|
||||
But this is all a big 🚧work in progress🚧 and
|
||||
<b>experiment</b>. Let's see where it takes us.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<footer class="w3-center w3-xlarge w3-padding">
|
||||
<button class="w3-button w3-yellow proceed">Okay</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide w3-gray" style="width: 90%">
|
||||
<div class="w3-card-4 w3-xlarge">
|
||||
<header class="w3-container w3-blue w3-center">
|
||||
<h1>🦀Let's Get this Tilde Friends Party Started🎉</h1>
|
||||
</header>
|
||||
<div class="w3-container w3-large w3-left-align">
|
||||
<p>The button below will take you to the Secure Scuttlebutt app.</p>
|
||||
<ul>
|
||||
<li>
|
||||
Remember:
|
||||
<ol>
|
||||
<li>You are in charge. This is all on your device.</li>
|
||||
<li>
|
||||
Make network connections to exchange messages with others.
|
||||
</li>
|
||||
<li>
|
||||
Follow more accounts to see more content, and block those
|
||||
posting content you'd rather not see.
|
||||
</li>
|
||||
<li>
|
||||
Be respectful, and consider the consequences of what you
|
||||
post.
|
||||
</li>
|
||||
<li>
|
||||
This is all under active development. Exercise patience, and
|
||||
report issues encountered.
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
To see this tutorial again later, select <b>apps</b> ->
|
||||
<b>Core Apps</b> -> <b>intro</b>.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<footer class="w3-center w3-xlarge w3-padding">
|
||||
<button class="w3-button w3-yellow" id="complete">Let's Go!</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="w3-text-white w3-xlarge w3-center w3-flex"
|
||||
style="
|
||||
width: 100%;
|
||||
flex: 0 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
"
|
||||
>
|
||||
<div class="w3-jumbo" id="left" style="flex: 1 0; cursor: pointer">
|
||||
❮
|
||||
</div>
|
||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
||||
<div class="w3-jumbo" style="flex: 1 0; cursor: pointer" id="right">
|
||||
❯
|
||||
</div>
|
||||
</div>
|
||||
<script type="module">
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
|
||||
let index = 0;
|
||||
function set(i) {
|
||||
show(i - index);
|
||||
}
|
||||
function show(delta) {
|
||||
let slides = [...document.getElementsByClassName('slide')];
|
||||
let dots = [...document.getElementsByClassName('dot')];
|
||||
index = (index + delta + slides.length) % slides.length;
|
||||
for (let slide of slides) {
|
||||
slide.style.display =
|
||||
slides.indexOf(slide) == index ? 'block' : 'none';
|
||||
}
|
||||
for (let dot of dots) {
|
||||
if (dots.indexOf(dot) == index) {
|
||||
dot.classList.add('w3-white');
|
||||
} else {
|
||||
dot.classList.remove('w3-white');
|
||||
}
|
||||
}
|
||||
document.getElementById('left').style.visibility =
|
||||
index == 0 ? 'hidden' : 'visible';
|
||||
document.getElementById('right').style.visibility =
|
||||
index == slides.length - 1 ? 'hidden' : 'visible';
|
||||
}
|
||||
|
||||
let dots = [...document.getElementsByClassName('dot')];
|
||||
for (let dot of dots) {
|
||||
dot.onclick = () => set(dots.indexOf(dot));
|
||||
}
|
||||
for (let button of document.getElementsByClassName('proceed')) {
|
||||
button.onclick = () => show(1);
|
||||
}
|
||||
document.getElementById('left').onclick = () => show(-1);
|
||||
document.getElementById('right').onclick = () => show(1);
|
||||
document.getElementById('complete').onclick = function () {
|
||||
console.log('completing');
|
||||
tfrpc.rpc.complete().finally(function () {
|
||||
console.log('completed');
|
||||
let a = document.createElement('a');
|
||||
a.href = '/~core/ssb/';
|
||||
a.target = '_top';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
});
|
||||
};
|
||||
window.addEventListener('keyup', function (event) {
|
||||
if (event.key == 'ArrowLeft') {
|
||||
show(-1);
|
||||
} else if (event.key == 'ArrowRight') {
|
||||
show(1);
|
||||
}
|
||||
});
|
||||
show(0);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
251
apps/intro/w3.css
Normal file
@@ -0,0 +1,251 @@
|
||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
|
||||
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
|
||||
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
|
||||
a{background-color:transparent}a:active,a:hover{outline-width:0}
|
||||
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
|
||||
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
|
||||
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
|
||||
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
|
||||
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
|
||||
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
|
||||
button,input{overflow:visible}button,select{text-transform:none}
|
||||
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
|
||||
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
|
||||
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
|
||||
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
|
||||
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
|
||||
[type=checkbox],[type=radio]{padding:0}
|
||||
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
|
||||
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
|
||||
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
|
||||
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
|
||||
/* End extract */
|
||||
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
|
||||
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
|
||||
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
|
||||
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
|
||||
hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
|
||||
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
|
||||
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
|
||||
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
|
||||
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
|
||||
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
|
||||
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
|
||||
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
|
||||
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
|
||||
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
|
||||
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
|
||||
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
|
||||
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
|
||||
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
|
||||
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
|
||||
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
|
||||
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
|
||||
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
|
||||
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
|
||||
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
|
||||
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
|
||||
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
|
||||
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
|
||||
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
|
||||
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
|
||||
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
|
||||
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
|
||||
.w3-main,#main{transition:margin-left .4s}
|
||||
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
|
||||
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
|
||||
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
|
||||
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
|
||||
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
|
||||
.w3-bar .w3-button{white-space:normal}
|
||||
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
|
||||
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
|
||||
.w3-responsive{display:block;overflow-x:auto}
|
||||
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
|
||||
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
|
||||
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
|
||||
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
|
||||
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
|
||||
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
|
||||
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
|
||||
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
|
||||
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
|
||||
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
|
||||
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
|
||||
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
|
||||
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
|
||||
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
|
||||
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
|
||||
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
|
||||
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
|
||||
@media (max-width:1205px){.w3-auto{max-width:95%}}
|
||||
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
|
||||
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
|
||||
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
|
||||
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
|
||||
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
|
||||
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
|
||||
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
|
||||
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
|
||||
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
|
||||
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
|
||||
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
|
||||
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
|
||||
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
|
||||
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
|
||||
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
|
||||
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
||||
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
||||
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
|
||||
.w3-display-position{position:absolute}
|
||||
.w3-circle{border-radius:50%}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
|
||||
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
|
||||
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
|
||||
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
|
||||
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
|
||||
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
|
||||
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
|
||||
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
|
||||
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
|
||||
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
|
||||
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
|
||||
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
|
||||
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
|
||||
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
|
||||
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
|
||||
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
|
||||
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
|
||||
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
|
||||
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
|
||||
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
|
||||
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
|
||||
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
|
||||
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
|
||||
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
|
||||
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
|
||||
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
|
||||
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
|
||||
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
|
||||
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
|
||||
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
|
||||
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
|
||||
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
|
||||
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
|
||||
.w3-left{float:left!important}.w3-right{float:right!important}
|
||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
|
||||
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
|
||||
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
|
||||
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
|
||||
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
|
||||
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
|
||||
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
|
||||
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
|
||||
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
|
||||
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
|
||||
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
|
||||
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
|
||||
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
|
||||
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
|
||||
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
|
||||
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
|
||||
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
|
||||
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
|
||||
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
|
||||
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
|
||||
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
|
||||
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
|
||||
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
|
||||
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
|
||||
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
|
||||
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
|
||||
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
|
||||
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
|
||||
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
|
||||
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
|
||||
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
|
||||
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
|
||||
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
|
||||
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
|
||||
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
|
||||
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
|
||||
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
|
||||
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
|
||||
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
|
||||
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
|
||||
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
|
||||
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
|
||||
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
|
||||
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
|
||||
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
|
||||
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
|
||||
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
|
||||
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
|
||||
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
|
||||
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
|
||||
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
|
||||
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
|
||||
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
|
||||
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
|
||||
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
|
||||
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
|
||||
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
|
||||
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
|
||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
8
apps/issues/lit-all.min.js
vendored
8
apps/journal/lit-all.min.js
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🚪",
|
||||
"previous": "&HXCdDG8gGYXElTyEFbg85jqa6lDXNL2ENPIA9UoJNbI=.sha256"
|
||||
"previous": "&DJwkqNfYWtW9yBtJQMseEXm7l04Enpi+yAxZulLq9Vk=.sha256"
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ async function main() {
|
||||
print(core.url);
|
||||
let host = core.url.match(/.*?\/\/([^:/]*)/)[1];
|
||||
let port = await ssb.port();
|
||||
let id = (await ssb.getServerIdentity()).substring(1);
|
||||
let room = `net:${host}:${port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
|
||||
let id = (await ssb.getServerIdentity()).substring(1).split('.')[0];
|
||||
let room = `net:${host}:${port}~shs:${id}:SSB+Room+PSK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
|
||||
await app.setDocument(`
|
||||
<body style="color: #fff">
|
||||
<h1>Server</h1>
|
||||
|
||||
8
apps/sneaker/lit-all.min.js
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🦀",
|
||||
"previous": "&Zv/eOewtUPxYuALmYV8v+JDKwH4+aN8zCTYFwB7oYEw=.sha256"
|
||||
"previous": "&Tozi8g5lPpLW7znc+jm7xDZxoTq1G3DJAh2YhG+FRAQ=.sha256"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as tfrpc from '/tfrpc.js';
|
||||
|
||||
let g_database;
|
||||
let g_hash;
|
||||
let g_sql_cache = {};
|
||||
|
||||
tfrpc.register(async function localStorageGet(key) {
|
||||
return app.localStorageGet(key);
|
||||
@@ -51,11 +52,38 @@ tfrpc.register(async function connect(token) {
|
||||
tfrpc.register(async function closeConnection(id) {
|
||||
await ssb.closeConnection(id);
|
||||
});
|
||||
tfrpc.register(async function query(sql, args) {
|
||||
tfrpc.register(async function query(sql, args, options) {
|
||||
let start = new Date();
|
||||
let result = [];
|
||||
await ssb.sqlAsync(sql, args, function callback(row) {
|
||||
result.push(row);
|
||||
});
|
||||
let key = options?.cacheable ? JSON.stringify([sql, args]) : undefined;
|
||||
let entry = key ? g_sql_cache[key] : undefined;
|
||||
const k_ideal_count = 64;
|
||||
if (entry) {
|
||||
result = entry.result;
|
||||
} else {
|
||||
await ssb.sqlAsync(sql, args, function callback(row) {
|
||||
result.push(row);
|
||||
});
|
||||
if (key) {
|
||||
g_sql_cache[key] = {
|
||||
result: result,
|
||||
time: new Date().valueOf(),
|
||||
};
|
||||
if (Object.keys(g_sql_cache).length > k_ideal_count * 2) {
|
||||
let aged = Object.entries(g_sql_cache).map(([k, v]) => [v.time, k]);
|
||||
aged.sort();
|
||||
for (let i = 0; i < aged.length / 2; i++) {
|
||||
delete g_sql_cache[aged[i][1]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let end = new Date();
|
||||
print(
|
||||
(end - start) / 1000,
|
||||
entry ? 'from cache' : 'from db',
|
||||
sql.replaceAll(/\s+/g, ' ').trim()
|
||||
);
|
||||
return result;
|
||||
});
|
||||
tfrpc.register(async function appendMessage(id, message) {
|
||||
@@ -76,6 +104,9 @@ tfrpc.register(function setHash(hash) {
|
||||
core.register('onMessage', async function (id) {
|
||||
await tfrpc.rpc.notifyNewMessage(id);
|
||||
});
|
||||
core.register('onBlob', async function (id) {
|
||||
await tfrpc.rpc.notifyNewBlob(id);
|
||||
});
|
||||
tfrpc.register(async function store_blob(blob) {
|
||||
if (Array.isArray(blob)) {
|
||||
blob = Uint8Array.from(blob);
|
||||
@@ -106,6 +137,15 @@ tfrpc.register(async function sync() {
|
||||
tfrpc.register(async function url() {
|
||||
return core.url;
|
||||
});
|
||||
tfrpc.register(async function globalSettingsGet(key) {
|
||||
return core.globalSettingsGet(key);
|
||||
});
|
||||
tfrpc.register(async function globalSettingsSet(key, value) {
|
||||
return core.globalSettingsSet(key, value);
|
||||
});
|
||||
tfrpc.register(function isAdministrator() {
|
||||
return core.user?.credentials?.permissions?.administration;
|
||||
});
|
||||
|
||||
core.register('onBroadcastsChanged', async function () {
|
||||
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {html, render} from './lit-all.min.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
let g_emojis;
|
||||
|
||||
@@ -14,23 +14,8 @@ function get_emojis() {
|
||||
});
|
||||
}
|
||||
|
||||
async function get_recent(author) {
|
||||
let recent = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT DISTINCT content ->> '$.vote.expression' AS value
|
||||
FROM messages
|
||||
WHERE author = ? AND
|
||||
content ->> '$.type' = 'vote'
|
||||
ORDER BY timestamp DESC LIMIT 10
|
||||
`,
|
||||
[author]
|
||||
);
|
||||
return recent.map((x) => x.value);
|
||||
}
|
||||
|
||||
export async function picker(callback, anchor, author) {
|
||||
export async function picker(callback, anchor, author, recent) {
|
||||
let json = await get_emojis();
|
||||
let recent = await get_recent(author);
|
||||
|
||||
let div = document.createElement('div');
|
||||
div.id = 'emoji_picker';
|
||||
@@ -155,6 +140,9 @@ export async function picker(callback, anchor, author) {
|
||||
<style>
|
||||
${styles}
|
||||
</style>
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<div
|
||||
class="w3-modal"
|
||||
style="display: block; box-sizing: border-box; z-index: 10"
|
||||
|
||||
8
apps/ssb/lit-all.min.js
vendored
@@ -12,12 +12,14 @@ import * as tf_tab_news from './tf-tab-news.js';
|
||||
import * as tf_tab_news_feed from './tf-tab-news-feed.js';
|
||||
import * as tf_tab_search from './tf-tab-search.js';
|
||||
import * as tf_tab_connections from './tf-tab-connections.js';
|
||||
import * as tf_tab_query from './tf-tab-query.js';
|
||||
import * as tf_tag from './tf-tag.js';
|
||||
import * as tf_styles from './tf-styles.js';
|
||||
|
||||
window.addEventListener('load', function () {
|
||||
let style = document.createElement('style');
|
||||
style.innerText = tf_styles.styles;
|
||||
Promise.resolve(tf_styles.generate_theme()).then(function (x) {
|
||||
style.innerText += x;
|
||||
});
|
||||
document.body.appendChild(style);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {LitElement, html, css, guard, until} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -11,6 +11,7 @@ class TfElement extends LitElement {
|
||||
broadcasts: {type: Array},
|
||||
connections: {type: Array},
|
||||
loading: {type: Boolean},
|
||||
loading_about: {type: Number},
|
||||
loaded: {type: Boolean},
|
||||
following: {type: Array},
|
||||
users: {type: Object},
|
||||
@@ -20,7 +21,13 @@ class TfElement extends LitElement {
|
||||
channels_latest: {type: Object},
|
||||
guest: {type: Boolean},
|
||||
url: {type: String},
|
||||
private_closed: {type: Object},
|
||||
private_messages: {type: Array},
|
||||
grouped_private_messages: {type: Object},
|
||||
recent_reactions: {type: Array},
|
||||
is_administrator: {type: Boolean},
|
||||
stay_connected: {type: Boolean},
|
||||
progress: {type: Number},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -36,11 +43,14 @@ class TfElement extends LitElement {
|
||||
this.following = [];
|
||||
this.users = {};
|
||||
this.loaded = false;
|
||||
this.loading_about = 0;
|
||||
this.channels = [];
|
||||
this.channels_unread = {};
|
||||
this.channels_latest = {};
|
||||
this.loading_latest = 0;
|
||||
this.loading_latest_scheduled = 0;
|
||||
this.recent_reactions = [];
|
||||
this.private_closed = {};
|
||||
tfrpc.rpc.getBroadcasts().then((b) => {
|
||||
self.broadcasts = b || [];
|
||||
});
|
||||
@@ -50,10 +60,22 @@ class TfElement extends LitElement {
|
||||
tfrpc.rpc.getHash().then((hash) => self.set_hash(hash));
|
||||
tfrpc.register(function hashChanged(hash) {
|
||||
self.set_hash(hash);
|
||||
self.reset_progress();
|
||||
});
|
||||
tfrpc.register(async function notifyNewMessage(id) {
|
||||
await self.fetch_new_message(id);
|
||||
});
|
||||
tfrpc.register(async function notifyNewBlob(id) {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('blob-stored', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
id: id,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
tfrpc.register(function set(name, value) {
|
||||
if (name === 'broadcasts') {
|
||||
self.broadcasts = value;
|
||||
@@ -69,13 +91,30 @@ class TfElement extends LitElement {
|
||||
async initial_load() {
|
||||
let whoami = await tfrpc.rpc.getActiveIdentity();
|
||||
let ids = (await tfrpc.rpc.getIdentities()) || [];
|
||||
this.is_administrator = await tfrpc.rpc.isAdministrator();
|
||||
this.stay_connected =
|
||||
this.is_administrator &&
|
||||
(await tfrpc.rpc.globalSettingsGet('stay_connected'));
|
||||
this.url = await tfrpc.rpc.url();
|
||||
this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
|
||||
this.guest = !this.whoami?.length;
|
||||
this.ids = ids;
|
||||
let private_closed =
|
||||
(await tfrpc.rpc.databaseGet('private_closed')) ?? '{}';
|
||||
this.private_closed = JSON.parse(private_closed);
|
||||
await this.load_channels();
|
||||
}
|
||||
|
||||
async close_private_chat(event) {
|
||||
let update = {};
|
||||
update[event.detail.key] = true;
|
||||
this.private_closed = Object.assign(update, this.private_closed);
|
||||
await tfrpc.rpc.databaseSet(
|
||||
'private_closed',
|
||||
JSON.stringify(this.private_closed)
|
||||
);
|
||||
}
|
||||
|
||||
async load_channels() {
|
||||
let channels = await tfrpc.rpc.query(
|
||||
`
|
||||
@@ -123,8 +162,34 @@ class TfElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
visible_private() {
|
||||
if (!this.grouped_private_messages || !this.private_closed) {
|
||||
return [];
|
||||
}
|
||||
let self = this;
|
||||
return Object.fromEntries(
|
||||
Object.entries(this.grouped_private_messages).filter(([key, value]) => {
|
||||
let channel = '🔐' + [...new Set(JSON.parse(key))].sort().join(',');
|
||||
let grouped_latest = Math.max(...value.map((x) => x.rowid));
|
||||
return (
|
||||
!self.private_closed[key] ||
|
||||
self.channels_unread[channel] === undefined ||
|
||||
grouped_latest > self.channels_unread[channel]
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
next_channel(delta) {
|
||||
let channel_names = ['', '@', '🔐', ...this.channels.map((x) => '#' + x)];
|
||||
let channel_names = [
|
||||
'',
|
||||
'@',
|
||||
'👍',
|
||||
...Object.keys(this.visible_private())
|
||||
.sort()
|
||||
.map((x) => '🔐' + JSON.parse(x).join(',')),
|
||||
...this.channels.map((x) => '#' + x),
|
||||
];
|
||||
let index = channel_names.indexOf(this.hash.substring(1));
|
||||
index = index != -1 ? index + delta : 0;
|
||||
tfrpc.rpc.setHash(
|
||||
@@ -141,16 +206,15 @@ class TfElement extends LitElement {
|
||||
this.tab = 'search';
|
||||
} else if (this.hash === '#connections') {
|
||||
this.tab = 'connections';
|
||||
} else if (this.hash.startsWith('#sql=')) {
|
||||
this.tab = 'query';
|
||||
} else {
|
||||
this.tab = 'news';
|
||||
}
|
||||
}
|
||||
|
||||
async fetch_about(following, users) {
|
||||
this.loading_about++;
|
||||
let ids = Object.keys(following).sort();
|
||||
const k_cache_version = 1;
|
||||
const k_cache_version = 3;
|
||||
let cache = await tfrpc.rpc.databaseGet('about');
|
||||
let original_cache = cache;
|
||||
cache = cache ? JSON.parse(cache) : {};
|
||||
@@ -158,116 +222,86 @@ class TfElement extends LitElement {
|
||||
cache = {
|
||||
version: k_cache_version,
|
||||
about: {},
|
||||
last_row_id: 0,
|
||||
};
|
||||
}
|
||||
let max_row_id = (
|
||||
await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT MAX(rowid) AS max_row_id FROM messages
|
||||
`,
|
||||
[]
|
||||
)
|
||||
)[0].max_row_id;
|
||||
|
||||
let ids_out_of_date = ids.filter(
|
||||
(x) =>
|
||||
(users[x]?.seq && !cache.about[x]?.seq) ||
|
||||
(users[x]?.seq && users[x]?.seq > cache.about[x].seq)
|
||||
);
|
||||
|
||||
for (let id of Object.keys(cache.about)) {
|
||||
if (ids.indexOf(id) == -1) {
|
||||
delete cache.about[id];
|
||||
} else {
|
||||
users[id] = Object.assign(cache.about[id], users[id] || {});
|
||||
}
|
||||
}
|
||||
|
||||
const k_chunk_size = 1024;
|
||||
let min_row_id = 0;
|
||||
console.log(
|
||||
'loading about for',
|
||||
ids.length,
|
||||
'accounts',
|
||||
cache.last_row_id,
|
||||
'=>',
|
||||
max_row_id
|
||||
ids_out_of_date.length,
|
||||
'out of date'
|
||||
);
|
||||
try {
|
||||
while (true) {
|
||||
let abouts = await tfrpc.rpc.query(
|
||||
if (ids_out_of_date.length) {
|
||||
try {
|
||||
let rows = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT * FROM (
|
||||
SELECT all_abouts.author, json(json_group_object(all_abouts.key, all_abouts.value)) AS about
|
||||
FROM (
|
||||
SELECT
|
||||
messages.rowid AS rowid, messages.author, json(messages.content) AS content, messages.sequence
|
||||
FROM
|
||||
messages,
|
||||
json_each(?1) AS following
|
||||
messages.author,
|
||||
fields.key,
|
||||
RANK() OVER (PARTITION BY messages.author, fields.key ORDER BY messages.sequence DESC) AS rank,
|
||||
fields.value
|
||||
FROM messages JOIN json_each(messages.content) AS fields
|
||||
WHERE
|
||||
messages.author = following.value AND
|
||||
messages.content ->> 'type' = 'about' AND
|
||||
messages.rowid > ?3 AND
|
||||
messages.rowid <= ?4
|
||||
UNION
|
||||
SELECT
|
||||
messages.rowid AS rowid, messages.author, json(messages.content) AS content, messages.sequence
|
||||
FROM
|
||||
messages,
|
||||
json_each(?2) AS following
|
||||
WHERE
|
||||
messages.author = following.value AND
|
||||
messages.content ->> 'type' = 'about' AND
|
||||
messages.rowid > ?6 AND
|
||||
messages.rowid <= ?4
|
||||
)
|
||||
ORDER BY rowid LIMIT ?5
|
||||
messages.content ->> '$.type' = 'about' AND
|
||||
messages.content ->> '$.about' = messages.author AND
|
||||
NOT fields.key IN ('about', 'type')) all_abouts
|
||||
JOIN json_each(?) AS following ON all_abouts.author = following.value
|
||||
WHERE rank = 1
|
||||
GROUP BY all_abouts.author
|
||||
`,
|
||||
[
|
||||
JSON.stringify(ids.filter((id) => cache.about[id])),
|
||||
JSON.stringify(ids.filter((id) => !cache.about[id])),
|
||||
cache.last_row_id,
|
||||
max_row_id,
|
||||
k_chunk_size,
|
||||
min_row_id,
|
||||
]
|
||||
[JSON.stringify(ids_out_of_date)]
|
||||
);
|
||||
let max_seen;
|
||||
for (let about of abouts) {
|
||||
let content = JSON.parse(about.content);
|
||||
if (content.about === about.author) {
|
||||
delete content.type;
|
||||
delete content.about;
|
||||
cache.about[about.author] = Object.assign(
|
||||
cache.about[about.author] || {},
|
||||
content
|
||||
);
|
||||
}
|
||||
max_seen = about.rowid;
|
||||
}
|
||||
console.log(
|
||||
'cache =',
|
||||
cache.last_row_id,
|
||||
'seen =',
|
||||
max_seen,
|
||||
'max =',
|
||||
max_row_id
|
||||
);
|
||||
cache.last_row_id = Math.max(cache.last_row_id, max_seen ?? max_row_id);
|
||||
min_row_id = Math.max(min_row_id, max_seen ?? max_row_id);
|
||||
let new_cache = JSON.stringify(cache);
|
||||
if (new_cache !== original_cache) {
|
||||
let start_time = new Date();
|
||||
tfrpc.rpc.databaseSet('about', new_cache).then(function () {
|
||||
console.log('saving about took', (new Date() - start_time) / 1000);
|
||||
});
|
||||
}
|
||||
users = users || {};
|
||||
for (let id of Object.keys(cache.about)) {
|
||||
users[id] = Object.assign(
|
||||
{follow_depth: following[id]?.d},
|
||||
users[id] || {},
|
||||
cache.about[id]
|
||||
for (let row of rows) {
|
||||
users[row.author] = Object.assign(
|
||||
users[row.author] || {},
|
||||
JSON.parse(row.about)
|
||||
);
|
||||
cache.about[row.author] = Object.assign(
|
||||
{seq: users[row.author].seq},
|
||||
JSON.parse(row.about)
|
||||
);
|
||||
}
|
||||
if (cache.last_row_id >= max_row_id) {
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
for (let id of ids_out_of_date) {
|
||||
if (!cache.about[id]?.seq) {
|
||||
cache.about[id] = Object.assign(cache.about[id] ?? {}, {
|
||||
seq: users[id]?.seq ?? 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.loading_about--;
|
||||
|
||||
let new_cache = JSON.stringify(cache);
|
||||
if (new_cache != original_cache) {
|
||||
let start_time = new Date();
|
||||
tfrpc.rpc.databaseSet('about', new_cache).then(function () {
|
||||
console.log('saving about took', (new Date() - start_time) / 1000);
|
||||
});
|
||||
}
|
||||
|
||||
return Object.assign({}, users);
|
||||
}
|
||||
|
||||
@@ -327,11 +361,7 @@ class TfElement extends LitElement {
|
||||
ranges.push([i, Math.min(i + k_chunk_size, latest), true]);
|
||||
}
|
||||
for (let i = cache.range[0]; i >= 0; i -= k_chunk_size) {
|
||||
ranges.push([
|
||||
Math.max(i - k_chunk_size, 0),
|
||||
Math.min(cache.range[0], i + k_chunk_size),
|
||||
false,
|
||||
]);
|
||||
ranges.push([Math.max(i - k_chunk_size, 0), i, false]);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < latest; i += k_chunk_size) {
|
||||
@@ -347,7 +377,7 @@ class TfElement extends LitElement {
|
||||
messages.rowid > ?1 AND
|
||||
messages.rowid <= ?2 AND
|
||||
json(messages.content) LIKE '"%'
|
||||
ORDER BY sequence DESC
|
||||
ORDER BY messages.rowid DESC
|
||||
`,
|
||||
[range[0], range[1]]
|
||||
);
|
||||
@@ -373,51 +403,116 @@ class TfElement extends LitElement {
|
||||
return [cache.latest, cache.messages];
|
||||
}
|
||||
|
||||
async group_private_messages(messages) {
|
||||
let groups = {};
|
||||
let result = await this.decrypt(
|
||||
await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT messages.rowid, messages.id, author, timestamp, json(content) AS content
|
||||
FROM messages
|
||||
JOIN json_each(?) AS ids
|
||||
WHERE messages.id = ids.value
|
||||
ORDER BY timestamp DESC
|
||||
`,
|
||||
[JSON.stringify(messages)]
|
||||
)
|
||||
);
|
||||
for (let message of result) {
|
||||
let key = JSON.stringify(
|
||||
[
|
||||
...new Set(
|
||||
message?.decrypted?.recps?.filter((x) => x != this.whoami)
|
||||
),
|
||||
].sort() ?? []
|
||||
);
|
||||
if (!groups[key]) {
|
||||
groups[key] = [];
|
||||
}
|
||||
groups[key].push(message);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
async load_channels_latest(following) {
|
||||
let start_time = new Date();
|
||||
let latest_private = this.get_latest_private(following);
|
||||
let channels = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
|
||||
JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value
|
||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||
WHERE
|
||||
messages.content ->> 'type' = 'post' AND
|
||||
messages.content ->> 'root' IS NULL AND
|
||||
messages.author != ?4
|
||||
GROUP by channel
|
||||
UNION
|
||||
SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages
|
||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||
WHERE
|
||||
messages.content ->> 'type' = 'post' AND
|
||||
messages.content ->> 'root' IS NULL AND
|
||||
messages.author != ?4
|
||||
UNION
|
||||
SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3)
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||
WHERE messages.author != ?4
|
||||
`,
|
||||
[
|
||||
JSON.stringify(this.channels),
|
||||
JSON.stringify(following),
|
||||
'"' + this.whoami.replace('"', '""') + '"',
|
||||
this.whoami,
|
||||
]
|
||||
);
|
||||
this.channels_latest = Object.fromEntries(
|
||||
channels.map((x) => [x.channel, x.rowid])
|
||||
);
|
||||
const k_args = [
|
||||
JSON.stringify(this.channels),
|
||||
JSON.stringify(following),
|
||||
'"' + this.whoami.replace('"', '""') + '"',
|
||||
this.whoami,
|
||||
];
|
||||
let channels = (
|
||||
await Promise.all([
|
||||
tfrpc.rpc.query(
|
||||
`
|
||||
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
|
||||
JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value
|
||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||
WHERE
|
||||
messages.content ->> 'type' = 'post' AND
|
||||
messages.content ->> 'root' IS NULL AND
|
||||
messages.author != ?4
|
||||
GROUP by channel
|
||||
`,
|
||||
k_args
|
||||
),
|
||||
tfrpc.rpc.query(
|
||||
`
|
||||
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
|
||||
JOIN messages_refs ON messages.id = messages_refs.message
|
||||
JOIN json_each(?1) AS channels ON messages_refs.ref = '#' || channels.value
|
||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||
WHERE
|
||||
messages.content ->> 'type' = 'post' AND
|
||||
messages.content ->> 'root' IS NULL AND
|
||||
messages.author != ?4
|
||||
GROUP by channel
|
||||
`,
|
||||
k_args
|
||||
),
|
||||
tfrpc.rpc.query(
|
||||
`
|
||||
SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages
|
||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||
WHERE
|
||||
messages.content ->> 'type' = 'post' AND
|
||||
messages.content ->> 'root' IS NULL AND
|
||||
messages.author != ?4
|
||||
`,
|
||||
k_args
|
||||
),
|
||||
tfrpc.rpc.query(
|
||||
`
|
||||
SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3)
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||
WHERE messages.author != ?4
|
||||
`,
|
||||
k_args
|
||||
),
|
||||
])
|
||||
).flat();
|
||||
let latest = {'🔐': undefined};
|
||||
for (let row of channels) {
|
||||
if (!latest[row.channel]) {
|
||||
latest[row.channel] = row.rowid;
|
||||
} else {
|
||||
latest[row.channel] = Math.max(row.rowid, latest[row.channel]);
|
||||
}
|
||||
}
|
||||
this.channels_latest = latest;
|
||||
console.log('channels took', (new Date() - start_time) / 1000.0);
|
||||
let self = this;
|
||||
start_time = new Date();
|
||||
latest_private.then(function (latest) {
|
||||
latest_private.then(async function (latest) {
|
||||
let grouped = await self.group_private_messages(latest[1]);
|
||||
self.channels_latest = Object.assign({}, self.channels_latest, {
|
||||
'🔐': latest[0],
|
||||
});
|
||||
console.log('private took', (new Date() - start_time) / 1000.0);
|
||||
self.private_messages = latest[1];
|
||||
self.grouped_private_messages = grouped;
|
||||
console.log('private took', (new Date() - start_time) / 1000.0);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -426,7 +521,28 @@ class TfElement extends LitElement {
|
||||
this.schedule_load_latest();
|
||||
}
|
||||
|
||||
reset_progress() {
|
||||
if (this.progress === undefined) {
|
||||
this._progress_start = new Date();
|
||||
requestAnimationFrame(this.update_progress.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
update_progress() {
|
||||
if (
|
||||
!this.loading_latest &&
|
||||
!this.loading_latest_scheduled &&
|
||||
!this.shadowRoot.getElementById('tf-tab-news')?.is_loading()
|
||||
) {
|
||||
this.progress = undefined;
|
||||
return;
|
||||
}
|
||||
this.progress = (new Date() - this._progress_start).valueOf();
|
||||
requestAnimationFrame(this.update_progress.bind(this));
|
||||
}
|
||||
|
||||
schedule_load_latest() {
|
||||
this.reset_progress();
|
||||
if (!this.loading_latest) {
|
||||
this.shadowRoot.getElementById('tf-tab-news')?.load_latest();
|
||||
this.load();
|
||||
@@ -446,43 +562,59 @@ class TfElement extends LitElement {
|
||||
[JSON.stringify(Object.keys(users))]
|
||||
);
|
||||
for (let row of info) {
|
||||
users[row.author].seq = row.max_seq;
|
||||
users[row.author].ts = row.max_ts;
|
||||
users[row.author] = Object.assign(users[row.author], {
|
||||
seq: row.max_sequence,
|
||||
ts: row.max_ts,
|
||||
});
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
async load_recent_reactions() {
|
||||
this.recent_reactions = (
|
||||
await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT DISTINCT content ->> '$.vote.expression' AS value
|
||||
FROM messages
|
||||
WHERE author = ? AND
|
||||
content ->> '$.type' = 'vote'
|
||||
ORDER BY timestamp DESC LIMIT 10
|
||||
`,
|
||||
[this.whoami]
|
||||
)
|
||||
).map((x) => x.value);
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.loading_latest = true;
|
||||
this.reset_progress();
|
||||
try {
|
||||
let start_time = new Date();
|
||||
let whoami = this.whoami;
|
||||
let following = await tfrpc.rpc.following([whoami], 2);
|
||||
let old_users = this.users ?? {};
|
||||
let users = {};
|
||||
let by_count = [];
|
||||
for (let [id, v] of Object.entries(following)) {
|
||||
users[id] = {
|
||||
following: v.of,
|
||||
blocking: v.ob,
|
||||
followed: v.if,
|
||||
blocked: v.ib,
|
||||
};
|
||||
users[id] = Object.assign(
|
||||
{
|
||||
following: v.of,
|
||||
blocking: v.ob,
|
||||
followed: v.if,
|
||||
blocked: v.ib,
|
||||
follow_depth: following[id]?.d,
|
||||
},
|
||||
old_users[id]
|
||||
);
|
||||
by_count.push({count: v.of, id: id});
|
||||
}
|
||||
let reactions = this.load_recent_reactions();
|
||||
this.load_channels_latest(Object.keys(following));
|
||||
this.channels_unread = JSON.parse(
|
||||
(await tfrpc.rpc.databaseGet('unread')) ?? '{}'
|
||||
);
|
||||
this.following = Object.keys(following);
|
||||
let about_start_time = new Date();
|
||||
users = await this.fetch_about(following, users);
|
||||
console.log(
|
||||
'about took',
|
||||
(new Date() - about_start_time) / 1000.0,
|
||||
'seconds for',
|
||||
Object.keys(users).length,
|
||||
'users'
|
||||
);
|
||||
start_time = new Date();
|
||||
users = await this.fetch_user_info(users);
|
||||
console.log(
|
||||
@@ -491,9 +623,22 @@ class TfElement extends LitElement {
|
||||
'seconds'
|
||||
);
|
||||
this.users = users;
|
||||
|
||||
let self = this;
|
||||
this.fetch_about(following, users).then(function (result) {
|
||||
self.users = result;
|
||||
console.log(
|
||||
'about took',
|
||||
(new Date() - about_start_time) / 1000.0,
|
||||
'seconds for',
|
||||
Object.keys(users).length,
|
||||
'users'
|
||||
);
|
||||
});
|
||||
console.log(
|
||||
`load finished ${whoami} => ${this.whoami} in ${(new Date() - start_time) / 1000}`
|
||||
);
|
||||
await reactions;
|
||||
this.whoami = whoami;
|
||||
this.loaded = whoami;
|
||||
} finally {
|
||||
@@ -544,13 +689,22 @@ class TfElement extends LitElement {
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
hash=${this.hash}
|
||||
?loading=${this.loading}
|
||||
?loading=${this.loading || this.loading_about != 0}
|
||||
.channels=${this.channels}
|
||||
.channels_latest=${this.channels_latest}
|
||||
.channels_unread=${this.channels_unread}
|
||||
@channelsetunread=${this.channel_set_unread}
|
||||
@refresh=${this.refresh}
|
||||
@toggle_stay_connected=${this.toggle_stay_connected}
|
||||
@loadmessages=${this.reset_progress}
|
||||
@closeprivatechat=${this.close_private_chat}
|
||||
.connections=${this.connections}
|
||||
.private_messages=${this.private_messages}
|
||||
.visible_private_messages=${this.visible_private()}
|
||||
.grouped_private_messages=${this.grouped_private_messages}
|
||||
.recent_reactions=${this.recent_reactions}
|
||||
?is_administrator=${this.is_administrator}
|
||||
?stay_connected=${this.stay_connected}
|
||||
></tf-tab-news>
|
||||
`;
|
||||
} else if (this.tab === 'connections') {
|
||||
@@ -572,28 +726,16 @@ class TfElement extends LitElement {
|
||||
: null}
|
||||
></tf-tab-search>
|
||||
`;
|
||||
} else if (this.tab === 'query') {
|
||||
return html`
|
||||
<tf-tab-query
|
||||
.following=${this.following}
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
query=${this.hash?.startsWith('#sql=')
|
||||
? decodeURIComponent(this.hash.substring(5))
|
||||
: null}
|
||||
></tf-tab-query>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
async set_tab(tab) {
|
||||
this.tab = tab;
|
||||
if (tab === 'news') {
|
||||
this.schedule_load_latest();
|
||||
await tfrpc.rpc.setHash('#');
|
||||
} else if (tab === 'connections') {
|
||||
await tfrpc.rpc.setHash('#connections');
|
||||
} else if (tab === 'query') {
|
||||
await tfrpc.rpc.setHash('#sql=');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,6 +743,29 @@ class TfElement extends LitElement {
|
||||
tfrpc.rpc.sync();
|
||||
}
|
||||
|
||||
async toggle_stay_connected() {
|
||||
let stay_connected = await tfrpc.rpc.globalSettingsGet('stay_connected');
|
||||
let new_stay_connected = !this.stay_connected;
|
||||
try {
|
||||
if (new_stay_connected != stay_connected) {
|
||||
await tfrpc.rpc.globalSettingsSet('stay_connected', new_stay_connected);
|
||||
}
|
||||
} finally {
|
||||
this.stay_connected = await tfrpc.rpc.globalSettingsGet('stay_connected');
|
||||
}
|
||||
}
|
||||
|
||||
async pick_color() {
|
||||
let input = document.createElement('input');
|
||||
input.type = 'color';
|
||||
input.value = (await tfrpc.rpc.localStorageGet('color')) ?? '#ff0000';
|
||||
input.addEventListener('change', async function () {
|
||||
await tfrpc.rpc.localStorageSet('color', input.value);
|
||||
window.location.reload();
|
||||
});
|
||||
input.click();
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
|
||||
@@ -615,7 +780,6 @@ class TfElement extends LitElement {
|
||||
'📰': 'news',
|
||||
'📡': 'connections',
|
||||
'🔍': 'search',
|
||||
'👩💻': 'query',
|
||||
};
|
||||
|
||||
let tabs = html`
|
||||
@@ -623,14 +787,25 @@ class TfElement extends LitElement {
|
||||
class="w3-bar w3-theme-l1"
|
||||
style="position: static; top: 0; z-index: 10"
|
||||
>
|
||||
<button
|
||||
class=${'w3-bar-item w3-button w3-circle w3-ripple' +
|
||||
(this.connections?.some((x) => x.flags.one_shot) ? ' w3-spin' : '')}
|
||||
style="width: 1.5em; height: 1.5em; padding: 8px"
|
||||
@click=${this.refresh}
|
||||
>
|
||||
↻
|
||||
</button>
|
||||
${this.is_administrator
|
||||
? html`
|
||||
<button
|
||||
class=${'w3-bar-item w3-button w3-circle w3-ripple' +
|
||||
(this.connections?.some((x) => x.flags.one_shot)
|
||||
? ' w3-spin'
|
||||
: '')}
|
||||
@click=${this.refresh}
|
||||
>
|
||||
↻
|
||||
</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-ripple"
|
||||
@click=${this.toggle_stay_connected}
|
||||
>
|
||||
${this.stay_connected ? '🔗' : '⛓️💥'}
|
||||
</button>
|
||||
`
|
||||
: undefined}
|
||||
${Object.entries(k_tabs).map(
|
||||
([k, v]) => html`
|
||||
<button
|
||||
@@ -647,6 +822,12 @@ class TfElement extends LitElement {
|
||||
</button>
|
||||
`
|
||||
)}
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-right"
|
||||
@click=${this.pick_color}
|
||||
>
|
||||
🎨<span class="w3-hide-small">Color</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
let contents = this.guest
|
||||
@@ -670,11 +851,26 @@ class TfElement extends LitElement {
|
||||
Loading...
|
||||
</div>`
|
||||
: this.render_tab();
|
||||
let progress =
|
||||
this.progress !== undefined
|
||||
? html`
|
||||
<div style="position: absolute; width: 100%" id="progress">
|
||||
<div
|
||||
class="w3-theme-l3"
|
||||
style=${`height: 4px; position: absolute; right: ${Math.cos(this.progress / 250) > 0 ? 'auto' : '0'}; width: ${50 * Math.sin(this.progress / 250) + 50}%`}
|
||||
></div>
|
||||
</div>
|
||||
`
|
||||
: undefined;
|
||||
return html`
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<div
|
||||
style="width: 100vw; min-height: 100vh; height: 100vh; display: flex; flex-direction: column"
|
||||
class="w3-theme-dark"
|
||||
>
|
||||
${progress}
|
||||
<div style="flex: 0 0">${tabs}</div>
|
||||
<div style="flex: 1 1; overflow: auto; contain: layout">
|
||||
${contents}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {LitElement, html, unsafeHTML, live} from './lit-all.min.js';
|
||||
import * as tfutils from './tf-utils.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
import Tribute from './tribute.esm.js';
|
||||
|
||||
class TfComposeElement extends LitElement {
|
||||
@@ -16,6 +16,7 @@ class TfComposeElement extends LitElement {
|
||||
author: {type: String},
|
||||
channel: {type: String},
|
||||
new_thread: {type: Boolean},
|
||||
recipients: {type: Array},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -91,7 +92,9 @@ class TfComposeElement extends LitElement {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
id: this.branch,
|
||||
id:
|
||||
this.branch ??
|
||||
(this.recipients ? this.recipients.join(',') : undefined),
|
||||
draft: draft,
|
||||
},
|
||||
})
|
||||
@@ -291,7 +294,7 @@ class TfComposeElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
get_values() {
|
||||
let values = Object.entries(this.users).map((x) => ({
|
||||
key: x[1].name ?? x[0],
|
||||
value: x[0],
|
||||
@@ -307,11 +310,15 @@ class TfComposeElement extends LitElement {
|
||||
values
|
||||
);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
let tribute = new Tribute({
|
||||
iframe: this.shadowRoot,
|
||||
collection: [
|
||||
{
|
||||
values: values,
|
||||
values: this.get_values(),
|
||||
selectTemplate: function (item) {
|
||||
return item
|
||||
? `[@${item.original.key}](${item.original.value})`
|
||||
@@ -330,6 +337,7 @@ class TfComposeElement extends LitElement {
|
||||
],
|
||||
});
|
||||
tribute.attach(this.renderRoot.getElementById('edit'));
|
||||
this._tribute = tribute;
|
||||
}
|
||||
|
||||
updated() {
|
||||
@@ -340,6 +348,7 @@ class TfComposeElement extends LitElement {
|
||||
preview.innerHTML = this.process_text(edit.innerText);
|
||||
this.last_updated_text = edit.innerText;
|
||||
}
|
||||
this._tribute.collection[0].values = this.get_values();
|
||||
let encrypt = this.renderRoot.getElementById('encrypt_to');
|
||||
if (encrypt) {
|
||||
let tribute = new Tribute({
|
||||
@@ -446,12 +455,15 @@ class TfComposeElement extends LitElement {
|
||||
self.apps = await tfrpc.rpc.apps();
|
||||
}
|
||||
if (!this.apps) {
|
||||
return html`<button class="w3-button w3-theme-d1" @click=${attach_app}>
|
||||
return html`<button
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${attach_app}
|
||||
>
|
||||
Attach App
|
||||
</button>`;
|
||||
} else {
|
||||
return html`<button
|
||||
class="w3-button w3-theme-d1"
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${() => (this.apps = null)}
|
||||
>
|
||||
Discard App
|
||||
@@ -472,18 +484,9 @@ class TfComposeElement extends LitElement {
|
||||
if (draft.content_warning !== undefined) {
|
||||
return html`
|
||||
<div class="w3-container w3-padding">
|
||||
<p>
|
||||
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning(undefined)} checked="checked"></input>
|
||||
<label for="cw">CW</label>
|
||||
</p>
|
||||
<input type="text" class="w3-input w3-border w3-theme-d1" id="content_warning" placeholder="Enter a content warning here." @input=${self.input} value=${draft.content_warning}></input>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
return html`
|
||||
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning('')}></input>
|
||||
<label for="cw">CW</label>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,7 +505,17 @@ class TfComposeElement extends LitElement {
|
||||
}
|
||||
|
||||
get_draft() {
|
||||
return this.drafts[this.branch || ''] || {};
|
||||
let key =
|
||||
this.branch ||
|
||||
(this.recipients ? this.recipients.join(',') : undefined) ||
|
||||
'';
|
||||
let draft = this.drafts[key] || {};
|
||||
if (this.recipients && !draft.encrypt_to?.length) {
|
||||
draft.encrypt_to = [
|
||||
...new Set(this.recipients).union(new Set(draft.encrypt_to ?? [])),
|
||||
];
|
||||
}
|
||||
return draft;
|
||||
}
|
||||
|
||||
update_encrypt(event) {
|
||||
@@ -524,7 +537,7 @@ class TfComposeElement extends LitElement {
|
||||
return html`
|
||||
<div style="display: flex; flex-direction: row; width: 100%">
|
||||
<label for="encrypt_to">🔐 To:</label>
|
||||
<input type="text" id="encrypt_to" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input>
|
||||
<input type="text" id="encrypt_to" class="w3-input w3-theme-d1 w3-border" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input>
|
||||
<button class="w3-button w3-theme-d1" @click=${() => this.set_encrypt(undefined)}>🚮</button>
|
||||
</div>
|
||||
<ul>
|
||||
@@ -546,6 +559,31 @@ class TfComposeElement extends LitElement {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
toggle_menu(event) {
|
||||
event.srcElement.parentNode
|
||||
.querySelector('.w3-dropdown-content')
|
||||
.classList.toggle('w3-show');
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._click_callback = this.document_click.bind(this);
|
||||
document.body.addEventListener('mouseup', this._click_callback);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
document.body.removeEventListener('mouseup', this._click_callback);
|
||||
}
|
||||
|
||||
document_click(event) {
|
||||
let content = this.renderRoot.querySelector('.w3-dropdown-content');
|
||||
let target = event.target;
|
||||
if (content && !content.contains(target)) {
|
||||
content.classList.remove('w3-show');
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
let draft = self.get_draft();
|
||||
@@ -559,12 +597,15 @@ class TfComposeElement extends LitElement {
|
||||
draft.encrypt_to !== undefined
|
||||
? undefined
|
||||
: html`<button
|
||||
class="w3-button w3-theme-d1"
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${() => this.set_encrypt([])}
|
||||
>
|
||||
🔐
|
||||
🔐 Encrypt
|
||||
</button>`;
|
||||
let result = html`
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<style>
|
||||
.w3-input:empty::before {
|
||||
content: attr(placeholder);
|
||||
@@ -583,11 +624,11 @@ class TfComposeElement extends LitElement {
|
||||
: undefined}
|
||||
${this.render_encrypt()}
|
||||
</header>
|
||||
<div class="w3-container w3-padding-small">
|
||||
<div class="w3-container" style="padding: 0 0 16px 0">
|
||||
<div class="w3-half">
|
||||
<span
|
||||
class="w3-input w3-theme-d1 w3-border"
|
||||
style="resize: vertical; width: 100%; overflow: hidden; white-space: pre-wrap"
|
||||
style="resize: vertical; width: 100%; white-space: pre-wrap"
|
||||
placeholder="Write a post here."
|
||||
id="edit"
|
||||
@input=${this.input}
|
||||
@@ -604,7 +645,7 @@ class TfComposeElement extends LitElement {
|
||||
${Object.values(draft.mentions || {}).map((x) =>
|
||||
self.render_mention(x)
|
||||
)}
|
||||
<footer class="w3-container">
|
||||
<footer>
|
||||
${this.render_attach_app()} ${this.render_content_warning()}
|
||||
${this.render_new_thread()}
|
||||
<button
|
||||
@@ -614,13 +655,43 @@ class TfComposeElement extends LitElement {
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
<button class="w3-button w3-theme-d1" @click=${this.attach}>
|
||||
Attach
|
||||
</button>
|
||||
${this.render_attach_app_button()} ${encrypt}
|
||||
<button class="w3-button w3-theme-d1" @click=${this.discard}>
|
||||
Discard
|
||||
</button>
|
||||
<div class="w3-dropdown-click">
|
||||
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
|
||||
⚙️
|
||||
</button>
|
||||
<div class="w3-dropdown-content w3-bar-block">
|
||||
${this.get_draft().content_warning === undefined
|
||||
? html`
|
||||
<button
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${() => self.set_content_warning('')}
|
||||
>
|
||||
Add Content Warning
|
||||
</button>
|
||||
`
|
||||
: html`
|
||||
<button
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${() => self.set_content_warning(undefined)}
|
||||
>
|
||||
Remove Content Warning
|
||||
</button>
|
||||
`}
|
||||
<button
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${this.attach}
|
||||
>
|
||||
Attach
|
||||
</button>
|
||||
${this.render_attach_app_button()} ${encrypt}
|
||||
<button
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${this.discard}
|
||||
>
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import {LitElement, html, repeat, render, unsafeHTML} from './lit-all.min.js';
|
||||
import {
|
||||
LitElement,
|
||||
css,
|
||||
html,
|
||||
repeat,
|
||||
render,
|
||||
unsafeCSS,
|
||||
unsafeHTML,
|
||||
until,
|
||||
} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import * as tfutils from './tf-utils.js';
|
||||
import * as emojis from './emojis.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfMessageElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -16,6 +25,8 @@ class TfMessageElement extends LitElement {
|
||||
expanded: {type: Object},
|
||||
channel: {type: String},
|
||||
channel_unread: {type: Number},
|
||||
recent_reactions: {type: Array},
|
||||
depth: {type: Number},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,16 +42,21 @@ class TfMessageElement extends LitElement {
|
||||
this.format = 'message';
|
||||
this.expanded = {};
|
||||
this.channel_unread = -1;
|
||||
this.recent_reactions = [];
|
||||
this.depth = 0;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._click_callback = this.document_click.bind(this);
|
||||
this._blob_stored = this.blob_stored.bind(this);
|
||||
document.body.addEventListener('mouseup', this._click_callback);
|
||||
window.addEventListener('blob-stored', this._blob_stored);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener('blob-stored', this._blob_stored);
|
||||
document.body.removeEventListener('mouseup', this._click_callback);
|
||||
}
|
||||
|
||||
@@ -52,6 +68,16 @@ class TfMessageElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
blob_stored(event) {
|
||||
let search = `/${event.detail.id}/view`;
|
||||
for (let img of this.shadowRoot.querySelectorAll('img')) {
|
||||
if (img.src.indexOf(search) != -1) {
|
||||
let src = img.src.split('?')[0];
|
||||
img.src = `${src}?${new Date().valueOf()}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
show_reply() {
|
||||
let event = new CustomEvent('tf-draft', {
|
||||
bubbles: true,
|
||||
@@ -84,20 +110,27 @@ class TfMessageElement extends LitElement {
|
||||
|
||||
render_votes() {
|
||||
function normalize_expression(expression) {
|
||||
if (expression === 'Like' || !expression) {
|
||||
return '👍';
|
||||
} else if (expression === 'Unlike') {
|
||||
if (
|
||||
expression === 'Unlike' ||
|
||||
expression === 'unlike' ||
|
||||
expression == 'undig'
|
||||
) {
|
||||
return '👎';
|
||||
} else if (expression === 'heart') {
|
||||
return '❤️';
|
||||
} else if (
|
||||
(expression ?? '').split('').every((x) => x.charCodeAt(0) < 256)
|
||||
) {
|
||||
return '👍';
|
||||
} else {
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
if (this.message?.votes?.length) {
|
||||
return html` <div class="w3-container">
|
||||
return html` <footer class="w3-container">
|
||||
<div
|
||||
class="w3-button w3-bar w3-padding-small"
|
||||
class="w3-button w3-bar"
|
||||
style="padding: 0"
|
||||
@click=${this.show_reactions}
|
||||
>
|
||||
${(this.message.votes || []).map(
|
||||
@@ -112,7 +145,7 @@ class TfMessageElement extends LitElement {
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</div>`;
|
||||
</footer>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +188,12 @@ class TfMessageElement extends LitElement {
|
||||
}
|
||||
|
||||
react(event) {
|
||||
emojis.picker((x) => this.vote(x), null, this.whoami);
|
||||
emojis.picker(
|
||||
(x) => this.vote(x),
|
||||
null,
|
||||
this.whoami,
|
||||
this.recent_reactions
|
||||
);
|
||||
}
|
||||
|
||||
show_image(link) {
|
||||
@@ -170,12 +208,12 @@ class TfMessageElement extends LitElement {
|
||||
div.style.display = 'grid';
|
||||
let img = document.createElement('img');
|
||||
img.src = link;
|
||||
img.style.maxWidth = '100%';
|
||||
img.style.maxHeight = '100%';
|
||||
img.style.maxWidth = '100vw';
|
||||
img.style.maxHeight = '100vh';
|
||||
img.style.display = 'block';
|
||||
img.style.margin = 'auto';
|
||||
img.style.objectFit = 'contain';
|
||||
img.style.width = '100%';
|
||||
img.style.width = '100vw';
|
||||
div.appendChild(img);
|
||||
function image_close(event) {
|
||||
document.body.removeChild(div);
|
||||
@@ -289,27 +327,37 @@ class TfMessageElement extends LitElement {
|
||||
return total;
|
||||
}
|
||||
|
||||
expanded_key() {
|
||||
return (
|
||||
this.message?.id || this.message?.messages?.map((x) => x.id).join(':')
|
||||
);
|
||||
}
|
||||
|
||||
set_expanded(expanded, tag) {
|
||||
let key = this.expanded_key();
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('tf-expand', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {id: (this.message.id || '') + (tag || ''), expanded: expanded},
|
||||
detail: {id: key + (tag || ''), expanded: expanded},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
toggle_expanded(tag) {
|
||||
this.set_expanded(
|
||||
!this.expanded[(this.message.id || '') + (tag || '')],
|
||||
tag
|
||||
);
|
||||
let key = this.expanded_key();
|
||||
this.set_expanded(!this.expanded[key + (tag || '')], tag);
|
||||
}
|
||||
|
||||
is_expanded(tag) {
|
||||
let key = this.expanded_key();
|
||||
return this.expanded[key + (tag || '')];
|
||||
}
|
||||
|
||||
render_children() {
|
||||
let self = this;
|
||||
if (this.message.child_messages?.length) {
|
||||
if (!this.expanded[this.message.id]) {
|
||||
if (!this.expanded[this.expanded_key()]) {
|
||||
return html`
|
||||
<button
|
||||
class="w3-button w3-theme-d1 w3-block w3-bar"
|
||||
@@ -320,12 +368,13 @@ class TfMessageElement extends LitElement {
|
||||
</button>
|
||||
`;
|
||||
} else {
|
||||
return html` <div class="w3-container w3-margin-bottom">
|
||||
${repeat(
|
||||
this.message.child_messages || [],
|
||||
(x) => x.id,
|
||||
(x) =>
|
||||
html`<tf-message
|
||||
return html` <ul class="w3-container w3-margin-bottom w3-ul w3-card-4">
|
||||
${repeat(
|
||||
this.message.child_messages || [],
|
||||
(x) => x.id,
|
||||
(x) =>
|
||||
html`<li style="padding: 0">
|
||||
<tf-message
|
||||
.message=${x}
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
@@ -333,16 +382,21 @@ class TfMessageElement extends LitElement {
|
||||
.expanded=${this.expanded}
|
||||
channel=${this.channel}
|
||||
channel_unread=${this.channel_unread}
|
||||
></tf-message>`
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
class="w3-button w3-theme-d1 w3-block w3-bar"
|
||||
style="box-sizing: border-box"
|
||||
@click=${() => self.set_expanded(false)}
|
||||
>
|
||||
Collapse
|
||||
</button>`;
|
||||
.recent_reactions=${this.recent_reactions}
|
||||
depth=${this.depth + 1}
|
||||
></tf-message>
|
||||
</li>`
|
||||
)}
|
||||
<li style="padding: 0" class="w3-margin-bottom">
|
||||
<button
|
||||
class="w3-button w3-theme-d1 w3-block w3-bar"
|
||||
style="box-sizing: border-box"
|
||||
@click=${() => self.set_expanded(false)}
|
||||
>
|
||||
Collapse
|
||||
</button>
|
||||
</li>
|
||||
</ul>`;
|
||||
}
|
||||
} else {
|
||||
return undefined;
|
||||
@@ -384,7 +438,7 @@ class TfMessageElement extends LitElement {
|
||||
class_background() {
|
||||
return this.message?.decrypted
|
||||
? 'w3-pale-red'
|
||||
: this.message?.rowid >= this.channel_unread
|
||||
: this.allow_unread() && this.message?.rowid >= this.channel_unread
|
||||
? 'w3-theme-d2'
|
||||
: 'w3-theme-d4';
|
||||
}
|
||||
@@ -441,7 +495,7 @@ class TfMessageElement extends LitElement {
|
||||
${this.drafts[this.message?.id] === undefined
|
||||
? html`
|
||||
<button class="w3-button w3-bar-item" @click=${this.show_reply}>
|
||||
⮢ Reply
|
||||
↩️ Reply
|
||||
</button>
|
||||
`
|
||||
: undefined}
|
||||
@@ -476,7 +530,10 @@ class TfMessageElement extends LitElement {
|
||||
return html`
|
||||
<header class="w3-bar">
|
||||
<span class="w3-bar-item">
|
||||
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
|
||||
${this.render_unread_icon()}<tf-user
|
||||
id=${this.message.author}
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
</span>
|
||||
${is_encrypted} ${this.render_menu()}
|
||||
<div class="w3-bar-item w3-right" style="text-wrap: nowrap">
|
||||
@@ -503,8 +560,11 @@ class TfMessageElement extends LitElement {
|
||||
}
|
||||
</style>
|
||||
<div
|
||||
class="w3-card-4 ${this.class_background()} w3-border-theme w3-margin-top"
|
||||
style="overflow: auto; overflow-wrap: anywhere; display: block; max-width: 100%"
|
||||
class="w3-card-4 ${this.class_background()} w3-border-theme ${this
|
||||
.depth == 0
|
||||
? 'w3-margin-top'
|
||||
: ''}"
|
||||
style="overflow-wrap: anywhere; display: block; max-width: 100%"
|
||||
>
|
||||
${inner}
|
||||
</div>
|
||||
@@ -529,6 +589,8 @@ class TfMessageElement extends LitElement {
|
||||
.expanded=${self.expanded}
|
||||
channel=${self.channel}
|
||||
channel_unread=${self.channel_unread}
|
||||
.recent_reactions=${self.recent_reactions}
|
||||
depth=${self.depth + 1}
|
||||
></tf-message>
|
||||
`
|
||||
)}
|
||||
@@ -540,24 +602,108 @@ class TfMessageElement extends LitElement {
|
||||
let reply =
|
||||
this.drafts[this.message?.id] !== undefined
|
||||
? html`
|
||||
<tf-compose
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
root=${content.root || this.message.id}
|
||||
branch=${this.message.id}
|
||||
.drafts=${this.drafts}
|
||||
@tf-discard=${this.discard_reply}
|
||||
author=${this.message.author}
|
||||
></tf-compose>
|
||||
<div class="w3-section w3-container">
|
||||
<tf-compose
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
root=${content.root || this.message.id}
|
||||
branch=${this.message.id}
|
||||
.drafts=${this.drafts}
|
||||
@tf-discard=${this.discard_reply}
|
||||
author=${this.message.author}
|
||||
.recent_reactions=${this.recent_reactions}
|
||||
></tf-compose>
|
||||
</div>
|
||||
`
|
||||
: undefined;
|
||||
return html`
|
||||
<div class="w3-section w3-container">${reply}</div>
|
||||
${reply}
|
||||
<footer>${this.render_children()}</footer>
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
content_group_by_author() {
|
||||
let sorted = this.message.messages
|
||||
.map((x) => [
|
||||
x.author,
|
||||
x.content.following && x.content.blocking
|
||||
? 'is following and blocking'
|
||||
: x.content.following
|
||||
? 'is following'
|
||||
: x.content.blocking
|
||||
? 'is blocking'
|
||||
: x.content.blocking !== undefined
|
||||
? 'is no longer blocking'
|
||||
: x.content.following !== undefined
|
||||
? 'is no longer following'
|
||||
: '',
|
||||
x.content.contact,
|
||||
x,
|
||||
])
|
||||
.sort();
|
||||
let result = [];
|
||||
let last;
|
||||
let group;
|
||||
for (let row of sorted) {
|
||||
if (last && last[0] == row[0] && last[1] == row[1]) {
|
||||
group.push(row[2]);
|
||||
} else {
|
||||
if (group) {
|
||||
result.push({author: last[0], action: last[1], users: group});
|
||||
}
|
||||
last = row;
|
||||
group = [row[2]];
|
||||
}
|
||||
}
|
||||
if (group) {
|
||||
result.push({author: last[0], action: last[1], users: group});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
channel_group_by_author() {
|
||||
let sorted = this.message.messages
|
||||
.map((x) => [
|
||||
x.author,
|
||||
x.content.subscribed ? 'subscribed to' : 'unsubscribed from',
|
||||
x.content.channel,
|
||||
x,
|
||||
])
|
||||
.sort();
|
||||
let result = [];
|
||||
let last;
|
||||
let group;
|
||||
for (let row of sorted) {
|
||||
if (last && last[0] == row[0] && last[1] == row[1]) {
|
||||
group.push(row[2]);
|
||||
} else {
|
||||
if (group) {
|
||||
result.push({author: last[0], action: last[1], channels: group});
|
||||
}
|
||||
last = row;
|
||||
group = [row[2]];
|
||||
}
|
||||
}
|
||||
if (group) {
|
||||
result.push({author: last[0], action: last[1], channels: group});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
allow_unread() {
|
||||
return (
|
||||
this.channel == '@' ||
|
||||
(!this.channel.startsWith('@') && !this.channel.startsWith('%'))
|
||||
);
|
||||
}
|
||||
|
||||
render_unread_icon() {
|
||||
return this.allow_unread() && this.message?.rowid >= this.channel_unread
|
||||
? html`✉️`
|
||||
: undefined;
|
||||
}
|
||||
|
||||
_render() {
|
||||
let content = this.message?.content;
|
||||
if (this.message?.decrypted?.type == 'post') {
|
||||
content = this.message.decrypted;
|
||||
@@ -565,29 +711,145 @@ class TfMessageElement extends LitElement {
|
||||
let class_background = this.class_background();
|
||||
let self = this;
|
||||
if (this.message?.type === 'contact_group') {
|
||||
return this.render_frame(
|
||||
html` ${this.message.messages.map(
|
||||
(x) =>
|
||||
html`<tf-message
|
||||
.message=${x}
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
.drafts=${this.drafts}
|
||||
.expanded=${this.expanded}
|
||||
channel=${this.channel}
|
||||
channel_unread=${this.channel_unread}
|
||||
></tf-message>`
|
||||
)}`
|
||||
);
|
||||
if (this.expanded[this.expanded_key()]) {
|
||||
return this.render_frame(html`
|
||||
<div class="w3-padding">
|
||||
${this.message.messages.map(
|
||||
(x) =>
|
||||
html`<tf-message
|
||||
.message=${x}
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
.drafts=${this.drafts}
|
||||
.expanded=${this.expanded}
|
||||
channel=${this.channel}
|
||||
channel_unread=${this.channel_unread}
|
||||
depth=${this.depth + 1}
|
||||
></tf-message>`
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
class="w3-button w3-theme-d1 w3-block w3-bar"
|
||||
style="box-sizing: border-box"
|
||||
@click=${() => self.set_expanded(false)}
|
||||
>
|
||||
Collapse
|
||||
</button>
|
||||
`);
|
||||
} else {
|
||||
return this.render_frame(html`
|
||||
<div class="w3-padding">
|
||||
${this.content_group_by_author().map(
|
||||
(x) => html`
|
||||
<div>
|
||||
<tf-user id=${x.author} .users=${this.users}></tf-user>
|
||||
${x.action}
|
||||
${x.users.map(
|
||||
(y) => html`
|
||||
<tf-user
|
||||
id=${y}
|
||||
.users=${this.users}
|
||||
icon_only="true"
|
||||
></tf-user>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
class="w3-button w3-theme-d1 w3-block w3-bar"
|
||||
style="box-sizing: border-box"
|
||||
@click=${() => self.set_expanded(true)}
|
||||
>
|
||||
Expand
|
||||
</button>
|
||||
`);
|
||||
}
|
||||
} else if (this.message?.type === 'channel_group') {
|
||||
if (this.expanded[this.expanded_key()]) {
|
||||
return this.render_frame(html`
|
||||
<div class="w3-padding">
|
||||
${this.message.messages.map(
|
||||
(x) =>
|
||||
html`<tf-message
|
||||
.message=${x}
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
.drafts=${this.drafts}
|
||||
.expanded=${this.expanded}
|
||||
channel=${this.channel}
|
||||
channel_unread=${this.channel_unread}
|
||||
depth=${this.depth + 1}
|
||||
></tf-message>`
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
class="w3-button w3-theme-d1 w3-block w3-bar"
|
||||
style="box-sizing: border-box"
|
||||
@click=${() => self.set_expanded(false)}
|
||||
>
|
||||
Collapse
|
||||
</button>
|
||||
`);
|
||||
} else {
|
||||
return this.render_frame(html`
|
||||
<div class="w3-padding">
|
||||
${this.channel_group_by_author().map(
|
||||
(x) => html`
|
||||
<div>
|
||||
<tf-user id=${x.author} .users=${this.users}></tf-user>
|
||||
${x.action}
|
||||
${x.channels.map(
|
||||
(y) => html` <tf-tag tag=${'#' + y}></tf-tag> `
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
class="w3-button w3-theme-d1 w3-block w3-bar"
|
||||
style="box-sizing: border-box"
|
||||
@click=${() => self.set_expanded(true)}
|
||||
>
|
||||
Expand
|
||||
</button>
|
||||
`);
|
||||
}
|
||||
} else if (this.message.placeholder) {
|
||||
return this.render_frame(
|
||||
html`<div class="w3-padding">
|
||||
<p>
|
||||
<a target="_top" href=${'#' + encodeURIComponent(this.message.id)}
|
||||
>${this.message.id}</a
|
||||
html`<div>
|
||||
<div class="w3-bar">
|
||||
<a
|
||||
class="w3-bar-item w3-panel w3-round-xlarge w3-theme-d1 w3-margin w3-button"
|
||||
target="_top"
|
||||
href=${'#' + encodeURIComponent(this.message?.id)}
|
||||
>
|
||||
(placeholder)
|
||||
</p>
|
||||
This message is not currently available.
|
||||
</a>
|
||||
<div class="w3-bar-item w3-right">
|
||||
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
|
||||
%
|
||||
</button>
|
||||
<div
|
||||
class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
|
||||
style="right: 48px"
|
||||
>
|
||||
<a
|
||||
target="_top"
|
||||
class="w3-button w3-bar-item"
|
||||
href=${'#' + encodeURIComponent(this.message?.id)}
|
||||
>View Message</a
|
||||
>
|
||||
<button
|
||||
class="w3-button w3-bar-item w3-border-bottom"
|
||||
@click=${this.copy_id}
|
||||
>
|
||||
Copy ID
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>${this.render_votes()}</div>
|
||||
${(this.message.child_messages || []).map(
|
||||
(x) => html`
|
||||
@@ -599,6 +861,7 @@ class TfMessageElement extends LitElement {
|
||||
.expanded=${this.expanded}
|
||||
channel=${this.channel}
|
||||
channel_unread=${this.channel_unread}
|
||||
depth=${this.depth + 1}
|
||||
></tf-message>
|
||||
`
|
||||
)}
|
||||
@@ -614,7 +877,7 @@ class TfMessageElement extends LitElement {
|
||||
}
|
||||
if (content.image !== undefined) {
|
||||
image = html`
|
||||
<div><img src=${'/' + (typeof content.image?.link == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div>
|
||||
<div @click=${this.body_click}><img src=${'/' + (typeof content.image?.link == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div>
|
||||
`;
|
||||
}
|
||||
if (content.description !== undefined) {
|
||||
@@ -637,25 +900,45 @@ class TfMessageElement extends LitElement {
|
||||
</div>
|
||||
`);
|
||||
} else if (content.type == 'contact') {
|
||||
return html`
|
||||
<div class="w3-padding">
|
||||
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
|
||||
is
|
||||
${content.blocking === true
|
||||
? 'blocking'
|
||||
: content.blocking === false
|
||||
? 'no longer blocking'
|
||||
: content.following === true
|
||||
? 'following'
|
||||
: content.following === false
|
||||
? 'no longer following'
|
||||
: '?'}
|
||||
<tf-user
|
||||
id=${this.message.content.contact}
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
</div>
|
||||
`;
|
||||
switch (this.format) {
|
||||
case 'message':
|
||||
default:
|
||||
return this.render_frame(html`
|
||||
<div class="w3-bar">
|
||||
<div class="w3-bar-item">
|
||||
<tf-user
|
||||
id=${this.message.author}
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
is
|
||||
${content.blocking === true
|
||||
? 'blocking'
|
||||
: content.blocking === false
|
||||
? 'no longer blocking'
|
||||
: content.following === true
|
||||
? 'following'
|
||||
: content.following === false
|
||||
? 'no longer following'
|
||||
: '?'}
|
||||
<tf-user
|
||||
id=${this.message.content.contact}
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
</div>
|
||||
${this.render_menu()} ${this.render_votes()}
|
||||
${this.render_actions()}
|
||||
</div>
|
||||
`);
|
||||
break;
|
||||
case 'raw':
|
||||
return this.render_frame(html`
|
||||
${this.render_header()}
|
||||
<div class="w3-container">${this.render_raw()}</div>
|
||||
${this.render_votes()} ${this.render_actions()}
|
||||
</div>
|
||||
`);
|
||||
break;
|
||||
}
|
||||
} else if (content.type == 'post') {
|
||||
let self = this;
|
||||
let body;
|
||||
@@ -678,11 +961,14 @@ class TfMessageElement extends LitElement {
|
||||
}
|
||||
let content_warning = html`
|
||||
<div
|
||||
class="w3-panel w3-round-xlarge w3-theme-l4"
|
||||
class="w3-panel w3-round-xlarge w3-theme-l4 w3"
|
||||
style="cursor: pointer"
|
||||
@click=${(x) => this.toggle_expanded(':cw')}
|
||||
>
|
||||
<p>${content.contentWarning}</p>
|
||||
<p class="w3-small">
|
||||
${this.is_expanded(':cw') ? 'Show less' : 'Show more'}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
let content_html = html`
|
||||
@@ -811,6 +1097,15 @@ class TfMessageElement extends LitElement {
|
||||
return this.render_small_frame(this.render_raw());
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
${this._render()}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tf-message', TfMessageElement);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {LitElement, html, unsafeHTML, repeat, until} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfNewsElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -13,6 +13,8 @@ class TfNewsElement extends LitElement {
|
||||
expanded: {type: Object},
|
||||
channel: {type: String},
|
||||
channel_unread: {type: Number},
|
||||
recent_reactions: {type: Array},
|
||||
hash: {type: String},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,6 +30,7 @@ class TfNewsElement extends LitElement {
|
||||
this.drafts = {};
|
||||
this.expanded = {};
|
||||
this.channel_unread = -1;
|
||||
this.recent_reactions = [];
|
||||
}
|
||||
|
||||
process_messages(messages) {
|
||||
@@ -157,46 +160,80 @@ class TfNewsElement extends LitElement {
|
||||
return recursive_sort(roots, true);
|
||||
}
|
||||
|
||||
group_following(messages) {
|
||||
group_messages(messages) {
|
||||
let result = [];
|
||||
let group = [];
|
||||
let type = undefined;
|
||||
for (let message of messages) {
|
||||
if (message?.content?.type === 'contact') {
|
||||
if (
|
||||
message?.content?.type === 'contact' ||
|
||||
message?.content?.type === 'channel'
|
||||
) {
|
||||
if (type && message.content.type !== type) {
|
||||
if (group.length == 1) {
|
||||
result.push(group[0]);
|
||||
group = [];
|
||||
} else if (group.length > 1) {
|
||||
result.push({
|
||||
rowid: Math.max(...group.map((x) => x.rowid)),
|
||||
type: `${type}_group`,
|
||||
messages: group,
|
||||
});
|
||||
group = [];
|
||||
}
|
||||
}
|
||||
type = message.content.type;
|
||||
group.push(message);
|
||||
} else {
|
||||
if (group.length > 0) {
|
||||
if (group.length == 1) {
|
||||
result.push(group[0]);
|
||||
group = [];
|
||||
} else if (group.length > 1) {
|
||||
result.push({
|
||||
rowid: Math.max(...group.map((x) => x.rowid)),
|
||||
type: 'contact_group',
|
||||
type: `${type}_group`,
|
||||
messages: group,
|
||||
});
|
||||
group = [];
|
||||
}
|
||||
result.push(message);
|
||||
type = undefined;
|
||||
}
|
||||
}
|
||||
if (group.length > 0) {
|
||||
if (group.length == 1) {
|
||||
result.push(group[0]);
|
||||
group = [];
|
||||
} else if (group.length > 1) {
|
||||
result.push({
|
||||
rowid: Math.max(...group.map((x) => x.rowid)),
|
||||
type: 'contact_group',
|
||||
type: `${type}_group`,
|
||||
messages: group,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
unread_allowed() {
|
||||
return !this.hash?.startsWith('#%') && !this.hash?.startsWith('#@');
|
||||
}
|
||||
|
||||
load_and_render(messages) {
|
||||
let messages_by_id = this.process_messages(messages);
|
||||
let final_messages = this.group_following(
|
||||
let final_messages = this.group_messages(
|
||||
this.finalize_messages(messages_by_id)
|
||||
);
|
||||
let unread_rowid = -1;
|
||||
for (let message of final_messages) {
|
||||
if (message.rowid >= this.channel_unread) {
|
||||
unread_rowid = message.rowid;
|
||||
if (this.unread_allowed()) {
|
||||
for (let message of final_messages) {
|
||||
if (message.rowid >= this.channel_unread) {
|
||||
unread_rowid = message.rowid;
|
||||
}
|
||||
}
|
||||
}
|
||||
return html`
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<div>
|
||||
${repeat(
|
||||
final_messages,
|
||||
@@ -211,13 +248,26 @@ class TfNewsElement extends LitElement {
|
||||
collapsed="true"
|
||||
channel=${this.channel}
|
||||
channel_unread=${this.channel_unread}
|
||||
.recent_reactions=${this.recent_reactions}
|
||||
></tf-message>
|
||||
${x.rowid == unread_rowid
|
||||
? html`<div style="display: flex; flex-direction: row">
|
||||
<div
|
||||
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
|
||||
></div>
|
||||
<div style="color: #f00; padding: 8px">unread</div>
|
||||
<button
|
||||
style="color: #f00; padding: 8px"
|
||||
class="w3-button"
|
||||
@click=${() =>
|
||||
this.dispatchEvent(
|
||||
new Event('mark_all_read', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
)}
|
||||
>
|
||||
unread
|
||||
</button>
|
||||
<div
|
||||
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
|
||||
></div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||
import {LitElement, html, until, unsafeHTML} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import * as tfutils from './tf-utils.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfProfileElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -14,6 +14,7 @@ class TfProfileElement extends LitElement {
|
||||
sequence: {type: Number},
|
||||
following: {type: Boolean},
|
||||
blocking: {type: Boolean},
|
||||
show_followed: {type: Boolean},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -36,16 +37,22 @@ class TfProfileElement extends LitElement {
|
||||
this.following = undefined;
|
||||
this.blocking = undefined;
|
||||
|
||||
let latest = (
|
||||
await tfrpc.rpc.query('SELECT MAX(rowid) AS latest FROM messages')
|
||||
)[0].latest;
|
||||
|
||||
let result = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT json_extract(content, '$.following') AS following
|
||||
FROM messages WHERE author = ? AND
|
||||
json_extract(content, '$.type') = 'contact' AND
|
||||
json_extract(content, '$.contact') = ? AND
|
||||
following IS NOT NULL
|
||||
following IS NOT NULL AND
|
||||
messages.rowid <= ?
|
||||
ORDER BY sequence DESC LIMIT 1
|
||||
`,
|
||||
[this.whoami, this.id]
|
||||
[this.whoami, this.id, latest],
|
||||
{cacheable: true}
|
||||
);
|
||||
this.following = result?.[0]?.following ?? false;
|
||||
result = await tfrpc.rpc.query(
|
||||
@@ -54,10 +61,12 @@ class TfProfileElement extends LitElement {
|
||||
FROM messages WHERE author = ? AND
|
||||
json_extract(content, '$.type') = 'contact' AND
|
||||
json_extract(content, '$.contact') = ? AND
|
||||
blocking IS NOT NULL
|
||||
blocking IS NOT NULL AND
|
||||
messages.rowid <= ?
|
||||
ORDER BY sequence DESC LIMIT 1
|
||||
`,
|
||||
[this.whoami, this.id]
|
||||
[this.whoami, this.id, latest],
|
||||
{cacheable: true}
|
||||
);
|
||||
this.blocking = result?.[0]?.blocking ?? false;
|
||||
}
|
||||
@@ -166,13 +175,78 @@ class TfProfileElement extends LitElement {
|
||||
navigator.clipboard.writeText(this.id);
|
||||
}
|
||||
|
||||
show_image(link) {
|
||||
let div = document.createElement('div');
|
||||
div.style.left = 0;
|
||||
div.style.top = 0;
|
||||
div.style.width = '100%';
|
||||
div.style.height = '100%';
|
||||
div.style.position = 'fixed';
|
||||
div.style.background = '#000';
|
||||
div.style.zIndex = 100;
|
||||
div.style.display = 'grid';
|
||||
let img = document.createElement('img');
|
||||
img.src = link;
|
||||
img.style.maxWidth = '100vw';
|
||||
img.style.maxHeight = '100vh';
|
||||
img.style.display = 'block';
|
||||
img.style.margin = 'auto';
|
||||
img.style.objectFit = 'contain';
|
||||
img.style.width = '100vw';
|
||||
div.appendChild(img);
|
||||
function image_close(event) {
|
||||
document.body.removeChild(div);
|
||||
window.removeEventListener('keydown', image_close);
|
||||
}
|
||||
div.onclick = image_close;
|
||||
window.addEventListener('keydown', image_close);
|
||||
document.body.appendChild(div);
|
||||
}
|
||||
|
||||
body_click(event) {
|
||||
if (event.srcElement.tagName == 'IMG') {
|
||||
this.show_image(event.srcElement.src);
|
||||
}
|
||||
}
|
||||
|
||||
toggle_account_list(event) {
|
||||
let content = event.srcElement.nextElementSibling;
|
||||
this.show_followed = !this.show_followed;
|
||||
}
|
||||
|
||||
async load_follows() {
|
||||
let accounts = await tfrpc.rpc.following([this.id], 1);
|
||||
return html`
|
||||
<div class="w3-container">
|
||||
<button
|
||||
class="w3-button w3-block w3-theme-d1 followed_accounts"
|
||||
@click=${this.toggle_account_list}
|
||||
>
|
||||
${this.show_followed ? 'Hide' : 'Show'} Followed Accounts
|
||||
(${Object.keys(accounts).length})
|
||||
</button>
|
||||
<div class=${'w3-card' + (this.show_followed ? '' : ' w3-hide')}>
|
||||
<ul class="w3-ul w3-theme-d4 w3-border-theme">
|
||||
${Object.keys(accounts).map(
|
||||
(x) => html`
|
||||
<li class="w3-border-theme">
|
||||
<tf-user id=${x} .users=${this.users}></tf-user>
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
this.load();
|
||||
let self = this;
|
||||
let profile = this.users[this.id] || {};
|
||||
tfrpc.rpc
|
||||
.query(
|
||||
`SELECT SUM(LENGTH(content)) AS size, MAX(sequence) AS sequence FROM messages WHERE author = ?`,
|
||||
`SELECT size AS size, max_sequence AS sequence FROM messages_stats WHERE author = ?`,
|
||||
[this.id]
|
||||
)
|
||||
.then(function (result) {
|
||||
@@ -242,22 +316,28 @@ class TfProfileElement extends LitElement {
|
||||
</div>
|
||||
</div>`
|
||||
: null;
|
||||
let image =
|
||||
typeof profile.image == 'string' ? profile.image : profile.image?.link;
|
||||
let image = profile.image;
|
||||
if (typeof image == 'string' && !image.startsWith('&')) {
|
||||
try {
|
||||
image = JSON.parse(image)?.link;
|
||||
} catch {}
|
||||
}
|
||||
image = this.editing?.image ?? image;
|
||||
let description = this.editing?.description ?? profile.description;
|
||||
return html`<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box">
|
||||
return html`
|
||||
<style>${generate_theme()}</style>
|
||||
<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box">
|
||||
<header class="w3-container">
|
||||
<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)} in ${this.sequence} messages)</p>
|
||||
</header>
|
||||
<div class="w3-container">
|
||||
<div class="w3-container" @click=${this.body_click}>
|
||||
<div class="w3-margin-bottom" style="display: flex; flex-direction: row">
|
||||
<input type="text" class="w3-input w3-border w3-theme-d1" style="display: flex 1 1" readonly value=${this.id}></input>
|
||||
<button class="w3-button w3-theme-d1 w3-ripple" style="flex: 0 0 auto" @click=${this.copy_id}>Copy</button>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: row; gap: 1em">
|
||||
${edit_profile}
|
||||
<div style="flex: 1 0 50%">
|
||||
<div style="flex: 1 0 50%; contain: layout; overflow: auto; word-wrap: normal; word-break: normal">
|
||||
${
|
||||
image
|
||||
? html`<div><img src=${'/' + image + '/view'} style="width: 256px; height: auto"></img></div>`
|
||||
@@ -276,8 +356,12 @@ class TfProfileElement extends LitElement {
|
||||
Blocked by ${profile.blocked} identities.
|
||||
</div>
|
||||
</div>
|
||||
${until(this.load_follows(), html`<p>Loading accounts followed...</p>`)}
|
||||
<footer class="w3-container">
|
||||
<p>
|
||||
<a class="w3-button w3-theme-d1" href=${'#🔐' + (this.id != this.whoami ? this.id : '')}>
|
||||
Open Private Chat
|
||||
</a>
|
||||
${edit}
|
||||
${follow}
|
||||
${block}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfReactionsModalElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -24,50 +24,57 @@ class TfReactionsModalElement extends LitElement {
|
||||
render() {
|
||||
let self = this;
|
||||
return this.votes?.length
|
||||
? html` <div
|
||||
class="w3-modal w3-animate-opacity"
|
||||
style="display: block; box-sizing: border-box; z-index: 10"
|
||||
@click=${this.clear}
|
||||
>
|
||||
? html` <style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<div
|
||||
class="w3-modal-content w3-card-4 w3-theme-d1"
|
||||
onclick="event.stopPropagation()"
|
||||
class="w3-modal w3-animate-opacity"
|
||||
style="display: block; box-sizing: border-box; z-index: 10"
|
||||
@click=${this.clear}
|
||||
>
|
||||
<div class="w3-container w3-padding">
|
||||
<header class="w3-container">
|
||||
<h2>Reactions</h2>
|
||||
<span class="w3-button w3-display-topright" @click=${this.clear}
|
||||
>×</span
|
||||
>
|
||||
</header>
|
||||
<ul class="w3-theme-dark w3-container w3-ul">
|
||||
${this.votes
|
||||
.sort((x, y) => y.timestamp - x.timestamp)
|
||||
.map(
|
||||
(x) => html`
|
||||
<li style="display: flex; flex-direction: row; gap: 4px">
|
||||
<span style="flex-basis: 3em"
|
||||
>${x?.content?.vote?.expression}</span
|
||||
<div
|
||||
class="w3-modal-content w3-card-4 w3-theme-d1"
|
||||
onclick="event.stopPropagation()"
|
||||
>
|
||||
<div class="w3-container w3-padding">
|
||||
<header class="w3-container">
|
||||
<h2>Reactions</h2>
|
||||
<span
|
||||
class="w3-button w3-display-topright"
|
||||
@click=${this.clear}
|
||||
>×</span
|
||||
>
|
||||
</header>
|
||||
<ul class="w3-theme-dark w3-container w3-ul">
|
||||
${this.votes
|
||||
.sort((x, y) => y.timestamp - x.timestamp)
|
||||
.map(
|
||||
(x) => html`
|
||||
<li
|
||||
style="display: flex; flex-direction: row; gap: 4px"
|
||||
>
|
||||
<tf-user
|
||||
style="flex: 1 1"
|
||||
id=${x.author}
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
<span
|
||||
style="flex-shrink: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
|
||||
>${new Date(x?.timestamp).toLocaleString()}</span
|
||||
>
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
</ul>
|
||||
<footer class="w3-container w3-padding">
|
||||
<button class="w3-button" @click=${this.clear}>Close</button>
|
||||
</footer>
|
||||
<span style="flex-basis: 3em"
|
||||
>${x?.content?.vote?.expression}</span
|
||||
>
|
||||
<tf-user
|
||||
style="flex: 1 1; overflow: hidden"
|
||||
id=${x.author}
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
<span
|
||||
style="flex-shrink: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
|
||||
>${new Date(x?.timestamp).toLocaleString()}</span
|
||||
>
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
</ul>
|
||||
<footer class="w3-container w3-padding">
|
||||
<button class="w3-button" @click=${this.clear}>Close</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
</div>`
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {css, unsafeCSS} from './lit-all.min.js';
|
||||
import {css, unsafeCSS, until} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
|
||||
const tf = css`
|
||||
img {
|
||||
@@ -43,6 +44,8 @@ const tf = css`
|
||||
border-left: 4px solid #fff;
|
||||
padding: 8px;
|
||||
padding-left: 12px;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -406,16 +409,8 @@ function is_dark(hex, value) {
|
||||
return (r * 299 + g * 587 + b * 114) / 1000 < value;
|
||||
}
|
||||
|
||||
function generated() {
|
||||
let now = new Date();
|
||||
let k_color = rgb_to_hex([
|
||||
(now.getDay() * 128) / 6,
|
||||
(now.getHours() * 128) / 23,
|
||||
(now.getSeconds() * 128) / 59,
|
||||
]);
|
||||
//let k_color = '#034f84';
|
||||
//let k_color = rgb_to_hex([Math.random() * 256, Math.random() * 256, Math.random() * 256]);
|
||||
let [r, g, b] = hex_to_rgb(k_color);
|
||||
export function generate(color) {
|
||||
let [r, g, b] = hex_to_rgb(color);
|
||||
let [h, s, l] = rgb_to_hsl(r, g, b);
|
||||
|
||||
let theme1 = {
|
||||
@@ -459,4 +454,28 @@ function generated() {
|
||||
return unsafeCSS(result);
|
||||
}
|
||||
|
||||
export let styles = [tf, w3, generated()];
|
||||
let g_theme;
|
||||
export function generate_theme() {
|
||||
return g_theme
|
||||
? g_theme
|
||||
: until(
|
||||
tfrpc.rpc.localStorageGet('color').then(function (value) {
|
||||
g_theme = generate(value ?? '#034f84');
|
||||
return g_theme;
|
||||
}),
|
||||
generated_now()
|
||||
);
|
||||
}
|
||||
|
||||
function generated_now() {
|
||||
let now = new Date();
|
||||
return generate(
|
||||
rgb_to_hex([
|
||||
(now.getDay() * 128) / 6,
|
||||
(now.getHours() * 128) / 23,
|
||||
(now.getSeconds() * 128) / 59,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
export let styles = [tf, w3];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {LitElement, html} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfTabConnectionsElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -15,6 +15,7 @@ class TfTabConnectionsElement extends LitElement {
|
||||
connect_attempt: {type: Object},
|
||||
connect_message: {type: String},
|
||||
connect_success: {type: Boolean},
|
||||
peer_exchange: {type: Boolean},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,6 +48,20 @@ class TfTabConnectionsElement extends LitElement {
|
||||
tfrpc.rpc.getServerIdentity().then(function (identity) {
|
||||
self.server_identity = identity;
|
||||
});
|
||||
this.check_peer_exchange();
|
||||
}
|
||||
|
||||
async check_peer_exchange() {
|
||||
if (await tfrpc.rpc.isAdministrator()) {
|
||||
this.peer_exchange = await tfrpc.rpc.globalSettingsGet('peer_exchange');
|
||||
} else {
|
||||
this.peer_exchange = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async enable_peer_exchange() {
|
||||
await tfrpc.rpc.globalSettingsSet('peer_exchange', true);
|
||||
await this.check_peer_exchange();
|
||||
}
|
||||
|
||||
render_connection_summary(connection) {
|
||||
@@ -251,7 +266,26 @@ class TfTabConnectionsElement extends LitElement {
|
||||
render() {
|
||||
let self = this;
|
||||
return html`
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<div class="w3-container" style="box-sizing: border-box">
|
||||
<div
|
||||
class=${'w3-panel w3-padding w3-theme-l3' +
|
||||
(this.peer_exchange !== false ? ' w3-hide' : '')}
|
||||
>
|
||||
<p>
|
||||
Looking for connections? Enabling this option will include publicly
|
||||
advertised rooms and pubs among the list of discovered connections
|
||||
to help you replicate.
|
||||
</p>
|
||||
<button
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${this.enable_peer_exchange}
|
||||
>
|
||||
🔍🌐 Use publicly advertised peers
|
||||
</button>
|
||||
</div>
|
||||
<h2>New Connection</h2>
|
||||
<textarea class="w3-input w3-theme-d1" id="code"></textarea>
|
||||
${this.render_message(this.renderRoot.getElementById('code')?.value)}
|
||||
@@ -277,7 +311,7 @@ class TfTabConnectionsElement extends LitElement {
|
||||
class="w3-button w3-block w3-theme-d1"
|
||||
@click=${() => self.toggle_accordian('broadcasts')}
|
||||
>
|
||||
Broadcasts (${this.valid_broadcasts().length})
|
||||
Discovery (${this.valid_broadcasts().length})
|
||||
</h2>
|
||||
<ul class="w3-ul w3-border w3-hide" id="broadcasts">
|
||||
${this.valid_broadcasts().map((x) => self.render_broadcast(x))}
|
||||
@@ -308,6 +342,12 @@ class TfTabConnectionsElement extends LitElement {
|
||||
<div class="w3-bar-item">
|
||||
<tf-user id=${x.pubkey} .users=${self.users}></tf-user>
|
||||
<div><small>${x.address}:${x.port}</small></div>
|
||||
<div>
|
||||
<small
|
||||
>Last connection:
|
||||
${new Date(x.last_success * 1000)}</small
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${this.render_message(x)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {LitElement, cache, html, unsafeHTML, until} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfTabNewsFeedElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -18,6 +18,8 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
time_range: {type: Array},
|
||||
time_loading: {type: Array},
|
||||
private_messages: {type: Array},
|
||||
grouped_private_messages: {type: Object},
|
||||
recent_reactions: {type: Array},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,6 +39,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
this.start_time = new Date().valueOf();
|
||||
this.time_range = [0, 0];
|
||||
this.time_loading = undefined;
|
||||
this.recent_reactions = [];
|
||||
this.loading = 0;
|
||||
}
|
||||
|
||||
@@ -104,8 +107,15 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
}
|
||||
|
||||
async fetch_messages(start_time, end_time) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('loadmessages', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
this.time_loading = [start_time, end_time];
|
||||
let result;
|
||||
const k_max_results = 64;
|
||||
if (this.hash == '#@') {
|
||||
result = await tfrpc.rpc.query(
|
||||
`
|
||||
@@ -116,7 +126,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
WHERE
|
||||
messages.author != ?1 AND
|
||||
(?3 IS NULL OR messages.timestamp >= ?3) AND messages.timestamp < ?4
|
||||
ORDER BY timestamp DESC limit 20)
|
||||
ORDER BY timestamp DESC limit ?5)
|
||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM mentions
|
||||
JOIN messages_refs ON mentions.id = messages_refs.ref
|
||||
@@ -129,6 +139,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
JSON.stringify(this.following),
|
||||
start_time,
|
||||
end_time,
|
||||
k_max_results,
|
||||
]
|
||||
);
|
||||
} else if (this.hash.startsWith('#@')) {
|
||||
@@ -138,7 +149,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
selected AS (SELECT rowid, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||
FROM messages
|
||||
WHERE messages.author = ?1 AND (?2 IS NULL OR messages.timestamp >= 2) AND messages.timestamp < ?3
|
||||
ORDER BY sequence DESC LIMIT 20
|
||||
ORDER BY sequence DESC LIMIT ?4
|
||||
)
|
||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM selected
|
||||
@@ -147,7 +158,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
UNION
|
||||
SELECT TRUE AS is_primary, * FROM selected
|
||||
`,
|
||||
[this.hash.substring(1), start_time, end_time]
|
||||
[this.hash.substring(1), start_time, end_time, k_max_results]
|
||||
);
|
||||
} else if (this.hash.startsWith('#%')) {
|
||||
result = await tfrpc.rpc.query(
|
||||
@@ -164,7 +175,6 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
[this.hash.substring(1)]
|
||||
);
|
||||
} else if (this.hash.startsWith('##')) {
|
||||
let t0 = new Date();
|
||||
let initial_messages = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH
|
||||
@@ -172,33 +182,30 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
WHERE messages.content ->> 'channel' = ?4
|
||||
WHERE messages.content ->> 'channel' = ?4 AND messages.content ->> 'type' != 'vote'
|
||||
UNION
|
||||
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages_fts(?5)
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
FROM messages_refs
|
||||
JOIN messages ON messages.id = messages_refs.message
|
||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
||||
JOIN json_tree(messages.content, '$.mentions') AS mention ON mention.value = '#' || ?4
|
||||
WHERE messages_refs.ref = '#' || ?4 AND messages.content ->> 'type' != 'vote'
|
||||
)
|
||||
SELECT TRUE AS is_primary, all_news.* FROM all_news
|
||||
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
|
||||
ORDER BY all_news.timestamp DESC LIMIT 20
|
||||
ORDER BY all_news.timestamp DESC LIMIT ?5
|
||||
`,
|
||||
[
|
||||
JSON.stringify(this.following),
|
||||
start_time,
|
||||
end_time,
|
||||
this.hash.substring(2),
|
||||
'"#' + this.hash.substring(2).replace('"', '""') + '"',
|
||||
k_max_results,
|
||||
]
|
||||
);
|
||||
let t1 = new Date();
|
||||
result = await this._fetch_related_messages(initial_messages);
|
||||
let t2 = new Date();
|
||||
console.log(
|
||||
`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
|
||||
);
|
||||
} else if (this.hash == '#🔐') {
|
||||
} else if (this.hash.startsWith('#🔐')) {
|
||||
let ids =
|
||||
this.hash == '#🔐' ? [] : this.hash.substring('#🔐'.length).split(',');
|
||||
result = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||
@@ -207,36 +214,65 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
WHERE
|
||||
(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND
|
||||
json(messages.content) LIKE '"%'
|
||||
ORDER BY messages.sequence DESC LIMIT 20
|
||||
ORDER BY messages.rowid DESC LIMIT ?4
|
||||
`,
|
||||
[JSON.stringify(this.private_messages), start_time, end_time]
|
||||
[
|
||||
JSON.stringify(
|
||||
this.grouped_private_messages?.[JSON.stringify(ids)]?.map(
|
||||
(x) => x.id
|
||||
) ?? []
|
||||
),
|
||||
start_time,
|
||||
end_time,
|
||||
k_max_results,
|
||||
]
|
||||
);
|
||||
result = (await this.decrypt(result)).filter((x) => x.decrypted);
|
||||
} else if (this.hash == '#👍') {
|
||||
result = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH votes AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages
|
||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
||||
WHERE
|
||||
messages.content ->> 'type' = 'vote' AND
|
||||
(?2 IS NULL OR messages.timestamp >= ?2) AND messages.timestamp < ?3
|
||||
ORDER BY timestamp DESC limit ?4)
|
||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM votes
|
||||
JOIN messages ON messages.id = votes.content ->> '$.vote.link'
|
||||
UNION
|
||||
SELECT TRUE AS is_primary, * FROM votes
|
||||
`,
|
||||
[JSON.stringify(this.following), start_time, end_time, k_max_results]
|
||||
);
|
||||
} else {
|
||||
let t0 = new Date();
|
||||
let initial_messages = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH
|
||||
all_news AS (
|
||||
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
channels AS (SELECT '#' || value AS value FROM json_each(?5))
|
||||
SELECT TRUE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
),
|
||||
news AS (
|
||||
SELECT * FROM all_news
|
||||
WHERE all_news.timestamp < ?3 AND (?2 IS NULL OR all_news.timestamp >= ?2)
|
||||
ORDER BY timestamp DESC LIMIT 20
|
||||
)
|
||||
SELECT TRUE AS is_primary, news.* FROM news
|
||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
||||
WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
|
||||
messages.content ->> 'type' != 'vote' AND
|
||||
(messages.content ->> 'root' IS NULL OR (
|
||||
NOT EXISTS (SELECT * FROM messages root JOIN channels ON ('#' || (root.content ->> 'channel')) = channels.value WHERE root.id = messages.content ->> 'root') AND
|
||||
NOT EXISTS (SELECT * FROM messages root JOIN messages_refs ON root.id = messages.content ->> 'root' JOIN channels ON messages_refs.message = root.id AND messages_refs.ref = channels.value)
|
||||
)) AND
|
||||
(messages.content ->> 'channel' IS NULL OR ('#' || (messages.content ->> 'channel')) NOT IN (SELECT * FROM channels)) AND
|
||||
NOT EXISTS (SELECT * FROM messages_refs JOIN channels ON messages_refs.message = messages.id AND messages_refs.ref = channels.value)
|
||||
ORDER BY timestamp DESC LIMIT ?4
|
||||
`,
|
||||
[JSON.stringify(this.following), start_time, end_time]
|
||||
[
|
||||
JSON.stringify(this.following),
|
||||
start_time,
|
||||
end_time,
|
||||
k_max_results,
|
||||
JSON.stringify(Object.keys(this.channels_latest)),
|
||||
]
|
||||
);
|
||||
let t1 = new Date();
|
||||
result = await this._fetch_related_messages(initial_messages);
|
||||
let t2 = new Date();
|
||||
console.log(
|
||||
`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
|
||||
);
|
||||
}
|
||||
this.time_loading = undefined;
|
||||
return result;
|
||||
@@ -256,6 +292,13 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
];
|
||||
}
|
||||
|
||||
unread_allowed() {
|
||||
return (
|
||||
this.hash == '#@' ||
|
||||
(!this.hash.startsWith('#%') && !this.hash.startsWith('#@'))
|
||||
);
|
||||
}
|
||||
|
||||
async load_more() {
|
||||
this.loading++;
|
||||
this.loading_canceled = false;
|
||||
@@ -345,12 +388,20 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
let self = this;
|
||||
this.loading++;
|
||||
let messages = [];
|
||||
let original_hash = this.hash;
|
||||
try {
|
||||
if (this._messages_hash !== this.hash) {
|
||||
this.messages = [];
|
||||
this._messages_hash = this.hash;
|
||||
}
|
||||
this._messages_following = this.following;
|
||||
this._messages_following = JSON.stringify(this.following);
|
||||
this._private_messages = JSON.stringify([
|
||||
this.private_messages,
|
||||
this.grouped_private_messages,
|
||||
]);
|
||||
this._channels_latest = JSON.stringify(
|
||||
Object.keys(this.channels_latest ?? {})
|
||||
);
|
||||
let now = new Date().valueOf();
|
||||
let start_time = now - 24 * 60 * 60 * 1000;
|
||||
this.start_time = start_time;
|
||||
@@ -363,7 +414,9 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
} finally {
|
||||
this.loading--;
|
||||
}
|
||||
this.messages = this.merge_messages(this.messages, messages);
|
||||
if (this.hash == original_hash) {
|
||||
this.messages = this.merge_messages(this.messages, messages);
|
||||
}
|
||||
this.time_loading = undefined;
|
||||
console.log(
|
||||
`loading ${messages.length} messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s`
|
||||
@@ -389,15 +442,49 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
close_private_chat() {
|
||||
this.mark_all_read();
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('closeprivatechat', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
key: JSON.stringify(
|
||||
this.hash == '#🔐'
|
||||
? []
|
||||
: this.hash.substring('#🔐'.length).split(',')
|
||||
),
|
||||
},
|
||||
})
|
||||
);
|
||||
tfrpc.rpc.setHash('#');
|
||||
}
|
||||
|
||||
render_close_chat_button() {
|
||||
if (this.hash.startsWith('#🔐')) {
|
||||
return html`
|
||||
<button class="w3-button w3-theme-d1" @click=${this.close_private_chat}>
|
||||
Close Chat
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (
|
||||
!this.messages ||
|
||||
this._messages_hash !== this.hash ||
|
||||
JSON.stringify(this._messages_following) !==
|
||||
JSON.stringify(this.following)
|
||||
this._messages_following !== JSON.stringify(this.following) ||
|
||||
this._private_messages !==
|
||||
JSON.stringify([
|
||||
this.private_messages,
|
||||
this.grouped_private_messages,
|
||||
]) ||
|
||||
this._channels_latest !==
|
||||
JSON.stringify(Object.keys(this.channels_latest))
|
||||
) {
|
||||
console.log(
|
||||
`loading messages for ${this.whoami} (following ${this.following.length})`
|
||||
`loading messages for ${this.whoami} (messages=${!this.messages},${this._messages_hash != this.hash} following=${this._messages_following !== JSON.stringify(this.following)}, channels=${this._channels_latest !== JSON.stringify(Object.keys(this.channels_latest))}, private=${this._private_messages !== JSON.stringify([this.private_messages, this.grouped_private_messages])},${this.private_messages?.length},${Object.keys(this.grouped_private_messages ?? {}).length})`
|
||||
);
|
||||
this.load_messages();
|
||||
}
|
||||
@@ -405,9 +492,16 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
if (!this.hash.startsWith('#%')) {
|
||||
more = html`
|
||||
<p>
|
||||
<button class="w3-button w3-theme-d1" @click=${this.mark_all_read}>
|
||||
Mark All Read
|
||||
</button>
|
||||
${this.unread_allowed()
|
||||
? html`
|
||||
<button
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${this.mark_all_read}
|
||||
>
|
||||
Mark All Read
|
||||
</button>
|
||||
`
|
||||
: undefined}
|
||||
<button
|
||||
?disabled=${this.loading}
|
||||
class="w3-button w3-theme-d1"
|
||||
@@ -439,9 +533,18 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
`;
|
||||
}
|
||||
return cache(html`
|
||||
<button class="w3-button w3-theme-d1" @click=${this.mark_all_read}>
|
||||
Mark All Read
|
||||
</button>
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
${this.unread_allowed()
|
||||
? html`<button
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${this.mark_all_read}
|
||||
>
|
||||
Mark All Read
|
||||
</button>`
|
||||
: undefined}
|
||||
${this.render_close_chat_button()}
|
||||
<tf-news
|
||||
id="news"
|
||||
whoami=${this.whoami}
|
||||
@@ -450,8 +553,11 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
.following=${this.following}
|
||||
.drafts=${this.drafts}
|
||||
.expanded=${this.expanded}
|
||||
hash=${this.hash}
|
||||
channel=${this.channel()}
|
||||
channel_unread=${this.channels_unread?.[this.channel()]}
|
||||
.recent_reactions=${this.recent_reactions}
|
||||
@mark_all_read=${this.mark_all_read}
|
||||
></tf-news>
|
||||
${more}
|
||||
`);
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
until,
|
||||
} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfTabNewsElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -24,6 +24,12 @@ class TfTabNewsElement extends LitElement {
|
||||
channels_latest: {type: Object},
|
||||
connections: {type: Array},
|
||||
private_messages: {type: Array},
|
||||
grouped_private_messages: {type: Object},
|
||||
visible_private_messages: {type: Object},
|
||||
recent_reactions: {type: Array},
|
||||
peer_exchange: {type: Boolean},
|
||||
is_administrator: {type: Boolean},
|
||||
stay_connected: {type: Boolean},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,9 +49,11 @@ class TfTabNewsElement extends LitElement {
|
||||
this.channels_latest = {};
|
||||
this.channels = [];
|
||||
this.connections = [];
|
||||
this.recent_reactions = [];
|
||||
tfrpc.rpc.localStorageGet('drafts').then(function (d) {
|
||||
self.drafts = JSON.parse(d || '{}');
|
||||
});
|
||||
this.check_peer_exchange();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
@@ -58,6 +66,14 @@ class TfTabNewsElement extends LitElement {
|
||||
document.body.removeEventListener('keypress', this.on_keypress.bind(this));
|
||||
}
|
||||
|
||||
async check_peer_exchange() {
|
||||
if (await tfrpc.rpc.isAdministrator()) {
|
||||
this.peer_exchange = await tfrpc.rpc.globalSettingsGet('peer_exchange');
|
||||
} else {
|
||||
this.peer_exchange = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
load_latest() {
|
||||
let news = this.shadowRoot?.getElementById('news');
|
||||
if (news) {
|
||||
@@ -95,7 +111,26 @@ class TfTabNewsElement extends LitElement {
|
||||
}
|
||||
|
||||
unread_status(channel) {
|
||||
if (
|
||||
if (channel === undefined) {
|
||||
if (
|
||||
Object.keys(this.channels_unread).some((x) => this.unread_status(x))
|
||||
) {
|
||||
return '✉️ ';
|
||||
}
|
||||
} else if (channel?.startsWith('🔐')) {
|
||||
let key = JSON.stringify(channel.substring('🔐'.length).split(','));
|
||||
if (this.grouped_private_messages?.[key]) {
|
||||
let grouped_latest = Math.max(
|
||||
...this.grouped_private_messages?.[key]?.map((x) => x.rowid)
|
||||
);
|
||||
if (
|
||||
this.channels_unread[channel] === undefined ||
|
||||
grouped_latest > this.channels_unread[channel]
|
||||
) {
|
||||
return '✉️ ';
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
this.channels_latest[channel] &&
|
||||
this.channels_latest[channel] > 0 &&
|
||||
(this.channels_unread[channel] === undefined ||
|
||||
@@ -136,11 +171,8 @@ class TfTabNewsElement extends LitElement {
|
||||
return this.hash.startsWith('##') ? this.hash.substring(2) : undefined;
|
||||
}
|
||||
|
||||
compare_follows() {
|
||||
const now = new Date().valueOf();
|
||||
return function (a, b) {
|
||||
return (b[1].ts > now ? -1 : b[1].ts) - (a[1].ts > now ? -1 : a[1].ts);
|
||||
};
|
||||
compare_follows(a, b) {
|
||||
return b[1].ts > a[1].ts ? 1 : b[1].ts < a[1].ts ? -1 : 0;
|
||||
}
|
||||
|
||||
suggested_follows() {
|
||||
@@ -149,13 +181,24 @@ class TfTabNewsElement extends LitElement {
|
||||
** pinned at the top.
|
||||
*/
|
||||
let self = this;
|
||||
let now = new Date().valueOf();
|
||||
return Object.entries(this.users)
|
||||
.filter((x) => x[1].ts < now)
|
||||
.filter((x) => x[1].follow_depth > 1)
|
||||
.sort(self.compare_follows())
|
||||
.sort(self.compare_follows)
|
||||
.slice(0, 8)
|
||||
.map((x) => x[0]);
|
||||
}
|
||||
|
||||
async enable_peer_exchange() {
|
||||
await tfrpc.rpc.globalSettingsSet('peer_exchange', true);
|
||||
await this.check_peer_exchange();
|
||||
}
|
||||
|
||||
is_loading() {
|
||||
return this.shadowRoot?.getElementById('news')?.loading;
|
||||
}
|
||||
|
||||
render_sidebar() {
|
||||
return html`
|
||||
<div
|
||||
@@ -177,7 +220,7 @@ class TfTabNewsElement extends LitElement {
|
||||
href="#"
|
||||
class="w3-bar-item w3-button"
|
||||
style="font-weight: bold"
|
||||
>${this.hash.substring(2)}</a
|
||||
>${this.hash.substring(1)}</a
|
||||
>
|
||||
`
|
||||
: undefined}
|
||||
@@ -195,11 +238,34 @@ class TfTabNewsElement extends LitElement {
|
||||
>${this.unread_status('@')}@mentions</a
|
||||
>
|
||||
<a
|
||||
href="#🔐"
|
||||
href="#👍"
|
||||
class="w3-bar-item w3-button"
|
||||
style=${this.hash == '#🔐' ? 'font-weight: bold' : undefined}
|
||||
>${this.unread_status('🔐')}🔐private</a
|
||||
style=${this.hash == '#👍' ? 'font-weight: bold' : undefined}
|
||||
>${this.unread_status('👍')}👍votes</a
|
||||
>
|
||||
${Object.keys(this?.visible_private_messages ?? [])
|
||||
?.sort()
|
||||
?.map(
|
||||
(key) => html`
|
||||
<a
|
||||
href=${'#🔐' + JSON.parse(key).join(',')}
|
||||
class="w3-bar-item w3-button"
|
||||
style=${this.hash == '#🔐' + JSON.parse(key).join(',')
|
||||
? 'font-weight: bold'
|
||||
: undefined}
|
||||
>${this.unread_status('🔐' + JSON.parse(key).join(','))}
|
||||
${(key != '[]' ? JSON.parse(key) : [this.whoami]).map(
|
||||
(id) => html`
|
||||
<tf-user
|
||||
id=${id}
|
||||
nolink="true"
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
`
|
||||
)}</a
|
||||
>
|
||||
`
|
||||
)}
|
||||
${Object.keys(this.drafts)
|
||||
.sort()
|
||||
.map(
|
||||
@@ -223,14 +289,60 @@ class TfTabNewsElement extends LitElement {
|
||||
`
|
||||
)}
|
||||
|
||||
<h4 class="w3-bar-item w3-theme-d2">Connections</h4>
|
||||
<a class="w3-bar-item w3-theme-d2 w3-button" href="#connections">
|
||||
<h4 style="margin: 0">Connections</h4>
|
||||
</a>
|
||||
${this.connections?.filter((x) => x.id)?.length == 0
|
||||
? html`
|
||||
<button
|
||||
class=${'w3-bar-item w3-button' +
|
||||
(this.connections?.some((x) => x.flags.one_shot)
|
||||
? ' w3-spin'
|
||||
: '')}
|
||||
@click=${() =>
|
||||
this.dispatchEvent(
|
||||
new Event('refresh', {bubbles: true, composed: true})
|
||||
)}
|
||||
>
|
||||
↻ Sync now
|
||||
</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-ripple"
|
||||
@click=${() =>
|
||||
this.dispatchEvent(
|
||||
new Event('toggle_stay_connected', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
)}
|
||||
>
|
||||
<span style="display: inline-block; width: 1.8em"
|
||||
>${this.stay_connected ? '🔗' : '⛓️💥'}</span
|
||||
>
|
||||
${this.stay_connected ? 'Online mode' : 'Passive mode'}
|
||||
</button>
|
||||
<button
|
||||
class=${'w3-bar-item w3-button w3-border w3-leftbar w3-rightbar' +
|
||||
(this.peer_exchange !== false ? ' w3-hide' : '')}
|
||||
@click=${this.enable_peer_exchange}
|
||||
>
|
||||
🔍🌐 Use publicly advertised peers
|
||||
</button>
|
||||
`
|
||||
: undefined}
|
||||
${this.connections
|
||||
.filter((x) => x.id && !x.destroy_reason)
|
||||
.filter((x) => x.id)
|
||||
.map(
|
||||
(x) => html`
|
||||
<tf-user
|
||||
class="w3-bar-item"
|
||||
style="max-width: 100%"
|
||||
style=${x.destroy_reason
|
||||
? 'border-left: 4px solid red; border-right: 4px solid red'
|
||||
: x.connected
|
||||
? x.flags?.one_shot
|
||||
? 'border-left: 4px solid blue; border-right: 4px solid blue'
|
||||
: 'border-left: 4px solid green; border-right: 4px solid green'
|
||||
: ''}
|
||||
id=${x.id}
|
||||
fallback_name=${x.host}
|
||||
.users=${this.users}
|
||||
@@ -284,9 +396,12 @@ class TfTabNewsElement extends LitElement {
|
||||
</div>`;
|
||||
}
|
||||
return cache(html`
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
${this.render_sidebar()}
|
||||
<div
|
||||
style="margin-left: 2in; padding: 0px; top: 0; max-height: 100%; overflow: auto; contain: layout"
|
||||
style="margin-left: 2in; padding: 0px; top: 0; height: 100vh; max-height: 100%; overflow: auto; contain: layout"
|
||||
id="main"
|
||||
class="w3-main"
|
||||
>
|
||||
@@ -311,7 +426,7 @@ class TfTabNewsElement extends LitElement {
|
||||
class="w3-button w3-hide-large"
|
||||
@click=${this.show_sidebar}
|
||||
>
|
||||
☰
|
||||
${this.unread_status()}☰
|
||||
</div>
|
||||
Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
|
||||
${edit_profile}
|
||||
@@ -324,6 +439,9 @@ class TfTabNewsElement extends LitElement {
|
||||
.drafts=${this.drafts}
|
||||
@tf-draft=${this.draft}
|
||||
.channel=${this.channel()}
|
||||
.recipients=${this.hash.startsWith('#🔐')
|
||||
? this.hash.substring('#🔐'.length).split(',')
|
||||
: undefined}
|
||||
></tf-compose>
|
||||
</div>
|
||||
${profile}
|
||||
@@ -340,6 +458,8 @@ class TfTabNewsElement extends LitElement {
|
||||
.channels_unread=${this.channels_unread}
|
||||
.channels_latest=${this.channels_latest}
|
||||
.private_messages=${this.private_messages}
|
||||
.grouped_private_messages=${this.grouped_private_messages}
|
||||
.recent_reactions=${this.recent_reactions}
|
||||
></tf-tab-news-feed>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
|
||||
class TfTabQueryElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
whoami: {type: String},
|
||||
users: {type: Object},
|
||||
following: {type: Array},
|
||||
query: {type: String},
|
||||
expanded: {type: Object},
|
||||
results: {type: Array},
|
||||
error: {type: Object},
|
||||
duration: {type: Number},
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
let self = this;
|
||||
this.whoami = null;
|
||||
this.users = {};
|
||||
this.following = [];
|
||||
this.expanded = {};
|
||||
this.duration = undefined;
|
||||
}
|
||||
|
||||
async search(query) {
|
||||
console.log('Searching...', this.whoami, query);
|
||||
this.results = [];
|
||||
this.error = undefined;
|
||||
this.duration = undefined;
|
||||
let search = this.renderRoot.getElementById('search');
|
||||
if (search) {
|
||||
search.value = query;
|
||||
search.focus();
|
||||
}
|
||||
await tfrpc.rpc.setHash('#sql=' + encodeURIComponent(query));
|
||||
let start_time = new Date();
|
||||
try {
|
||||
this.results = await tfrpc.rpc.query(query, []);
|
||||
} catch (error) {
|
||||
this.error = error;
|
||||
}
|
||||
let end_time = new Date();
|
||||
this.duration = (end_time - start_time).valueOf();
|
||||
console.log('Done.');
|
||||
search = this.renderRoot.getElementById('search');
|
||||
if (search) {
|
||||
search.value = query;
|
||||
search.focus();
|
||||
}
|
||||
}
|
||||
|
||||
search_keydown(event) {
|
||||
if (event.keyCode == 13 && event.ctrlKey) {
|
||||
this.query = this.renderRoot.getElementById('search').value;
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
on_expand(event) {
|
||||
if (event.detail.expanded) {
|
||||
let expand = {};
|
||||
expand[event.detail.id] = true;
|
||||
this.expanded = Object.assign({}, this.expanded, expand);
|
||||
} else {
|
||||
delete this.expanded[event.detail.id];
|
||||
this.expanded = Object.assign({}, this.expanded);
|
||||
}
|
||||
}
|
||||
|
||||
render_results() {
|
||||
if (!this.results?.length) {
|
||||
return html`<div>No results.</div>`;
|
||||
} else {
|
||||
let keys = Object.keys(this.results[0]).sort();
|
||||
return html`<table style="width: 100%; max-width: 100%">
|
||||
<tr>
|
||||
${keys.map((key) => html`<th>${key}</th>`)}
|
||||
</tr>
|
||||
${this.results.map(
|
||||
(row) =>
|
||||
html`<tr>
|
||||
${keys.map((key) => html`<td>${row[key]}</td>`)}
|
||||
</tr>`
|
||||
)}
|
||||
</table>`;
|
||||
}
|
||||
}
|
||||
|
||||
render_error() {
|
||||
if (this.error) {
|
||||
return html`<h2 style="color: red">${this.error.message}</h2>
|
||||
<pre style="color: red">${this.error.stack}</pre>`;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.query !== this.last_query) {
|
||||
this.last_query = this.query;
|
||||
this.search(this.query);
|
||||
}
|
||||
let self = this;
|
||||
return html`
|
||||
<div style="display: flex; flex-direction: row; gap: 4px">
|
||||
<textarea
|
||||
id="search"
|
||||
rows="8"
|
||||
class="w3-input w3-theme-d1"
|
||||
style="flex: 1; resize: vertical"
|
||||
@keydown=${this.search_keydown}
|
||||
>
|
||||
${this.query}</textarea
|
||||
>
|
||||
<button
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${(event) =>
|
||||
self.search(self.renderRoot.getElementById('search').value)}
|
||||
>
|
||||
Execute
|
||||
</button>
|
||||
</div>
|
||||
<div ?hidden=${this.duration === undefined}>
|
||||
Took ${this.duration / 1000.0} seconds.
|
||||
</div>
|
||||
<div ?hidden=${this.duration !== undefined}>Executing...</div>
|
||||
${this.render_error()} ${this.render_results()}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tf-tab-query', TfTabQueryElement);
|
||||
@@ -1,6 +1,6 @@
|
||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfTabSearchElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -11,6 +11,9 @@ class TfTabSearchElement extends LitElement {
|
||||
following: {type: Array},
|
||||
query: {type: String},
|
||||
expanded: {type: Object},
|
||||
messages: {type: Array},
|
||||
results: {type: Array},
|
||||
error: {type: Object},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,24 +41,40 @@ class TfTabSearchElement extends LitElement {
|
||||
search.select();
|
||||
}
|
||||
await tfrpc.rpc.setHash('#q=' + encodeURIComponent(query));
|
||||
let results = await tfrpc.rpc.query(
|
||||
`
|
||||
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
|
||||
ORDER BY timestamp DESC limit 100
|
||||
`,
|
||||
['"' + query.replace('"', '""') + '"', JSON.stringify(this.following)]
|
||||
);
|
||||
console.log('Done.');
|
||||
search = this.renderRoot.getElementById('search');
|
||||
if (search) {
|
||||
search.value = query;
|
||||
search.focus();
|
||||
search.select();
|
||||
this.error = undefined;
|
||||
this.results = [];
|
||||
this.messages = [];
|
||||
if (query.startsWith('sql:')) {
|
||||
this.messages = [];
|
||||
try {
|
||||
this.results = await tfrpc.rpc.query(
|
||||
query.substring('sql:'.length),
|
||||
[]
|
||||
);
|
||||
} catch (e) {
|
||||
this.results = [];
|
||||
this.error = e;
|
||||
}
|
||||
} else {
|
||||
let results = await tfrpc.rpc.query(
|
||||
`
|
||||
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
|
||||
ORDER BY timestamp DESC limit 100
|
||||
`,
|
||||
['"' + query.replace('"', '""') + '"', JSON.stringify(this.following)]
|
||||
);
|
||||
console.log('Done.');
|
||||
search = this.renderRoot.getElementById('search');
|
||||
if (search) {
|
||||
search.value = query;
|
||||
search.focus();
|
||||
search.select();
|
||||
}
|
||||
this.messages = results;
|
||||
}
|
||||
this.renderRoot.getElementById('news').messages = results;
|
||||
}
|
||||
|
||||
search_keydown(event) {
|
||||
@@ -87,6 +106,39 @@ class TfTabSearchElement extends LitElement {
|
||||
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
|
||||
}
|
||||
|
||||
render_results() {
|
||||
if (this.error) {
|
||||
return html`<h2 style="color: red">${this.error.message}</h2>
|
||||
<pre style="color: red">${this.error.stack}</pre>`;
|
||||
} else if (this.messages?.length) {
|
||||
return html`<tf-news
|
||||
id="news"
|
||||
whoami=${this.whoami}
|
||||
.messages=${this.messages}
|
||||
.users=${this.users}
|
||||
.expanded=${this.expanded}
|
||||
.drafts=${this.drafts}
|
||||
@tf-expand=${this.on_expand}
|
||||
@tf-draft=${this.draft}
|
||||
></tf-news>`;
|
||||
} else if (this.results?.length) {
|
||||
let keys = Object.keys(this.results[0]).sort();
|
||||
return html`<table style="width: 100%; max-width: 100%">
|
||||
<tr>
|
||||
${keys.map((key) => html`<th>${key}</th>`)}
|
||||
</tr>
|
||||
${this.results.map(
|
||||
(row) =>
|
||||
html`<tr>
|
||||
${keys.map((key) => html`<td>${row[key]}</td>`)}
|
||||
</tr>`
|
||||
)}
|
||||
</table>`;
|
||||
} else {
|
||||
return html`<div>No results.</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.query !== this.last_query) {
|
||||
this.last_query = this.query;
|
||||
@@ -94,11 +146,14 @@ class TfTabSearchElement extends LitElement {
|
||||
}
|
||||
let self = this;
|
||||
return html`
|
||||
<div style="display: flex; flex-direction: row; gap: 4px">
|
||||
<input type="text" class="w3-input w3-theme-d1" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
|
||||
<button class="w3-button w3-theme-d1" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button>
|
||||
<style>${generate_theme()}</style>
|
||||
<div class="w3-padding">
|
||||
<div style="display: flex; flex-direction: row; gap: 4px">
|
||||
<input type="text" class="w3-input w3-theme-d1" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
|
||||
<button class="w3-button w3-theme-d1" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button>
|
||||
</div>
|
||||
${this.render_results()}
|
||||
</div>
|
||||
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} .drafts=${this.drafts} @tf-expand=${this.on_expand} @tf-draft=${this.draft}></tf-news>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfTagElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -17,11 +17,15 @@ class TfTagElement extends LitElement {
|
||||
|
||||
render() {
|
||||
let number = this.count ? html` (${this.count})` : undefined;
|
||||
return html`<a
|
||||
href=${'#' + encodeURIComponent(this.tag)}
|
||||
class="w3-tag w3-theme-d1 w3-round-4 w3-button"
|
||||
>${this.tag}${number}</a
|
||||
> `;
|
||||
return html`
|
||||
<style>
|
||||
${generate_theme()}</style
|
||||
><a
|
||||
href=${'#' + encodeURIComponent(this.tag)}
|
||||
class="w3-tag w3-theme-d1 w3-round-4 w3-button"
|
||||
>${this.tag}${number}</a
|
||||
>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import {LitElement, html} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfUserElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
id: {type: String},
|
||||
fallback_name: {type: String},
|
||||
icon_only: {type: Boolean},
|
||||
users: {type: Object},
|
||||
nolink: {type: Boolean},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,6 +19,7 @@ class TfUserElement extends LitElement {
|
||||
super();
|
||||
this.id = null;
|
||||
this.fallback_name = null;
|
||||
this.icon_only = false;
|
||||
this.users = {};
|
||||
}
|
||||
|
||||
@@ -32,27 +35,38 @@ class TfUserElement extends LitElement {
|
||||
>😎</span
|
||||
>`;
|
||||
let name = this.users?.[this.id]?.name;
|
||||
name = html`<a target="_top" href=${'#' + this.id}
|
||||
>${name ?? this.fallback_name ?? this.id}</a
|
||||
>`;
|
||||
let name_string = name ?? this.fallback_name ?? this.id;
|
||||
name = this.icon_only
|
||||
? undefined
|
||||
: !this.nolink
|
||||
? html`<a target="_top" href=${'#' + this.id}>${name_string}</a>`
|
||||
: html`<span>${name_string}</span>`;
|
||||
|
||||
if (user) {
|
||||
let image_link = user.image;
|
||||
image_link =
|
||||
typeof image_link == 'string' ? image_link : image_link?.link;
|
||||
if (typeof image_link == 'string' && !image_link.startsWith('&')) {
|
||||
try {
|
||||
image_link = JSON.parse(image_link)?.link;
|
||||
} catch {}
|
||||
}
|
||||
if (image_link !== undefined) {
|
||||
image = html`<img
|
||||
class=${'w3-theme-l4 ' + shape}
|
||||
style="width: 2em; height: 2em; vertical-align: middle; object-fit: cover"
|
||||
src="/${image_link}/view"
|
||||
title=${name_string + ' (' + this.id + ')'}
|
||||
/>`;
|
||||
}
|
||||
}
|
||||
return html` <div
|
||||
style="display: inline-block; vertical-align: middle; font-weight: bold; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis"
|
||||
>
|
||||
${image} ${name}
|
||||
</div>`;
|
||||
return html` <style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<div
|
||||
style=${'display: inline-block; vertical-align: middle; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis' +
|
||||
(this.nolink ? '' : '; font-weight: bold')}
|
||||
>
|
||||
${image} ${name}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,9 +50,9 @@ function image(node, entering) {
|
||||
'</div>'
|
||||
);
|
||||
if (this.options.safe && potentiallyUnsafe(node.destination)) {
|
||||
this.lit('<img src="" alt="');
|
||||
this.lit('<img src="" title="');
|
||||
} else {
|
||||
this.lit('<img src="' + this.esc(node.destination) + '" alt="');
|
||||
this.lit('<img src="' + this.esc(node.destination) + '" title="');
|
||||
}
|
||||
}
|
||||
this.disableTags += 1;
|
||||
@@ -104,12 +104,12 @@ export function markdown(md) {
|
||||
node.destination.startsWith('@') &&
|
||||
node.destination.endsWith('.ed25519')
|
||||
) {
|
||||
node.destination = '#' + node.destination;
|
||||
node.destination = '#' + encodeURIComponent(node.destination);
|
||||
} else if (
|
||||
node.destination.startsWith('%') &&
|
||||
node.destination.endsWith('.sha256')
|
||||
) {
|
||||
node.destination = '#' + node.destination;
|
||||
node.destination = '#' + encodeURIComponent(node.destination);
|
||||
} else if (
|
||||
node.destination.startsWith('&') &&
|
||||
node.destination.endsWith('.sha256')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "👋",
|
||||
"previous": "&wAb7J6E35xEXpiXsQ6t1RaWTGIvlatUnyH8ipF6pVic=.sha256"
|
||||
"previous": "&n1QkPkB5JoduFSx8UKOY3IlZqS2GwLiTUZv4ZrEOthQ=.sha256"
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
async function main() {
|
||||
await app.setDocument(utf8Decode(getFile('index.html')));
|
||||
}
|
||||
|
||||
main();
|
||||
1
apps/welcome/gitea.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 640 640" width="32" height="32"><path d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12" style="fill:#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6M125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1m300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1" style="fill:#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8s2 16.3 9.1 20c7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3s17.4 1.7 22.5-5.3c5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8z" style="fill:#609926"/></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
1521
apps/welcome/hermietildefriends.svg
Normal file
|
After Width: | Height: | Size: 86 KiB |
@@ -28,65 +28,39 @@
|
||||
<b>😎 Tilde Friends</b>
|
||||
</h1>
|
||||
<h1 class="w3-xxlarge w3-text-green">
|
||||
<b
|
||||
>the Secure Scuttlebutt decentralized social network client that's
|
||||
<i>fancy🎩</i></b
|
||||
>
|
||||
<b>a Secure Scuttlebutt decentralized social network client</b>
|
||||
</h1>
|
||||
<p>
|
||||
In addition to participating in Secure Scuttlebutt, Tilde Friends is
|
||||
a platform for building, running, and sharing applications.
|
||||
</p>
|
||||
<p>
|
||||
Available for lots of devices:
|
||||
<i class="fa-brands fa-linux w3-xlarge"></i>
|
||||
<i class="fa-brands fa-android w3-xlarge"></i>
|
||||
<i class="fa-brands fa-apple w3-xlarge"></i>
|
||||
<i class="fa fa-mobile-screen w3-xlarge"></i>
|
||||
<i class="fa-brands fa-windows w3-xlarge"></i>
|
||||
</p>
|
||||
<a
|
||||
class="w3-button w3-blue w3-padding-large"
|
||||
href="https://www.tildefriends.net/~core/ssb/"
|
||||
>🦀 Try It</a
|
||||
>
|
||||
<a
|
||||
class="w3-button w3-black w3-padding-large"
|
||||
href="https://dev.tildefriends.net/cory/tildefriends/releases"
|
||||
href="https://dev.tildefriends.net/cory/tildefriends/releases/latest"
|
||||
><i class="fa fa-download"></i> Download</a
|
||||
>
|
||||
<a
|
||||
class="w3-button w3-black w3-padding-large"
|
||||
href="https://www.tildefriends.net/~core/ssb/"
|
||||
><i class="fa fa-link"></i> Try It</a
|
||||
>
|
||||
<a
|
||||
class="w3-button w3-black w3-padding-large"
|
||||
href="https://dev.tildefriends.net/"
|
||||
><i class="fa fa-mug-hot"></i> Development</a
|
||||
href="https://dev.tildefriends.net/cory/tildefriends"
|
||||
>
|
||||
<img src="gitea.svg" style="height: 1em; margin: 0" />
|
||||
Development
|
||||
</a>
|
||||
<a
|
||||
class="w3-button w3-black w3-padding-large"
|
||||
href="https://docs.tildefriends.net/"
|
||||
><i class="fa fa-book"></i> Documentation</a
|
||||
>
|
||||
<p>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
href="https://f-droid.org/en/packages/com.unprompted.tildefriends.fdroid/"
|
||||
><img src="f-droid.svg" style="height: 2em; margin: 0" /> Get it
|
||||
on F-Droid</a
|
||||
>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
href="https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage"
|
||||
>
|
||||
<img src="appimage.svg" style="height: 2em; margin: 0" />
|
||||
Get Linux 64-bit AppImage
|
||||
</a>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
href="https://play.google.com/store/apps/details?id=com.unprompted.tildefriends"
|
||||
>
|
||||
<img src="googleplay.svg" style="height: 2em; margin: 0" />
|
||||
Get it on Google Play (Open Testing)
|
||||
</a>
|
||||
</p>
|
||||
<a
|
||||
class="w3-button w3-black w3-padding-large"
|
||||
href="https://www.tildefriends.net/~cory/tildeblog/"
|
||||
><i class="fa fa-solid fa-square-rss"></i> Blog</a
|
||||
>
|
||||
</div>
|
||||
<div class="w3-col l4 m6">
|
||||
<img src="tildefriends.png" class="w3-image w3-right w3-hide-small" />
|
||||
@@ -104,15 +78,119 @@
|
||||
<h2>First-time user checklist:</h2>
|
||||
<ol type="1" style="text-align: left">
|
||||
<li>
|
||||
<a href="https://dev.tildefriends.net/cory/tildefriends/releases"
|
||||
<a
|
||||
href="https://dev.tildefriends.net/cory/tildefriends/releases/latest"
|
||||
>Download</a
|
||||
>
|
||||
Tilde Friends or use
|
||||
<a href="https://www.tildefriends.net/"
|
||||
>https://www.tildefriends.net/</a
|
||||
>.
|
||||
<div class="w3-cell-row">
|
||||
<div class="w3-container w3-cell w3-mobile">
|
||||
<h3>Mobile</h3>
|
||||
<p>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
href="https://f-droid.org/en/packages/com.unprompted.tildefriends.fdroid/"
|
||||
><img src="f-droid.svg" style="height: 2em; margin: 0" />
|
||||
Get it on F-Droid</a
|
||||
>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
href="https://play.google.com/store/apps/details?id=com.unprompted.tildefriends"
|
||||
>
|
||||
<img
|
||||
src="googleplay.svg"
|
||||
style="height: 2em; margin: 0"
|
||||
/>
|
||||
Get it on Google Play
|
||||
</a>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
href="https://testflight.apple.com/join/tXxgtSpE"
|
||||
>
|
||||
<img src="ios.svg" style="height: 2em; margin: 0" />
|
||||
Get it on iOS (TestFlight)
|
||||
</a>
|
||||
</p>
|
||||
<p>Just launch the app.</p>
|
||||
</div>
|
||||
<div class="w3-container w3-cell w3-mobile">
|
||||
<h3>Web</h3>
|
||||
<p>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-blue w3-padding-large"
|
||||
href="https://www.tildefriends.net/~core/ssb/"
|
||||
>🦀 Try It</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<a href="/login?return=/~core/intro"
|
||||
>Register an account with tildefriends.net</a
|
||||
>
|
||||
to take it for a spin right away.
|
||||
</p>
|
||||
<h3>PeachCloud</h3>
|
||||
<p>
|
||||
Tilde Friends is also a part of 🍑☁️<a
|
||||
href="https://peach-docs.commoninternet.net/"
|
||||
>PeachCloud</a
|
||||
>, which is available on
|
||||
<a href="https://apps.yunohost.org/app/peachpub"
|
||||
>YunoHost</a
|
||||
>
|
||||
for accessible self-hosting.
|
||||
</p>
|
||||
</div>
|
||||
<div class="w3-container w3-cell w3-mobile">
|
||||
<h3>Desktop</h3>
|
||||
<p>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-black w3-padding-large"
|
||||
href="https://dev.tildefriends.net/cory/tildefriends/releases"
|
||||
><i class="fa fa-download"></i> Download</a
|
||||
>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray"
|
||||
href="https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage"
|
||||
>
|
||||
<img src="appimage.svg" style="height: 2em; margin: 0" />
|
||||
Get Linux 64-bit AppImage
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
Tilde Friends is distributed as a single executable file (or
|
||||
source that you can
|
||||
<a href="http://dev.tildefriends.net">build yourself</a>)
|
||||
and stores all of its data in a single
|
||||
file(<code>db.sqlite</code>). You can generally download the
|
||||
latest executable from
|
||||
<a
|
||||
href="https://dev.tildefriends.net/cory/tildefriends/releases"
|
||||
>releases</a
|
||||
>
|
||||
for your platform, mark it as executable (<code
|
||||
>chmod +x tildefriends*</code
|
||||
>
|
||||
on macOS and Linux), and run. Run with <code>-h</code> to
|
||||
learn more.
|
||||
</p>
|
||||
<p>
|
||||
Tilde Friends will run in the console and provide a web
|
||||
interface at
|
||||
<a href="http://localhost:12345/">http://localhost:12345/</a
|
||||
>. You will have to register a username and password to sign
|
||||
into your instance.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
After a <a href="/~core/intro">brief introduction</a>, Tilde
|
||||
Friends will take you to the Secure Scuttlebutt social network
|
||||
app.
|
||||
</p>
|
||||
</li>
|
||||
<li>Create an account to identify yourself with that instance.</li>
|
||||
<li>
|
||||
Describe yourself in your profile in the <b>ssb</b> app. Give
|
||||
yourself a name and an avatar if you like.
|
||||
@@ -146,11 +224,11 @@
|
||||
<!-- SSB Section -->
|
||||
<div class="w3-light-grey">
|
||||
<div class="w3-row-padding w3-padding-64">
|
||||
<div class="w3-col l4 m6 s4">
|
||||
<div class="w3-col l4 m6 s4 w3-center">
|
||||
<a href="https://scuttlebutt.nz/"
|
||||
><img
|
||||
class="w3-image w3-round-large"
|
||||
src="ssb.png"
|
||||
class="w3-image"
|
||||
src="hermietildefriends.svg"
|
||||
alt="Secure Scuttlebutt"
|
||||
/></a>
|
||||
</div>
|
||||
@@ -220,7 +298,7 @@
|
||||
|
||||
<!-- Technlology Section -->
|
||||
<div class="w3-container w3-padding-64 w3-light-grey w3-center">
|
||||
<h1 class="w3-jumbo"><b>Built the Old Fashioned Way</b></h1>
|
||||
<h1 class="w3-jumbo"><b>Built to Last</b></h1>
|
||||
<p>
|
||||
Tilde Friends strives to use only simple and widely adopted dependencies
|
||||
in order to keep it easy to build for all sorts of platforms and
|
||||
@@ -262,10 +340,6 @@
|
||||
<i class="fa fa-lock w3-text-purple w3-jumbo"></i>
|
||||
<p>libsodium</p>
|
||||
</a>
|
||||
<a href="https://github.com/openssl/openssl/releases" class="w3-col s3">
|
||||
<i class="fa fa-shield-halved w3-text-green w3-jumbo"></i>
|
||||
<p>OpenSSL</p>
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/ianlancetaylor/libbacktrace"
|
||||
class="w3-col s3"
|
||||
@@ -273,13 +347,13 @@
|
||||
<i class="fa fa-burst w3-text-pink w3-jumbo"></i>
|
||||
<p>libbacktrace</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="w3-row" style="margin-top: 64px">
|
||||
<a href="https://codemirror.net/docs/changelog/" class="w3-col s3">
|
||||
<i class="fa fa-keyboard w3-text-indigo w3-jumbo"></i>
|
||||
<p>CodeMirror</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="w3-row" style="margin-top: 64px">
|
||||
<a href="https://github.com/jlfwong/speedscope/" class="w3-col s3">
|
||||
<i class="fa fa-microscope w3-text-orange w3-jumbo"></i>
|
||||
<p>Speedscope</p>
|
||||
@@ -292,9 +366,6 @@
|
||||
<i class="fa fa-book-atlas w3-text-purple w3-jumbo"></i>
|
||||
<p>c-ares</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="w3-row" style="margin-top: 64px">
|
||||
<a href="https://www.gnu.org/software/make/" class="w3-col s3">
|
||||
<i class="fa fa-hammer w3-text-teal w3-jumbo"></i>
|
||||
<p>GNU Make</p>
|
||||
|
||||
3
apps/welcome/ios.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="814" height="1000">
|
||||
<path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 660 B |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
@@ -1,4 +1,4 @@
|
||||
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@@ -108,10 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@@ -152,10 +150,11 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
@@ -170,24 +169,28 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
|
||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
|
||||
8
apps/wiki/lit-all.min.js
vendored
39
core/app.js
@@ -1,7 +1,23 @@
|
||||
/**
|
||||
* \file
|
||||
* \defgroup tfapp Tilde Friends App JS
|
||||
* Tilde Friends server-side app wrapper.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** \cond */
|
||||
import * as core from './core.js';
|
||||
|
||||
let gSessionIndex = 0;
|
||||
export {App};
|
||||
/** \endcond */
|
||||
|
||||
/** A sequence number of apps. */
|
||||
let g_session_index = 0;
|
||||
|
||||
/**
|
||||
** App constructor.
|
||||
** @return An app instance.
|
||||
*/
|
||||
function App() {
|
||||
this._send_queue = [];
|
||||
this.calls = {};
|
||||
@@ -9,6 +25,12 @@ function App() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
** Create a function wrapper that when called invokes a function on the app
|
||||
** itself.
|
||||
** @param api The function and argument names.
|
||||
** @return A function.
|
||||
*/
|
||||
App.prototype.makeFunction = function (api) {
|
||||
let self = this;
|
||||
let result = function () {
|
||||
@@ -32,6 +54,10 @@ App.prototype.makeFunction = function (api) {
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
** Send a message to the app.
|
||||
** @param message The message to send.
|
||||
*/
|
||||
App.prototype.send = function (message) {
|
||||
if (this._send_queue) {
|
||||
if (this._on_output) {
|
||||
@@ -46,6 +72,11 @@ App.prototype.send = function (message) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
** App socket handler.
|
||||
** @param request The HTTP request of the WebSocket connection.
|
||||
** @param response The HTTP response.
|
||||
*/
|
||||
exports.app_socket = async function socket(request, response) {
|
||||
let process;
|
||||
let options = {};
|
||||
@@ -118,7 +149,7 @@ exports.app_socket = async function socket(request, response) {
|
||||
parentApp: parentApp,
|
||||
id: blobId,
|
||||
},
|
||||
await ssb.getIdentityInfo(
|
||||
await ssb_internal.getIdentityInfo(
|
||||
credentials?.session?.name,
|
||||
packageOwner,
|
||||
packageName
|
||||
@@ -133,7 +164,7 @@ exports.app_socket = async function socket(request, response) {
|
||||
options.packageOwner = packageOwner;
|
||||
options.packageName = packageName;
|
||||
options.url = message.url;
|
||||
let sessionId = 'session_' + (gSessionIndex++).toString();
|
||||
let sessionId = 'session_' + (g_session_index++).toString();
|
||||
if (blobId) {
|
||||
if (message.edit_only) {
|
||||
response.send(
|
||||
@@ -218,4 +249,4 @@ exports.app_socket = async function socket(request, response) {
|
||||
response.upgrade(100, {});
|
||||
};
|
||||
|
||||
export {App};
|
||||
/** @} */
|
||||
|
||||
@@ -75,6 +75,10 @@
|
||||
margin-bottom: 1em;
|
||||
padding: 1em;
|
||||
}
|
||||
#code_of_conduct:has(>textarea:empty) {
|
||||
display: none;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<div style="display: flex; flex-direction: column; max-width: 1280px; margin: auto">
|
||||
<h1 ?hidden=${this.name}>Welcome.</h1>
|
||||
@@ -126,8 +130,10 @@
|
||||
There is currently no administrator. You will be made administrator.
|
||||
</div>
|
||||
|
||||
<h2>Code of Conduct</h2>
|
||||
<textarea readonly rows="20" cols="80" style="resize: none">${this.code_of_conduct}</textarea>
|
||||
<div id="code_of_conduct">
|
||||
<h2>Code of Conduct</h2>
|
||||
<textarea readonly rows="20" style="resize: none; width: 100%">${this.code_of_conduct}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
715
core/client.js
287
core/core.js
@@ -1,12 +1,31 @@
|
||||
/**
|
||||
* \file
|
||||
* \defgroup tfcore Tilde Friends Core JS
|
||||
* Tilde Friends process management, in JavaScript.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** \cond */
|
||||
import * as app from './app.js';
|
||||
import * as http from './http.js';
|
||||
|
||||
export {invoke, getProcessBlob};
|
||||
/** \endcond */
|
||||
|
||||
/** All running processes. */
|
||||
let gProcesses = {};
|
||||
/** Whether stats are currently being sent. */
|
||||
let gStatsTimer = false;
|
||||
/** Effectively a process ID. */
|
||||
let g_handler_index = 0;
|
||||
|
||||
/** Whether updating accounts information is currently scheduled. */
|
||||
let g_update_accounts_scheduled;
|
||||
/** Time between pings, in milliseconds. */
|
||||
const k_ping_interval = 60 * 1000;
|
||||
|
||||
/**
|
||||
* Print an error.
|
||||
* @param error The error.
|
||||
*/
|
||||
function printError(error) {
|
||||
if (error.stackTrace) {
|
||||
print(error.fileName + ':' + error.lineNumber + ': ' + error.message);
|
||||
@@ -19,6 +38,12 @@ function printError(error) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a handler.
|
||||
* @param handlers The handlers on which to invoke the callback.
|
||||
* @param argv Arguments to pass to the handlers.
|
||||
* @return A promise.
|
||||
*/
|
||||
function invoke(handlers, argv) {
|
||||
let promises = [];
|
||||
if (handlers) {
|
||||
@@ -39,6 +64,12 @@ function invoke(handlers, argv) {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a named event to all registered apps.
|
||||
* @param eventName the name of the event.
|
||||
* @param argv Arguments to pass to the handlers.
|
||||
* @return A promise.
|
||||
*/
|
||||
function broadcastEvent(eventName, argv) {
|
||||
let promises = [];
|
||||
for (let process of Object.values(gProcesses)) {
|
||||
@@ -49,6 +80,11 @@ function broadcastEvent(eventName, argv) {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to all other instances of the same app.
|
||||
* @param message The message.
|
||||
* @return A promise.
|
||||
*/
|
||||
function broadcast(message) {
|
||||
let sender = this;
|
||||
let promises = [];
|
||||
@@ -65,6 +101,15 @@ function broadcast(message) {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to all instances of the same app running as the same user.
|
||||
* @param user The user.
|
||||
* @param packageOwner The owner of the app.
|
||||
* @param packageName The name of the app.
|
||||
* @param eventName The name of the event.
|
||||
* @param argv The arguments to pass.
|
||||
* @return A promise.
|
||||
*/
|
||||
function broadcastAppEventToUser(
|
||||
user,
|
||||
packageOwner,
|
||||
@@ -87,6 +132,11 @@ function broadcastAppEventToUser(
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user context information for a call.
|
||||
* @param caller The calling process.
|
||||
* @param process The receiving process.
|
||||
*/
|
||||
function getUser(caller, process) {
|
||||
return {
|
||||
key: process.key,
|
||||
@@ -97,38 +147,26 @@ function getUser(caller, process) {
|
||||
};
|
||||
}
|
||||
|
||||
async function getApps(user, process) {
|
||||
if (
|
||||
process.credentials &&
|
||||
process.credentials.session &&
|
||||
process.credentials.session.name
|
||||
) {
|
||||
if (user && user !== process.credentials.session.name && user !== 'core') {
|
||||
return {};
|
||||
} else if (!user) {
|
||||
user = process.credentials.session.name;
|
||||
}
|
||||
}
|
||||
if (user) {
|
||||
let db = new Database(user);
|
||||
try {
|
||||
let names = JSON.parse(await db.get('apps'));
|
||||
let result = {};
|
||||
for (let name of names) {
|
||||
result[name] = await db.get('path:' + name);
|
||||
}
|
||||
return result;
|
||||
} catch {}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message.
|
||||
* @param from The calling process.
|
||||
* @param to The receiving process.
|
||||
* @param message The message.
|
||||
* @return A promise.
|
||||
*/
|
||||
function postMessageInternal(from, to, message) {
|
||||
if (to.eventHandlers['message']) {
|
||||
return invoke(to.eventHandlers['message'], [getUser(from, from), message]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a process for an app blob.
|
||||
* @param blobId The blob identifier.
|
||||
* @param key A unique key for the invocation.
|
||||
* @param options Other options.
|
||||
* @return The process.
|
||||
*/
|
||||
async function getProcessBlob(blobId, key, options) {
|
||||
let process = gProcesses[key];
|
||||
if (!process && !(options && 'create' in options && !options.create)) {
|
||||
@@ -142,6 +180,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
process.task = new Task();
|
||||
process.packageOwner = options.packageOwner;
|
||||
process.packageName = options.packageName;
|
||||
process.url = options?.url;
|
||||
process.eventHandlers = {};
|
||||
if (!options?.script || options?.script === 'app.js') {
|
||||
process.app = new app.App();
|
||||
@@ -155,54 +194,13 @@ async function getProcessBlob(blobId, key, options) {
|
||||
});
|
||||
gProcesses[key] = process;
|
||||
process.task.onExit = function (exitCode, terminationSignal) {
|
||||
broadcastEvent('onSessionEnd', [getUser(process, process)]);
|
||||
process.task = null;
|
||||
delete gProcesses[key];
|
||||
};
|
||||
let imports = {
|
||||
core: {
|
||||
broadcast: broadcast.bind(process),
|
||||
register: function (eventName, handler) {
|
||||
if (!process.eventHandlers[eventName]) {
|
||||
process.eventHandlers[eventName] = [];
|
||||
}
|
||||
process.eventHandlers[eventName].push(handler);
|
||||
},
|
||||
unregister: function (eventName, handler) {
|
||||
if (process.eventHandlers[eventName]) {
|
||||
let index = process.eventHandlers[eventName].indexOf(handler);
|
||||
if (index != -1) {
|
||||
process.eventHandlers[eventName].splice(index, 1);
|
||||
}
|
||||
if (process.eventHandlers[eventName].length == 0) {
|
||||
delete process.eventHandlers[eventName];
|
||||
}
|
||||
}
|
||||
},
|
||||
user: getUser(process, process),
|
||||
users: async function () {
|
||||
try {
|
||||
return JSON.parse(await new Database('auth').get('users'));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
permissionsGranted: async function () {
|
||||
let user = process?.credentials?.session?.name;
|
||||
let settings = await loadSettings();
|
||||
if (
|
||||
user &&
|
||||
options?.packageOwner &&
|
||||
options?.packageName &&
|
||||
settings.userPermissions &&
|
||||
settings.userPermissions[user] &&
|
||||
settings.userPermissions[user][options.packageOwner]
|
||||
) {
|
||||
return settings.userPermissions[user][options.packageOwner][
|
||||
options.packageName
|
||||
];
|
||||
}
|
||||
},
|
||||
allPermissionsGranted: async function () {
|
||||
let user = process?.credentials?.session?.name;
|
||||
let settings = await loadSettings();
|
||||
@@ -216,13 +214,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
return settings.userPermissions[user];
|
||||
}
|
||||
},
|
||||
permissionsForUser: async function (user) {
|
||||
let settings = await loadSettings();
|
||||
return settings?.permissions?.[user] ?? [];
|
||||
},
|
||||
apps: (user) => getApps(user, process),
|
||||
getSockets: getSockets,
|
||||
permissionTest: async function (permission) {
|
||||
permissionTest: async function (permission, description) {
|
||||
let user = process?.credentials?.session?.name;
|
||||
let settings = await loadSettings();
|
||||
if (!user || !options?.packageOwner || !options?.packageName) {
|
||||
@@ -249,7 +241,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
}
|
||||
} else if (process.app) {
|
||||
return process.app
|
||||
.makeFunction(['requestPermission'])(permission)
|
||||
.makeFunction(['requestPermission'])(permission, description)
|
||||
.then(async function (value) {
|
||||
if (value == 'allow') {
|
||||
await ssb.setUserPermission(
|
||||
@@ -282,26 +274,26 @@ async function getProcessBlob(blobId, key, options) {
|
||||
throw Error(`Permission denied: ${permission}.`);
|
||||
}
|
||||
},
|
||||
app: {
|
||||
owner: options?.packageOwner,
|
||||
name: options?.packageName,
|
||||
},
|
||||
url: options?.url,
|
||||
},
|
||||
};
|
||||
process.sendIdentities = async function () {
|
||||
process.app.send(
|
||||
Object.assign(
|
||||
{
|
||||
action: 'identities',
|
||||
},
|
||||
await ssb.getIdentityInfo(
|
||||
process?.credentials?.session?.name,
|
||||
options?.packageOwner,
|
||||
options?.packageName
|
||||
)
|
||||
)
|
||||
let identities = await ssb_internal.getIdentityInfo(
|
||||
process?.credentials?.session?.name,
|
||||
options?.packageOwner,
|
||||
options?.packageName
|
||||
);
|
||||
let json = JSON.stringify(identities);
|
||||
if (process._last_sent_identities !== json) {
|
||||
process.app.send(
|
||||
Object.assign(
|
||||
{
|
||||
action: 'identities',
|
||||
},
|
||||
identities
|
||||
)
|
||||
);
|
||||
process._last_sent_identities = json;
|
||||
}
|
||||
};
|
||||
process.setActiveIdentity = async function (identity) {
|
||||
if (
|
||||
@@ -338,7 +330,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
options.packageName,
|
||||
'setActiveIdentity',
|
||||
[
|
||||
await ssb.getActiveIdentity(
|
||||
await imports.ssb.getActiveIdentity(
|
||||
process.credentials?.session?.name,
|
||||
options.packageOwner,
|
||||
options.packageName
|
||||
@@ -351,21 +343,11 @@ async function getProcessBlob(blobId, key, options) {
|
||||
}
|
||||
};
|
||||
if (process.credentials?.permissions?.administration) {
|
||||
imports.core.globalSettingsDescriptions = async function () {
|
||||
let settings = Object.assign({}, defaultGlobalSettings());
|
||||
for (let [key, value] of Object.entries(await loadSettings())) {
|
||||
if (settings[key]) {
|
||||
settings[key].value = value;
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
imports.core.globalSettingsGet = async function (key) {
|
||||
let settings = await loadSettings();
|
||||
return settings?.[key];
|
||||
};
|
||||
imports.core.globalSettingsSet = async function (key, value) {
|
||||
await imports.core.permissionTest('set_global_setting');
|
||||
await imports.core.permissionTest(
|
||||
'set_global_setting',
|
||||
`Set ${JSON.stringify(key)} to ${JSON.stringify(value)}.`
|
||||
);
|
||||
print('Setting', key, value);
|
||||
let settings = await loadSettings();
|
||||
settings[key] = value;
|
||||
@@ -445,26 +427,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
}
|
||||
};
|
||||
imports.ssb.setActiveIdentity = (id) => process.setActiveIdentity(id);
|
||||
imports.ssb.getActiveIdentity = () =>
|
||||
ssb.getActiveIdentity(
|
||||
process.credentials?.session?.name,
|
||||
options.packageOwner,
|
||||
options.packageName
|
||||
);
|
||||
imports.ssb.getOwnerIdentities = function () {
|
||||
if (options.packageOwner) {
|
||||
return ssb.getIdentities(options.packageOwner);
|
||||
}
|
||||
};
|
||||
imports.ssb.getIdentities = function () {
|
||||
if (
|
||||
process.credentials &&
|
||||
process.credentials.session &&
|
||||
process.credentials.session.name
|
||||
) {
|
||||
return ssb.getIdentities(process.credentials.session.name);
|
||||
}
|
||||
};
|
||||
imports.ssb.getPrivateKey = function (id) {
|
||||
if (
|
||||
process.credentials &&
|
||||
@@ -484,8 +446,18 @@ async function getProcessBlob(blobId, key, options) {
|
||||
process.credentials.session &&
|
||||
process.credentials.session.name
|
||||
) {
|
||||
let action;
|
||||
try {
|
||||
if (message?.type === 'vote' && message?.vote?.expression) {
|
||||
action = `React with ${message?.vote?.expression}.`;
|
||||
} else if (typeof message === 'string') {
|
||||
action = `Post a private message.`;
|
||||
} else {
|
||||
action = `Publish ${'aeiou'.indexOf(message?.type?.toLowerCase()?.substring(0, 1)) != -1 ? 'an' : 'a'} "${message?.type}" message.`;
|
||||
}
|
||||
} catch {}
|
||||
return Promise.resolve(
|
||||
imports.core.permissionTest('ssb_append')
|
||||
imports.core.permissionTest('ssb_append', action)
|
||||
).then(function () {
|
||||
return ssb.appendMessageWithIdentity(
|
||||
process.credentials.session.name,
|
||||
@@ -534,13 +506,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
);
|
||||
}
|
||||
};
|
||||
imports.ssb.addEventListener = undefined;
|
||||
imports.ssb.removeEventListener = undefined;
|
||||
imports.ssb.getIdentityInfo = undefined;
|
||||
imports.fetch = async function (url, options) {
|
||||
let settings = await loadSettings();
|
||||
return http.fetch(url, options, settings?.fetch_hosts);
|
||||
};
|
||||
|
||||
if (
|
||||
process.credentials &&
|
||||
@@ -644,7 +609,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
} catch (e) {
|
||||
printError(e);
|
||||
}
|
||||
broadcastEvent('onSessionBegin', [getUser(process, process)]);
|
||||
if (process.app) {
|
||||
process.app.send({action: 'ready', version: version()});
|
||||
await process.sendPermissions();
|
||||
@@ -667,18 +631,46 @@ async function getProcessBlob(blobId, key, options) {
|
||||
return process;
|
||||
}
|
||||
|
||||
ssb.addEventListener('message', function () {
|
||||
/**
|
||||
* Send any changed account information.
|
||||
*/
|
||||
function updateAccounts() {
|
||||
g_update_accounts_scheduled = false;
|
||||
let promises = [];
|
||||
for (let process of Object.values(gProcesses)) {
|
||||
promises.push(process.sendIdentities());
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* SSB message added callback.
|
||||
*/
|
||||
ssb_internal.addEventListener('message', function () {
|
||||
broadcastEvent('onMessage', [...arguments]);
|
||||
|
||||
if (!g_update_accounts_scheduled) {
|
||||
setTimeout(updateAccounts, 1000);
|
||||
g_update_accounts_scheduled = true;
|
||||
}
|
||||
});
|
||||
|
||||
ssb.addEventListener('broadcasts', function () {
|
||||
ssb_internal.addEventListener('blob', function () {
|
||||
broadcastEvent('onBlob', [...arguments]);
|
||||
});
|
||||
|
||||
ssb_internal.addEventListener('broadcasts', function () {
|
||||
broadcastEvent('onBroadcastsChanged', []);
|
||||
});
|
||||
|
||||
ssb.addEventListener('connections', function () {
|
||||
ssb_internal.addEventListener('connections', function () {
|
||||
broadcastEvent('onConnectionsChanged', []);
|
||||
});
|
||||
|
||||
/**
|
||||
* Load settings from the database.
|
||||
* @return The settings as a key value pairs object.
|
||||
*/
|
||||
async function loadSettings() {
|
||||
let data = {};
|
||||
try {
|
||||
@@ -697,6 +689,9 @@ async function loadSettings() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send periodic stats to all clients.
|
||||
*/
|
||||
function sendStats() {
|
||||
let apps = Object.values(gProcesses)
|
||||
.filter((process) => process.app)
|
||||
@@ -712,6 +707,16 @@ function sendStats() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke an app's handler.js.
|
||||
* @param response The response object.
|
||||
* @param app_blob_id The app's blob identifier.
|
||||
* @param path The request path.
|
||||
* @param query The request query string.
|
||||
* @param headers The request headers.
|
||||
* @param package_owner The app's owner.
|
||||
* @param package_name The app's name.
|
||||
*/
|
||||
exports.callAppHandler = async function callAppHandler(
|
||||
response,
|
||||
app_blob_id,
|
||||
@@ -777,4 +782,4 @@ exports.callAppHandler = async function callAppHandler(
|
||||
response.end(answer?.data);
|
||||
};
|
||||
|
||||
export {invoke, getProcessBlob};
|
||||
/** @} */
|
||||
|
||||
67
core/eula.html
Normal file
@@ -0,0 +1,67 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tilde Friends Usage Agreement</title>
|
||||
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<body class="w3-container">
|
||||
<h1>Tilde Friends Usage Agreement</h1>
|
||||
<p>
|
||||
Tilde Friends is an app that enables communication with other users
|
||||
through the
|
||||
<a href="https://ssbc.github.io/scuttlebutt-protocol-guide/"
|
||||
>Secure Scuttlebutt</a
|
||||
>
|
||||
protocol.
|
||||
</p>
|
||||
<h2>Your actions are your responsibility</h2>
|
||||
<p>
|
||||
Apple tolerates no objectionable content or abusive users on their
|
||||
platforms.
|
||||
</p>
|
||||
<p>
|
||||
You are responsible for your own actions within this app. You are
|
||||
responsible for complying with all applicable rules and laws.
|
||||
</p>
|
||||
<h2>You choose what you see</h2>
|
||||
<p>
|
||||
You are in full control of the content you see in Tilde Friends. The peers
|
||||
to which you choose to connect and the users you choose to follow directly
|
||||
determine the content presented to you. Initially you will be following no
|
||||
one with no connections and as a result see no user-generated content.
|
||||
</p>
|
||||
<p>
|
||||
If you encounter objectionable content, you can filter it from your view
|
||||
by blocking the user who posted it. This also makes it so that users
|
||||
following you will not see it as a consequence of following you.
|
||||
</p>
|
||||
<p>
|
||||
The <code>admin</code> app contains a variety of settings that control the
|
||||
types of connections Tilde Friends will make or accept, including whether
|
||||
the app will even accept or make connections at all.
|
||||
</p>
|
||||
<h2>This app is not a service</h2>
|
||||
<p>
|
||||
Tilde Friends is an app. It relies on no servers. The author of this app
|
||||
has no more ability to see or filter what you post or read than any other
|
||||
user of the network.
|
||||
</p>
|
||||
<p>
|
||||
If you believe objectionable content obtained from a service that is
|
||||
running as part of the Secure Scuttlebutt network should be flagged,
|
||||
report it to the operator of the service, generally by contacting the
|
||||
email address listed when visiting the server address in a web browser.
|
||||
</p>
|
||||
<h2>Agreement</h2>
|
||||
<p>
|
||||
If you do not accept these terms, do not use this app. You may close and
|
||||
delete it now.
|
||||
</p>
|
||||
<div class="w3-center w3-margin">
|
||||
<a class="w3-button w3-blue w3-round-large" href="/eula/accept"
|
||||
>Accept Agreement</a
|
||||
>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
113
core/http.js
@@ -1,113 +0,0 @@
|
||||
/**
|
||||
* TODOC
|
||||
* TODO: document so we can improve this
|
||||
* @param {*} url
|
||||
* @returns
|
||||
*/
|
||||
function parseUrl(url) {
|
||||
// XXX: Hack.
|
||||
let match = url.match(new RegExp('(\\w+)://([^/:]+)(?::(\\d+))?(.*)'));
|
||||
return {
|
||||
protocol: match[1],
|
||||
host: match[2],
|
||||
path: match[4],
|
||||
port: match[3] ? parseInt(match[3]) : match[1] == 'http' ? 80 : 443,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
function parseResponse(data) {
|
||||
let firstLine;
|
||||
let headers = {};
|
||||
while (true) {
|
||||
let endLine = data.indexOf('\r\n');
|
||||
let line = data.substring(0, endLine);
|
||||
data = data.substring(endLine + 2);
|
||||
if (!line.length) {
|
||||
break;
|
||||
} else if (!firstLine) {
|
||||
firstLine = line;
|
||||
} else {
|
||||
let colon = line.indexOf(':');
|
||||
headers[line.substring(colon)] = line.substring(colon + 1);
|
||||
}
|
||||
}
|
||||
return {body: data};
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} url
|
||||
* @param {*} options
|
||||
* @param {*} allowed_hosts
|
||||
* @returns
|
||||
*/
|
||||
export function fetch(url, options, allowed_hosts) {
|
||||
let parsed = parseUrl(url);
|
||||
return new Promise(function (resolve, reject) {
|
||||
if ((allowed_hosts ?? []).indexOf(parsed.host) == -1) {
|
||||
throw new Error(`fetch() request to host ${parsed.host} is not allowed.`);
|
||||
}
|
||||
let socket = new Socket();
|
||||
let buffer = new Uint8Array(0);
|
||||
|
||||
return socket
|
||||
.connect(parsed.host, parsed.port)
|
||||
.then(function () {
|
||||
socket.read(function (data) {
|
||||
if (data && data.length) {
|
||||
let newBuffer = new Uint8Array(buffer.length + data.length);
|
||||
newBuffer.set(buffer, 0);
|
||||
newBuffer.set(data, buffer.length);
|
||||
buffer = newBuffer;
|
||||
} else {
|
||||
let result = parseHttpResponse(buffer);
|
||||
if (!result) {
|
||||
reject(new Exception('Parse failed.'));
|
||||
}
|
||||
if (typeof result == 'number') {
|
||||
if (result == -2) {
|
||||
reject('Incomplete request.');
|
||||
} else {
|
||||
reject('Bad request.');
|
||||
}
|
||||
} else if (typeof result == 'object') {
|
||||
resolve({
|
||||
body: buffer.slice(result.bytes_parsed),
|
||||
status: result.status,
|
||||
message: result.message,
|
||||
headers: result.headers,
|
||||
});
|
||||
} else {
|
||||
reject(new Exception('Unexpected parse result.'));
|
||||
}
|
||||
resolve(parseResponse(utf8Decode(buffer)));
|
||||
}
|
||||
});
|
||||
|
||||
if (parsed.port == 443) {
|
||||
return socket.startTls();
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
let body =
|
||||
typeof options?.body == 'string'
|
||||
? utf8Encode(options.body)
|
||||
: options.body || new Uint8Array(0);
|
||||
let headers = utf8Encode(
|
||||
`${options?.method ?? 'GET'} ${parsed.path} HTTP/1.0\r\nHost: ${parsed.host}\r\nConnection: close\r\nContent-Length: ${body.length}\r\n\r\n`
|
||||
);
|
||||
let fullRequest = new Uint8Array(headers.length + body.length);
|
||||
fullRequest.set(headers, 0);
|
||||
fullRequest.set(body, headers.length);
|
||||
socket.write(fullRequest);
|
||||
})
|
||||
.catch(function (error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -5,7 +5,10 @@
|
||||
<link type="text/css" rel="stylesheet" href="/static/style.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
|
||||
<link type="image/svg+xml" rel="icon" href="/static/tildefriends.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1"
|
||||
/>
|
||||
<meta
|
||||
name="title"
|
||||
content="Tilde Friends - Make friends and apps from your web browser."
|
||||
|
||||
@@ -152,4 +152,5 @@ body {
|
||||
border-bottom: 4px solid #fff;
|
||||
padding: 1em;
|
||||
margin: 0 auto;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
/**
|
||||
* \file
|
||||
* \defgroup tfrpc Tilde Friends RPC.
|
||||
* Tilde Friends RPC.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Whether this module is being run in a web browser. */
|
||||
const k_is_browser = get_is_browser();
|
||||
/** Registered methods. */
|
||||
let g_api = {};
|
||||
/** The next method identifier. */
|
||||
let g_next_id = 1;
|
||||
/** Identifiers of pending calls. */
|
||||
let g_calls = {};
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Check if being called from a browser vs. server-side.
|
||||
* @return true if called from a browser.
|
||||
*/
|
||||
function get_is_browser() {
|
||||
try {
|
||||
@@ -15,16 +26,30 @@ function get_is_browser() {
|
||||
}
|
||||
}
|
||||
|
||||
/** \cond */
|
||||
if (k_is_browser) {
|
||||
print = console.log;
|
||||
}
|
||||
|
||||
if (k_is_browser) {
|
||||
window.addEventListener('message', function (event) {
|
||||
call_rpc(event.data);
|
||||
});
|
||||
} else {
|
||||
core.register('message', function (message) {
|
||||
call_rpc(message?.message);
|
||||
});
|
||||
}
|
||||
|
||||
export let rpc = new Proxy({}, {get: make_rpc});
|
||||
/** \endcond */
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} target
|
||||
* @param {*} prop
|
||||
* @param {*} receiver
|
||||
* @returns
|
||||
* Make a function to invoke a remote procedure.
|
||||
* @param target The target.
|
||||
* @param prop The name of the function.
|
||||
* @param receiver The receiver.
|
||||
* @return A function.
|
||||
*/
|
||||
function make_rpc(target, prop, receiver) {
|
||||
return function () {
|
||||
@@ -55,8 +80,8 @@ function make_rpc(target, prop, receiver) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} response
|
||||
* Send a response.
|
||||
* @param response The response.
|
||||
*/
|
||||
function send(response) {
|
||||
if (k_is_browser) {
|
||||
@@ -67,8 +92,8 @@ function send(response) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} message
|
||||
* Invoke a remote procedure.
|
||||
* @param message An object describing the call.
|
||||
*/
|
||||
function call_rpc(message) {
|
||||
if (message && message.message === 'tfrpc') {
|
||||
@@ -112,22 +137,12 @@ function call_rpc(message) {
|
||||
}
|
||||
}
|
||||
|
||||
if (k_is_browser) {
|
||||
window.addEventListener('message', function (event) {
|
||||
call_rpc(event.data);
|
||||
});
|
||||
} else {
|
||||
core.register('message', function (message) {
|
||||
call_rpc(message?.message);
|
||||
});
|
||||
}
|
||||
|
||||
export let rpc = new Proxy({}, {get: make_rpc});
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} method
|
||||
* Register a function that to be called remotely.
|
||||
* @param method The method.
|
||||
*/
|
||||
export function register(method) {
|
||||
g_api[method.name] = method;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
31
core/w3.css
@@ -1,4 +1,4 @@
|
||||
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@@ -108,10 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@@ -152,10 +150,11 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
@@ -170,24 +169,28 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
|
||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
|
||||
@@ -25,14 +25,14 @@
|
||||
}:
|
||||
pkgs.stdenv.mkDerivation rec {
|
||||
pname = "tildefriends";
|
||||
version = "0.0.29";
|
||||
version = "0.2025.9";
|
||||
|
||||
src = pkgs.fetchFromGitea {
|
||||
domain = "dev.tildefriends.net";
|
||||
owner = "cory";
|
||||
repo = "tildefriends";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-bQXFpocOYOlFmVj9OZeQhNrgFuTJ8sx2RSw1tgmelOM=";
|
||||
hash = "sha256-1nhsfhdOO5HIiiTMb+uROB8nDPL/UpOYm52hZ/OpPyk=";
|
||||
fetchSubmodules = true;
|
||||
};
|
||||
|
||||
|
||||
2
deps/codemirror/cm6.js
vendored
496
deps/codemirror_src/package-lock.json
generated
vendored
@@ -19,9 +19,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/autocomplete": {
|
||||
"version": "6.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
|
||||
"integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
|
||||
"version": "6.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.19.1.tgz",
|
||||
"integrity": "sha512-q6NenYkEy2fn9+JyjIxMWcNjzTL/IhwqfzOut1/G3PrIFkrbl4AL7Wkse5tLrQUUyqGoAKU5+Pi5jnnXxH5HGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
@@ -30,9 +31,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/commands": {
|
||||
"version": "6.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
|
||||
"integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz",
|
||||
"integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
@@ -44,6 +46,7 @@
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
|
||||
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
@@ -53,9 +56,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-html": {
|
||||
"version": "6.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
|
||||
"integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
|
||||
"version": "6.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
|
||||
"integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/lang-css": "^6.0.0",
|
||||
@@ -65,13 +69,14 @@
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/css": "^1.1.0",
|
||||
"@lezer/html": "^1.3.0"
|
||||
"@lezer/html": "^1.3.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-javascript": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.3.tgz",
|
||||
"integrity": "sha512-8PR3vIWg7pSu7ur8A07pGiYHgy3hHj+mRYRCSG8q+mPIrl0F02rgpGv+DsQTHRTc30rydOsf5PZ7yjKFg2Ackw==",
|
||||
"version": "6.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
|
||||
"integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
@@ -83,18 +88,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-json": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
|
||||
"integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz",
|
||||
"integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/json": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.0.tgz",
|
||||
"integrity": "sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==",
|
||||
"version": "6.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
|
||||
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
@@ -105,9 +112,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lint": {
|
||||
"version": "6.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
|
||||
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
|
||||
"version": "6.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.2.tgz",
|
||||
"integrity": "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.35.0",
|
||||
@@ -115,9 +123,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/search": {
|
||||
"version": "6.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.10.tgz",
|
||||
"integrity": "sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==",
|
||||
"version": "6.5.11",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
|
||||
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
@@ -128,14 +137,16 @@
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
||||
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/theme-one-dark": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz",
|
||||
"integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==",
|
||||
"version": "6.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz",
|
||||
"integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
@@ -144,27 +155,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.36.5",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.5.tgz",
|
||||
"integrity": "sha512-cd+FZEUlu3GQCYnguYm3EkhJ8KJVisqqUsCOKedBoAt/d9c76JUUap6U0UrpElln5k6VyrEOYliMuDAKIeDQLg==",
|
||||
"version": "6.38.8",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz",
|
||||
"integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.5.0",
|
||||
"crelt": "^1.0.6",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
||||
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
@@ -172,72 +182,71 @@
|
||||
"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,
|
||||
"license": "MIT",
|
||||
"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==",
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
||||
"dev": true
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"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==",
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/common": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.3.0.tgz",
|
||||
"integrity": "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@lezer/css": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.11.tgz",
|
||||
"integrity": "sha512-FuAnusbLBl1SEAtfN8NdShxYJiESKw9LAFysfea1T96jD3ydBn12oYjaSG1a04BQRIUd93/0D8e5CV1cUMkmQg==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz",
|
||||
"integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
"@lezer/lr": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/highlight": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
|
||||
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
"@lezer/common": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/html": {
|
||||
"version": "1.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
|
||||
"integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
|
||||
"version": "1.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz",
|
||||
"integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
@@ -245,9 +254,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/javascript": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz",
|
||||
"integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==",
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz",
|
||||
"integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
@@ -258,6 +268,7 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
@@ -265,9 +276,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/lr": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.3.tgz",
|
||||
"integrity": "sha512-yenN5SqAxAPv/qMnpWW0AT7l+SxVrgG+u0tNsRQWqbrz66HIl8DnEbBObvy21J5K7+I1v7gsAnlE2VQ5yYVSeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
@@ -275,12 +287,14 @@
|
||||
"node_modules/@marijn/find-cluster-break": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
|
||||
"integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
"@types/resolve": "1.20.2",
|
||||
@@ -305,6 +319,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
|
||||
"integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"serialize-javascript": "^6.0.1",
|
||||
"smob": "^1.0.0",
|
||||
@@ -323,9 +338,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
|
||||
"integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
|
||||
"integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
@@ -344,260 +360,309 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz",
|
||||
"integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
|
||||
"integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz",
|
||||
"integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
|
||||
"integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz",
|
||||
"integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
|
||||
"integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz",
|
||||
"integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
|
||||
"integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz",
|
||||
"integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
|
||||
"integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz",
|
||||
"integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
|
||||
"integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz",
|
||||
"integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
|
||||
"integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz",
|
||||
"integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
|
||||
"integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz",
|
||||
"integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
|
||||
"integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz",
|
||||
"integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
|
||||
"integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz",
|
||||
"integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==",
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
|
||||
"integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz",
|
||||
"integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==",
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
|
||||
"integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz",
|
||||
"integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
|
||||
"integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz",
|
||||
"integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
|
||||
"integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz",
|
||||
"integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
|
||||
"integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz",
|
||||
"integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
|
||||
"integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz",
|
||||
"integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
|
||||
"integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz",
|
||||
"integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==",
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
|
||||
"integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
|
||||
"integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz",
|
||||
"integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
|
||||
"integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
|
||||
"integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz",
|
||||
"integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
|
||||
"integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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=="
|
||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
|
||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -609,12 +674,14 @@
|
||||
"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
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/codemirror": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
|
||||
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
|
||||
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/commands": "^6.0.0",
|
||||
@@ -629,17 +696,20 @@
|
||||
"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
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -647,13 +717,15 @@
|
||||
"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=="
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@@ -666,6 +738,7 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
@@ -674,6 +747,7 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
@@ -685,6 +759,7 @@
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
@@ -698,17 +773,20 @@
|
||||
"node_modules/is-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="
|
||||
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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=="
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -721,16 +799,18 @@
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||
"version": "1.22.11",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.16.0",
|
||||
"is-core-module": "^2.16.1",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
@@ -745,11 +825,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.40.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz",
|
||||
"integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==",
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
|
||||
"integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.7"
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
@@ -759,26 +840,28 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.40.0",
|
||||
"@rollup/rollup-android-arm64": "4.40.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.40.0",
|
||||
"@rollup/rollup-darwin-x64": "4.40.0",
|
||||
"@rollup/rollup-freebsd-arm64": "4.40.0",
|
||||
"@rollup/rollup-freebsd-x64": "4.40.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.40.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.40.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.40.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.40.0",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.40.0",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.40.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.40.0",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.40.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.40.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.40.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.40.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.40.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.40.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.40.0",
|
||||
"@rollup/rollup-android-arm-eabi": "4.53.3",
|
||||
"@rollup/rollup-android-arm64": "4.53.3",
|
||||
"@rollup/rollup-darwin-arm64": "4.53.3",
|
||||
"@rollup/rollup-darwin-x64": "4.53.3",
|
||||
"@rollup/rollup-freebsd-arm64": "4.53.3",
|
||||
"@rollup/rollup-freebsd-x64": "4.53.3",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.53.3",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.53.3",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.53.3",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.53.3",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.53.3",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.53.3",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.53.3",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.53.3",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.53.3",
|
||||
"@rollup/rollup-linux-x64-musl": "4.53.3",
|
||||
"@rollup/rollup-openharmony-arm64": "4.53.3",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.53.3",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.53.3",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.53.3",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.53.3",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@@ -800,13 +883,15 @@
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"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,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
@@ -815,13 +900,15 @@
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz",
|
||||
"integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"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,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -831,20 +918,23 @@
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"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=="
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
|
||||
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -853,13 +943,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.39.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
|
||||
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
|
||||
"version": "5.44.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz",
|
||||
"integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.8.2",
|
||||
"acorn": "^8.15.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
@@ -873,7 +964,8 @@
|
||||
"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=="
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
deps/libbacktrace
vendored
2
deps/libuv
vendored
8
deps/lit/lit-all.min.js
vendored
2
deps/lit/lit-all.min.js.map
vendored
1
deps/openssl_src
vendored
2
deps/picohttpparser
vendored
2
deps/quickjs
vendored
2
deps/speedscope/index.html
vendored
@@ -11,7 +11,7 @@
|
||||
<link rel="icon" type="image/x-icon" href="favicon-FOKUP5Y5.ico">
|
||||
</head>
|
||||
<body>
|
||||
<script src="speedscope-VHEG2FVF.js"></script>
|
||||
<script src="speedscope-432XE7GS.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
6
deps/speedscope/release.txt
vendored
@@ -1,3 +1,3 @@
|
||||
speedscope@1.22.2
|
||||
Sat Feb 15 13:02:38 PST 2025
|
||||
1c254dcb3e2b4f6d921340d20e972d9d27b788f4
|
||||
speedscope@1.24.0
|
||||
Mon Oct 20 18:11:29 PDT 2025
|
||||
fc76932551754a442cd5c4f0afdba28032d14d8a
|
||||
|
||||
3478
deps/sqlite/shell.c
vendored
9914
deps/sqlite/sqlite3.c
vendored
721
deps/sqlite/sqlite3.h
vendored
11
deps/sqlite/sqlite3ext.h
vendored
@@ -366,6 +366,12 @@ struct sqlite3_api_routines {
|
||||
/* Version 3.44.0 and later */
|
||||
void *(*get_clientdata)(sqlite3*,const char*);
|
||||
int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
|
||||
/* Version 3.50.0 and later */
|
||||
int (*setlk_timeout)(sqlite3*,int,int);
|
||||
/* Version 3.51.0 and later */
|
||||
int (*set_errmsg)(sqlite3*,int,const char*);
|
||||
int (*db_status64)(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int);
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -699,6 +705,11 @@ typedef int (*sqlite3_loadext_entry)(
|
||||
/* Version 3.44.0 and later */
|
||||
#define sqlite3_get_clientdata sqlite3_api->get_clientdata
|
||||
#define sqlite3_set_clientdata sqlite3_api->set_clientdata
|
||||
/* Version 3.50.0 and later */
|
||||
#define sqlite3_setlk_timeout sqlite3_api->setlk_timeout
|
||||
/* Version 3.51.0 and later */
|
||||
#define sqlite3_set_errmsg sqlite3_api->set_errmsg
|
||||
#define sqlite3_db_status64 sqlite3_api->db_status64
|
||||
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
|
||||
|
||||
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
||||
|
||||
40
docs/connecting_manyverse.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Connecting with Manyverse
|
||||
|
||||
Communication with [Manyverse](https://www.manyver.se/) should Just Work (tm).
|
||||
|
||||
This document is intended as a cheat sheet for the instances where it doesn't.
|
||||
If your experience differs, please share so we can make things better.
|
||||
|
||||
## Connecting Manyverse to the tildefriends.net room
|
||||
|
||||
Open the `Connections` tab. This is from the desktop app, but mobile is similar.
|
||||
|
||||

|
||||
|
||||
Open the `Connections Panel`.
|
||||
|
||||

|
||||
|
||||
Use the `Add Connection` button at the bottom right to open the dialog to enter
|
||||
a connections string to add a new connection.
|
||||
|
||||

|
||||
|
||||
Copy the tildefriends.net room code from https://www.tildefriends.net/~cory/room/.
|
||||
|
||||

|
||||
|
||||
Paste.
|
||||
|
||||
On mobile especially, make sure the full text is pasted without modification.
|
||||
|
||||

|
||||
|
||||
Click `Done`, and you should be connected successfully. tildefriends.net is
|
||||
all things: a room, a pub, and a client, so you should be able to start replicating
|
||||
immediately as well as find other similarly connected people with whom to establish
|
||||
further connections.
|
||||
|
||||
When logged into tildefriends.net, active connections it sees can be found on
|
||||
the `Connections` tab: https://www.tildefriends.net/~core/ssb/#connections,
|
||||
which may indicate errors if you find yourself disconnecting.
|
||||
BIN
docs/images/manyverse_code.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
docs/images/manyverse_connections_panel.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
docs/images/manyverse_connections_tab.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
docs/images/manyverse_paste_invite_code.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/images/tildefriends_room_app.png
Normal file
|
After Width: | Height: | Size: 136 KiB |
32
docs/model.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Model
|
||||
|
||||
A reasonable mental model of Tilde Friends is as a virtual computer. User
|
||||
interace is through a web browser. Communication with the outside world is
|
||||
through the Secure Scuttlebutt (SSB) network protocol. Persistence is through
|
||||
an SSB store and an additional key-value store in an sqlite database.
|
||||
|
||||
The schema for the sqlite database is primarily a `messages` table and a
|
||||
`blobs` table, which are what one would expect from the SSB specifications.
|
||||
|
||||
```dot
|
||||
digraph {
|
||||
web_browser -> tilde_friends_web_interface [dir=both];
|
||||
web_browser [shape=rect,label="Web Browser"];
|
||||
subgraph cluster_tilde_friends {
|
||||
label = "Tilde Friends";
|
||||
tilde_friends_web_interface -> example_app [dir=both];
|
||||
subgraph cluster_sandbox {
|
||||
label = "app sandbox";
|
||||
example_app;
|
||||
}
|
||||
example_app -> tilde_friends_core;
|
||||
tilde_friends_core -> example_app;
|
||||
tilde_friends_web_interface -> tilde_friends_core;
|
||||
tilde_friends_core -> "db.sqlite";
|
||||
tilde_friends_core -> ssb;
|
||||
"db.sqlite" [shape=cylinder];
|
||||
}
|
||||
ssb -> other_ssb_clients [label="Secure Handshake",dir=both];
|
||||
other_ssb_clients [shape=rect,label="SSB Peers"];
|
||||
}
|
||||
```
|
||||
@@ -14,7 +14,7 @@
|
||||
- upload to Apple with dist-ios on macos
|
||||
- nix
|
||||
- june and december: update release version
|
||||
- run `nix flake update`
|
||||
- run `nix --extra-experimental-features nix-command --extra-experimental-features flakes flake update`
|
||||
- comment out the hash in default.nix
|
||||
- update the version
|
||||
- run `nix-build`
|
||||
|
||||
288
docs/usage.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# CLI Usage
|
||||
|
||||
## tildefriends -h
|
||||
|
||||
```
|
||||
Usage: out/debug/tildefriends command [command-options]
|
||||
commands:
|
||||
run - Run tildefriends (default).
|
||||
sandbox - Run a sandboxed tildefriends sandbox process (used internally).
|
||||
import - Import apps from file to the database.
|
||||
export - Export apps from the database to file.
|
||||
publish - Append a message to a feed.
|
||||
private - Append a private post message to a feed.
|
||||
create_invite - Create an invite.
|
||||
get_sequence - Get the last sequence number for a feed.
|
||||
get_identity - Get the server account identity.
|
||||
get_profile - Get profile information for the given identity.
|
||||
get_contacts - Get information about followed, blocked, and friend identities.
|
||||
has_blob - Check whether a blob is in the blob store.
|
||||
get_blob - Read a file from the blob store.
|
||||
store_blob - Write a file to the blob store.
|
||||
verify - Verify a feed.
|
||||
test - Test SSB.
|
||||
```
|
||||
|
||||
## tildefriends run -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends run [options]
|
||||
|
||||
Run tildefriends (default).
|
||||
|
||||
options:
|
||||
-s, --script script Script to run (default: core/core.js).
|
||||
-d, --db-path path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
|
||||
-k, --ssb-network-key key SSB network key to use.
|
||||
-n, --count count Number of instances to run.
|
||||
-a, --args args Arguments of the format key=value,foo=bar,verbose=true (note: these are persisted to the database).
|
||||
code_of_conduct (default: ""): Code of conduct presented at sign-in.
|
||||
ssb_port (default: 8008): Port on which to listen for SSB secure handshake connections.
|
||||
http_local_only (default: false): Whether to bind http(s) to the loopback address. Otherwise any.
|
||||
http_port (default: 12345): Port on which to listen for HTTP connections.
|
||||
out_http_port_file (default: ""): File to which to write bound HTTP port.
|
||||
blob_fetch_age_seconds (default: -1): Only blobs mentioned more recently than this age will be automatically fetched.
|
||||
blob_expire_age_seconds (default: -1): Blobs older than this will be automatically deleted.
|
||||
http_redirect (default: ""): If connecting by HTTP and HTTPS is configured, Location header prefix (ie, "http://example.com")
|
||||
index (default: "/~core/intro/"): Default path.
|
||||
index_map (default: ""): Mappings from hostname to redirect path, one per line, as in: "www.tildefriends.net=/~core/index/"
|
||||
peer_exchange (default: false): Enable discovery of, sharing of, and connecting to internet peer strangers, including announcing this instance.
|
||||
replicator (default: true): Enable message and blob replication.
|
||||
room (default: true): Enable peers to tunnel through this instance as a room.
|
||||
room_name (default: "tilde friends tunnel"): Name of the room.
|
||||
seeds_host (default: "seeds.tildefriends.net"): Hostname for seed connections.
|
||||
account_registration (default: true): Allow registration of new accounts.
|
||||
replication_hops (default: 2): Number of hops to replicate (1 = direct follows, 2 = follows of follows, etc.).
|
||||
delete_stale_feeds (default: false): Periodically delete feeds that aren't visible from local accounts or related follows.
|
||||
talk_to_strangers (default: true): Whether connections are accepted from accounts that aren't in the replication range or otherwise already known.
|
||||
autologin (default: false): Whether mobile autologin is supported.
|
||||
broadcast (default: true): Send network discovery broadcasts.
|
||||
discovery (default: true): Receive network discovery broadcasts.
|
||||
stay_connected (default: false): Whether to attempt to keep several peer connections open.
|
||||
accepted_eula_version (default: 0): The version of the last accepted EULA.
|
||||
-o, --one-proc Run everything in one process (unsafely!).
|
||||
-z, --zip path Zip archive from which to load files.
|
||||
-v, --verbose Log raw messages.
|
||||
-h, --help Show this usage information.
|
||||
```
|
||||
|
||||
## tildefriends sandbox -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends sandbox [options]
|
||||
|
||||
Run a sandboxed tildefriends sandbox process (used internally).
|
||||
|
||||
options:
|
||||
-h, --help Show this usage information.
|
||||
-f, --fd File descriptor with which to communicate with parent process.
|
||||
```
|
||||
|
||||
## tildefriends import -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends import [options] [paths...]
|
||||
|
||||
Import apps from file to the database.
|
||||
|
||||
options:
|
||||
-u, --user user User into whose account apps will be imported (default: "import").
|
||||
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
|
||||
-h, --help Show this usage information.
|
||||
```
|
||||
|
||||
## tildefriends export -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends export [options] [paths...]
|
||||
|
||||
Export apps from the database to file.
|
||||
|
||||
options:
|
||||
-u, --user user User from whose account apps will be exported (default: "core").
|
||||
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
|
||||
-h, --help Show this usage information.
|
||||
|
||||
paths Paths of apps to export (example: /~core/ssb /~user/app).
|
||||
```
|
||||
|
||||
## tildefriends publish -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends publish [options]
|
||||
|
||||
Append a message to a feed.
|
||||
|
||||
options:
|
||||
-u, --user user User owning identity with which to publish.
|
||||
-i, --id identity Identity with which to publish message.
|
||||
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
|
||||
-c, --content json JSON content of message to publish.
|
||||
-h, --help Show this usage information.
|
||||
```
|
||||
|
||||
## tildefriends private -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends private [options]
|
||||
|
||||
Append a private post message to a feed.
|
||||
|
||||
options:
|
||||
-u, --user user User owning identity with which to publish (optional).
|
||||
-i, --id identity Identity with which to publish message.
|
||||
-r, --recipients recipients Recipient identities.
|
||||
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
|
||||
-t, --text text Private post text.
|
||||
-h, --help Show this usage information.
|
||||
```
|
||||
|
||||
## tildefriends create_invite -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends create_invite [options]
|
||||
|
||||
Create an invite.
|
||||
|
||||
options:
|
||||
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
|
||||
-i, --identity identity Account from which to get latest sequence number.
|
||||
-a, --address address Address to which the recipient will connect.
|
||||
-p, --port port Port to which the recipient will connect.
|
||||
-u, --use_count count Number of times this invite may be used (default: 1).
|
||||
-e, --expires seconds How long this invite is valid in seconds (-1 for indefinitely, default: 1 hour).
|
||||
-h, --help Show this usage information.
|
||||
```
|
||||
|
||||
## tildefriends get_sequence -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends get_sequence [options]
|
||||
|
||||
Get the last sequence number for a feed.
|
||||
|
||||
options:
|
||||
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
|
||||
-i, --identity identity Account from which to get latest sequence number.
|
||||
-h, --help Show this usage information.
|
||||
```
|
||||
|
||||
## tildefriends get_identity -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends get_identity [options]
|
||||
|
||||
Get the server account identity.
|
||||
|
||||
options:
|
||||
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
|
||||
-h, --help Show this usage information.
|
||||
```
|
||||
|
||||
## tildefriends get_profile -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends get_profile [options]
|
||||
|
||||
Get profile information for the given identity.
|
||||
|
||||
options:
|
||||
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
|
||||
-i, --identity identity Account for which to get profile information.
|
||||
-h, --help Show this usage information.
|
||||
```
|
||||
|
||||
## tildefriends get_contacts -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends get_contacts [options]
|
||||
|
||||
Get information about followed, blocked, and friend identities.
|
||||
|
||||
options:
|
||||
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
|
||||
-i, --identity identity Account from which to get contact information.
|
||||
-h, --help Show this usage information.
|
||||
```
|
||||
|
||||
## tildefriends has_blob -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends has_blob [options]
|
||||
|
||||
Check whether a blob is in the blob store.
|
||||
|
||||
options:
|
||||
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
|
||||
-b, --blob_id blob_id ID of blob to query.
|
||||
-h, --help Show this usage information.
|
||||
```
|
||||
|
||||
## tildefriends get_blob -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends get_blob [options]
|
||||
|
||||
Read a file from the blob store.
|
||||
|
||||
options:
|
||||
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
|
||||
-b, --blob blob_id Blob identifier to retrieve.
|
||||
-o, --output file_path Location to write the retrieved blob.
|
||||
-h, --help Show this usage information.
|
||||
```
|
||||
|
||||
## tildefriends store_blob -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends store_blob [options]
|
||||
|
||||
Write a file to the blob store.
|
||||
|
||||
options:
|
||||
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
|
||||
-f, --file file_path Path to file to add to the blob store.
|
||||
-h, --help Show this usage information.
|
||||
```
|
||||
|
||||
## tildefriends verify -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends verify [options]
|
||||
|
||||
Verify a feed.
|
||||
|
||||
options:
|
||||
-i, --identity identity Identity to verify.
|
||||
-s, --sequence sequence Sequence number to debug.
|
||||
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
|
||||
-h, --help Show this usage information.
|
||||
```
|
||||
|
||||
## tildefriends test -h
|
||||
|
||||
```
|
||||
|
||||
Usage: out/debug/tildefriends test [options]
|
||||
|
||||
Test SSB.
|
||||
|
||||
options:
|
||||
-t, --tests tests Comma-separated list of tests to run. (default: all)
|
||||
-h, --help Show this usage information.
|
||||
```
|
||||
8
flake.lock
generated
@@ -20,16 +20,16 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1739758141,
|
||||
"narHash": "sha256-uq6A2L7o1/tR6VfmYhZWoVAwb3gTy7j4Jx30MIrH0rE=",
|
||||
"lastModified": 1758589230,
|
||||
"narHash": "sha256-zMTCFGe8aVGTEr2RqUi/QzC1nOIQ0N1HRsbqB4f646k=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c618e28f70257593de75a7044438efc1c1fc0791",
|
||||
"rev": "d1d883129b193f0b495d75c148c2c3a7d95789a0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-24.11",
|
||||
"ref": "nixos-25.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "Tilde Friends is a platform for making, running, and sharing web applications.";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
|
||||
13
metadata/en-US/changelogs/37.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
* Faster loads.
|
||||
* Minor UI tweaks.
|
||||
* Added an intro app as part of the initial flow for first-time users.
|
||||
* Fixed more shutdown issues.
|
||||
* Fixed a longstanding potential database issue.
|
||||
* Added a blob export command.
|
||||
* Refresh blob wants for blobs that are requested over the web.
|
||||
* Updates:
|
||||
* CodeMirror
|
||||
* QuickJS 2025-04-26
|
||||
* libuv 1.51.0
|
||||
* sqlite 3.49.2
|
||||
* w3.css 5.02
|
||||
14
metadata/en-US/changelogs/38.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
* Improve load times.
|
||||
* Fix the messages_refs table, and make it usable for hashtags.
|
||||
* Fix a circumstance where we would fail to call promise callbacks.
|
||||
* Fix the private messages tab.
|
||||
* Fix the room app.
|
||||
* Expose followed accounts in a user's profile.
|
||||
* Show connection status in the sidebar.
|
||||
* Simplify placeholder messages.
|
||||
* Only show "Mark as Read" when relevant.
|
||||
* Treat profile images more like post images.
|
||||
* Limit the WAL file size.
|
||||
* Updates:
|
||||
* CodeMirror
|
||||
* sqlite 3.50.1
|
||||
2
metadata/en-US/changelogs/39.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
* Updating Android SDK+target versions.
|
||||
* Minor UI improvements.
|
||||