Compare commits
256 Commits
v0.0.33
...
bbfcbfcae6
| Author | SHA1 | Date | |
|---|---|---|---|
| bbfcbfcae6 | |||
| cd2903c0df | |||
| d873d99b23 | |||
| 1a5392d942 | |||
| ef80c0910c | |||
| 6c641acdd3 | |||
| f0babc6f95 | |||
| 1382eac7e5 | |||
| 79b7252a27 | |||
| 2e8402d11d | |||
| c34065795c | |||
| 1463c18c12 | |||
| f39b0977b7 | |||
| 8f9824e9b7 | |||
| 33392e7c55 | |||
| b4c014fd27 | |||
| 81353b4da9 | |||
| d67297c35b | |||
| 192e9e0955 | |||
| 2449202b5d | |||
| f1876a34ec | |||
| 3c6eeb9cd3 | |||
| c29ab66073 | |||
| d7782d53a1 | |||
| ce3a8c53c6 | |||
| 0af54edac1 | |||
| 2086075f7b | |||
| 14955fa421 | |||
| 1e1059489b | |||
| 68dc5129c8 | |||
| 690b027c0c | |||
| 2f0c379a69 | |||
| 7c1931f529 | |||
| 69f9646955 | |||
| c4ff00dec1 | |||
| 9ce08f79fb | |||
| 0fa9c90ab9 | |||
| 2b191a5345 | |||
| 6381ba6785 | |||
| d84b06f814 | |||
| 3a3b889196 | |||
| 78474e0bea | |||
| 759d5849ba | |||
| 0df9796fb8 | |||
| 95483b3e55 | |||
| 80c0394ec0 | |||
| 1972ce7091 | |||
| f756d1e5b2 | |||
| 4ebc3b0ccc | |||
| a7099d00b9 | |||
| 0ec9010bd4 | |||
| 1381696f9b | |||
| 19b346cc6d | |||
| ec3064e0a1 | |||
| af8e60f8c3 | |||
| d3e06434e6 | |||
| 83fd005ded | |||
| c0dd47ba28 | |||
| e44a85c92b | |||
| 89d3e9b4fe | |||
| f4c6e2db1f | |||
| 48406bfe38 | |||
| 07c879f5f5 | |||
| cd34c127d1 | |||
| fb6e554e59 | |||
| d200e361f7 | |||
| bc3fd57d7a | |||
| fa4ef3b082 | |||
| 0827718d68 | |||
| 0ec862eaac | |||
| 7e1621dfb4 | |||
| c4d4e3822d | |||
| d2e5015eac | |||
| 510c2f81bd | |||
| 4f2e0245d3 | |||
| eecdbf6852 | |||
| ddc4603f13 | |||
| 759b522cd1 | |||
| 7ecb4a192d | |||
| d84626ac31 | |||
| 9c36e0db7b | |||
| fcd26bac1c | |||
| e8e7c98705 | |||
| b5af5cc223 | |||
| ba8253fa30 | |||
| f5bd389183 | |||
| 0c34a38e15 | |||
| 7c7857a6cd | |||
| 716bce2bb0 | |||
| 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 |
@@ -3,6 +3,36 @@ run-name: ${{ gitea.actor }} running 🚀
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
Build-Docs:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:trixie-slim
|
||||
valid_volumes:
|
||||
- '/opt/keys'
|
||||
volumes:
|
||||
- /opt/keys:/opt/keys
|
||||
steps:
|
||||
- name: Install build dependencies
|
||||
run: >
|
||||
apt update && apt install -y \
|
||||
build-essential \
|
||||
doxygen \
|
||||
file \
|
||||
git \
|
||||
graphviz \
|
||||
rsync \
|
||||
unzip \
|
||||
zip
|
||||
- name: Get code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Build documentation
|
||||
run: |
|
||||
mkdir -p out/html/ ~/.ssh/
|
||||
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/
|
||||
Build-All:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
@@ -50,7 +80,6 @@ jobs:
|
||||
mkdir -p out/html/ ~/.ssh/
|
||||
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
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
@@ -60,10 +89,12 @@ jobs:
|
||||
uses: android-actions/setup-android@v3
|
||||
with:
|
||||
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
|
||||
- name: Test Debug
|
||||
run: TF_TEST_auto=0 out/debug/tildefriends test
|
||||
- name: Docker build
|
||||
run: DOCKER_BUILDKIT=1 docker build .
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
4
.gitmodules
vendored
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
|
||||
|
||||
@@ -3,6 +3,7 @@ src
|
||||
deps
|
||||
.clang-format
|
||||
flake.lock
|
||||
apps/trace/speedscope/**
|
||||
|
||||
# Minified files
|
||||
**/*.min.css
|
||||
|
||||
@@ -4,7 +4,6 @@ RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
libc6-dev \
|
||||
perl \
|
||||
make
|
||||
|
||||
COPY . /app
|
||||
|
||||
359
Doxyfile
359
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.
|
||||
|
||||
@@ -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,10 +907,8 @@ 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/http.js \
|
||||
core/tfrpc.js \
|
||||
docs/ \
|
||||
src/
|
||||
@@ -957,21 +918,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.
|
||||
@@ -983,12 +933,12 @@ 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 \
|
||||
@@ -1030,6 +980,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 =
|
||||
|
||||
@@ -1074,11 +1027,6 @@ IMAGE_PATH = docs/images/
|
||||
# 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.
|
||||
@@ -1120,15 +1068,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
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -1266,11 +1205,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 =
|
||||
@@ -1349,12 +1287,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 =
|
||||
@@ -1369,19 +1302,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
|
||||
@@ -1412,6 +1332,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
|
||||
@@ -1431,13 +1360,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
|
||||
@@ -1568,16 +1490,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
|
||||
@@ -1753,6 +1665,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.
|
||||
@@ -1814,8 +1737,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):
|
||||
@@ -2066,16 +1989,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.
|
||||
|
||||
@@ -2096,6 +2012,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
|
||||
@@ -2261,39 +2185,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
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -2436,15 +2334,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.
|
||||
|
||||
@@ -2458,9 +2356,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.
|
||||
@@ -2469,10 +2374,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
|
||||
|
||||
@@ -2486,51 +2391,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
|
||||
@@ -2538,21 +2429,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.
|
||||
|
||||
@@ -2612,9 +2497,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.
|
||||
|
||||
@@ -2623,10 +2506,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.
|
||||
|
||||
@@ -2666,10 +2546,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.
|
||||
|
||||
@@ -2685,13 +2562,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.
|
||||
@@ -2723,12 +2599,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
|
||||
@@ -2778,6 +2653,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
|
||||
@@ -2805,19 +2692,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 =
|
||||
|
||||
170
GNUmakefile
170
GNUmakefile
@@ -16,15 +16,14 @@ MAKEFLAGS += --no-builtin-rules
|
||||
## LD := Linker.
|
||||
## ANDROID_SDK := Path to the Android SDK.
|
||||
|
||||
VERSION_CODE := 40
|
||||
VERSION_CODE_IOS := 15
|
||||
VERSION_NUMBER := 0.0.33
|
||||
VERSION_CODE := 49
|
||||
VERSION_NUMBER := 0.2025.12-wip
|
||||
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-3500400.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-3510100.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 +37,6 @@ BUNDLETOOL = out/bundletool.jar
|
||||
|
||||
HAVE_WIN :=
|
||||
HAVE_CROSS_AARCH64 :=
|
||||
USE_SYSTEM_SSL :=
|
||||
|
||||
export SOURCE_DATE_EPOCH=1
|
||||
export TZ=UTC
|
||||
@@ -65,7 +63,6 @@ LDFLAGS += \
|
||||
-lbsd \
|
||||
-lnetwork \
|
||||
-Wno-stringop-overflow
|
||||
USE_SYSTEM_SSL := 1
|
||||
HAVE_ANDROID = 0
|
||||
HAVE_LINUX_IOS = 0
|
||||
HAVE_LINUX_MACOS = 0
|
||||
@@ -80,13 +77,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 \
|
||||
@@ -253,7 +249,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 +266,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 +296,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)
|
||||
@@ -821,9 +789,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 += \
|
||||
@@ -831,8 +796,6 @@ $(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
||||
endif
|
||||
endif
|
||||
$(WINDOWS_TARGETS): LDFLAGS += \
|
||||
-lssl \
|
||||
-lcrypto \
|
||||
-lcrypt32 \
|
||||
-ldbghelp \
|
||||
-liphlpapi \
|
||||
@@ -845,15 +808,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
|
||||
|
||||
##
|
||||
@@ -929,7 +892,7 @@ src/ios/Info.plist : $(firstword $(MAKEFILE_LIST))
|
||||
tr '\n' '^' | \
|
||||
sed -r \
|
||||
-e 's@(<key>CFBundleShortVersionString</key>\^[[:space:]]*<string>)[0-9.]*(</string>)@\1$(VERSION_NUMBER:%-wip=%)\2@' \
|
||||
-e 's@(<key>CFBundleVersion</key>\^[[:space:]]*<string>)[[:digit:]]+(</string>)@\1$(VERSION_CODE_IOS)\2@' \
|
||||
-e 's@(<key>CFBundleVersion</key>\^[[:space:]]*<string>)[[:digit:]]+(</string>)@\1$(VERSION_CODE)\2@' \
|
||||
-e 's@(<key>MinimumOSVersion</key>\^[[:space:]]*<string>)[0-9.]*(</string>)@\1$(IPHONEOS_VERSION_MIN)\2@' | \
|
||||
tr '^' '\n' > \
|
||||
$@.tmp && mv $@.tmp $@ || rm -f $@.tmp
|
||||
@@ -995,8 +958,7 @@ PACKAGE_DIRS := \
|
||||
core \
|
||||
deps/codemirror \
|
||||
deps/prettier \
|
||||
deps/lit \
|
||||
deps/speedscope
|
||||
deps/lit
|
||||
|
||||
RAW_FILES := $(sort $(filter-out apps/blog% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f -not -name '.*')))
|
||||
|
||||
@@ -1140,6 +1102,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
|
||||
@@ -1185,6 +1152,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/
|
||||
@@ -1212,98 +1180,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)
|
||||
@@ -1377,7 +1258,6 @@ tarball: ## Build an all-inclusive source tarball (.tar.xz).
|
||||
--exclude=deps/libsodium/test \
|
||||
--exclude=deps/libuv/docs \
|
||||
--exclude=deps/libuv/test \
|
||||
--exclude=deps/speedscope/*.map \
|
||||
--exclude=deps/sqlite/shell.c \
|
||||
--exclude=deps/zlib/contrib/vstudio \
|
||||
--exclude=deps/zlib/doc \
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🎛",
|
||||
"previous": "&kmKNyb/uaXNb24gCinJtfS8iWx4cLUWdtl0y2DwEUas=.sha256"
|
||||
"previous": "&bRhS1LQIH8WQjbBfQqdhjLv7tqDdHT7IEPyCmj39b+4=.sha256"
|
||||
}
|
||||
|
||||
@@ -8,12 +8,20 @@ tfrpc.register(function global_settings_set(key, value) {
|
||||
return core.globalSettingsSet(key, value);
|
||||
});
|
||||
|
||||
tfrpc.register(function addBlock(id) {
|
||||
return ssb.addBlock(id);
|
||||
});
|
||||
tfrpc.register(function removeBlock(id) {
|
||||
return ssb.removeBlock(id);
|
||||
});
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
let data = {
|
||||
users: {},
|
||||
granted: await core.allPermissionsGranted(),
|
||||
settings: await core.globalSettingsDescriptions(),
|
||||
blocks: await ssb.getBlocks(),
|
||||
};
|
||||
for (let user of await core.users()) {
|
||||
data.users[user] = await core.permissionsForUser(user);
|
||||
|
||||
@@ -16,6 +16,14 @@ function delete_user(user) {
|
||||
}
|
||||
}
|
||||
|
||||
async function add_block() {
|
||||
await tfrpc.rpc.addBlock(document.getElementById('add_block').value);
|
||||
}
|
||||
|
||||
async function remove_block(id) {
|
||||
await tfrpc.rpc.removeBlock(id);
|
||||
}
|
||||
|
||||
function global_settings_set(key, value) {
|
||||
tfrpc.rpc
|
||||
.global_settings_set(key, value)
|
||||
@@ -94,11 +102,32 @@ ${description.value}</textarea
|
||||
${user}: ${permissions.map((x) => permission_template(x))}
|
||||
</li>
|
||||
`;
|
||||
const block_template = (block) => html`
|
||||
<li class="w3-card w3-margin">
|
||||
<button
|
||||
class="w3-button w3-theme-action"
|
||||
@click=${(e) => remove_block(block.id)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<code>${block.id}</code>
|
||||
${new Date(block.timestamp)}
|
||||
</li>
|
||||
`;
|
||||
const users_template = (users) =>
|
||||
html` <header class="w3-container w3-theme-l2"><h2>Users</h2></header>
|
||||
<ul class="w3-ul">
|
||||
${Object.entries(users).map((u) => user_template(u[0], u[1]))}
|
||||
</ul>`;
|
||||
const blocks_template = (blocks) =>
|
||||
html` <header class="w3-container w3-theme-l2"><h2>Blocks</h2></header>
|
||||
<div class="w3-row w3-margin">
|
||||
<input type="text" class="w3-threequarter w3-input" id="add_block"></input>
|
||||
<button class="w3-quarter w3-button w3-theme-action" @click=${add_block}>Add</button>
|
||||
</div>
|
||||
<ul class="w3-ul">
|
||||
${blocks.map((b) => block_template(b))}
|
||||
</ul>`;
|
||||
const page_template = (data) =>
|
||||
html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%">
|
||||
<header class="w3-container w3-theme-l2"><h2>Global Settings</h2></header>
|
||||
@@ -109,7 +138,7 @@ ${description.value}</textarea
|
||||
.map((x) => html`${input_template(x, data.settings[x])}`)}
|
||||
</ul>
|
||||
</div>
|
||||
${users_template(data.users)}
|
||||
${users_template(data.users)} ${blocks_template(data.blocks)}
|
||||
</div> `;
|
||||
render(page_template(g_data), document.body);
|
||||
});
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "💡",
|
||||
"previous": "&eN6DNPpQUNhGvxneLuLPgsOXR6qyFZ7u+MAz0b4fa7k=.sha256"
|
||||
"previous": "&FGkkfFLaEID3V4lUjPbgCOwgEvNXkcVkzs0zzwD/gQ8=.sha256"
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
class="w3-flex w3-dark-gray w3-center"
|
||||
>
|
||||
<div
|
||||
id="scrollbox"
|
||||
style="
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
@@ -251,6 +252,7 @@
|
||||
index == 0 ? 'hidden' : 'visible';
|
||||
document.getElementById('right').style.visibility =
|
||||
index == slides.length - 1 ? 'hidden' : 'visible';
|
||||
document.getElementById('scrollbox').scrollTo(0, 0);
|
||||
}
|
||||
|
||||
let dots = [...document.getElementsByClassName('dot')];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🦀",
|
||||
"previous": "&DGtlnm5wWRZCgJMF8JsP6VtzNRrd4KLoERJRpFULqOY=.sha256"
|
||||
"previous": "&Gq9oYdYRgeFSi5TbP/K8xRFtRmcRFmKgnbsEgDzEDoE=.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,40 @@ 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();
|
||||
if (end - start > 1000) {
|
||||
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 +106,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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -140,6 +140,9 @@ export async function picker(callback, anchor, author, recent) {
|
||||
<style>
|
||||
${styles}
|
||||
</style>
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<div
|
||||
class="w3-modal"
|
||||
style="display: block; box-sizing: border-box; z-index: 10"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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() {
|
||||
@@ -21,10 +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},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,6 +50,7 @@ class TfElement extends LitElement {
|
||||
this.loading_latest = 0;
|
||||
this.loading_latest_scheduled = 0;
|
||||
this.recent_reactions = [];
|
||||
this.private_closed = {};
|
||||
tfrpc.rpc.getBroadcasts().then((b) => {
|
||||
self.broadcasts = b || [];
|
||||
});
|
||||
@@ -56,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;
|
||||
@@ -83,9 +99,22 @@ class TfElement extends LitElement {
|
||||
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(
|
||||
`
|
||||
@@ -133,12 +162,33 @@ 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 = [
|
||||
'',
|
||||
'@',
|
||||
'👍',
|
||||
'🔐',
|
||||
'🚩',
|
||||
...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));
|
||||
@@ -157,8 +207,6 @@ 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';
|
||||
}
|
||||
@@ -356,16 +404,37 @@ class TfElement extends LitElement {
|
||||
return [cache.latest, cache.messages];
|
||||
}
|
||||
|
||||
async query_timed(sql, args) {
|
||||
let start = new Date();
|
||||
let result = await tfrpc.rpc.query(sql, args);
|
||||
let end = new Date();
|
||||
console.log((end - start) / 1000, sql.replaceAll(/\s+/g, ' ').trim());
|
||||
return result;
|
||||
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);
|
||||
const k_args = [
|
||||
JSON.stringify(this.channels),
|
||||
@@ -375,7 +444,7 @@ class TfElement extends LitElement {
|
||||
];
|
||||
let channels = (
|
||||
await Promise.all([
|
||||
this.query_timed(
|
||||
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
|
||||
@@ -388,7 +457,7 @@ class TfElement extends LitElement {
|
||||
`,
|
||||
k_args
|
||||
),
|
||||
this.query_timed(
|
||||
tfrpc.rpc.query(
|
||||
`
|
||||
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
|
||||
JOIN messages_refs ON messages.id = messages_refs.message
|
||||
@@ -402,7 +471,7 @@ class TfElement extends LitElement {
|
||||
`,
|
||||
k_args
|
||||
),
|
||||
this.query_timed(
|
||||
tfrpc.rpc.query(
|
||||
`
|
||||
SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages
|
||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||
@@ -413,7 +482,7 @@ class TfElement extends LitElement {
|
||||
`,
|
||||
k_args
|
||||
),
|
||||
this.query_timed(
|
||||
tfrpc.rpc.query(
|
||||
`
|
||||
SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3)
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
@@ -422,9 +491,16 @@ class TfElement extends LitElement {
|
||||
`,
|
||||
k_args
|
||||
),
|
||||
tfrpc.rpc.query(
|
||||
`
|
||||
SELECT '🚩' AS channel, MAX(messages.rowid) AS rowid FROM messages
|
||||
WHERE messages.content ->> 'type' = 'flag'
|
||||
`,
|
||||
k_args
|
||||
),
|
||||
])
|
||||
).flat();
|
||||
let latest = {};
|
||||
let latest = {'🔐': undefined};
|
||||
for (let row of channels) {
|
||||
if (!latest[row.channel]) {
|
||||
latest[row.channel] = row.rowid;
|
||||
@@ -433,15 +509,14 @@ class TfElement extends LitElement {
|
||||
}
|
||||
}
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -450,7 +525,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();
|
||||
@@ -495,8 +591,8 @@ class TfElement extends LitElement {
|
||||
|
||||
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 ?? {};
|
||||
@@ -516,36 +612,20 @@ class TfElement extends LitElement {
|
||||
by_count.push({count: v.of, id: id});
|
||||
}
|
||||
let reactions = this.load_recent_reactions();
|
||||
this.load_channels_latest(Object.keys(following));
|
||||
let channels = 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();
|
||||
start_time = new Date();
|
||||
users = await this.fetch_user_info(users);
|
||||
console.log(
|
||||
'user info took',
|
||||
(new Date() - start_time) / 1000.0,
|
||||
'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;
|
||||
await channels;
|
||||
this.whoami = whoami;
|
||||
this.loaded = whoami;
|
||||
} finally {
|
||||
@@ -603,8 +683,12 @@ class TfElement extends LitElement {
|
||||
@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}
|
||||
@@ -624,33 +708,19 @@ class TfElement extends LitElement {
|
||||
.following=${this.following}
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
query=${this.hash?.startsWith('#q=')
|
||||
? decodeURIComponent(this.hash.substring(3))
|
||||
: null}
|
||||
query=${this.search_text()}
|
||||
></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=');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,6 +740,43 @@ class TfElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
search() {
|
||||
let search_text = this.renderRoot.getElementById('search_text');
|
||||
if (!search_text.value.length) {
|
||||
search_text.focus();
|
||||
this.set_tab('search');
|
||||
} else {
|
||||
this.set_hash('#q=' + encodeURIComponent(search_text.value));
|
||||
}
|
||||
}
|
||||
|
||||
search_keydown(event) {
|
||||
if (event.keyCode == 13) {
|
||||
this.search();
|
||||
}
|
||||
}
|
||||
|
||||
search_text() {
|
||||
if (this.hash.startsWith('#q=')) {
|
||||
try {
|
||||
return decodeURIComponent(this.hash.substring('#q='.length));
|
||||
} catch {
|
||||
return this.hash.substring('#q='.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
|
||||
@@ -683,35 +790,19 @@ class TfElement extends LitElement {
|
||||
const k_tabs = {
|
||||
'📰': 'news',
|
||||
'📡': 'connections',
|
||||
'🔍': 'search',
|
||||
'👩💻': 'query',
|
||||
};
|
||||
|
||||
let tabs = html`
|
||||
<style>
|
||||
#search_text:focus {
|
||||
float: none !important;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<div
|
||||
class="w3-bar w3-theme-l1"
|
||||
style="position: static; top: 0; z-index: 10"
|
||||
>
|
||||
${this.is_administrator && self.tab != 'news'
|
||||
? html`
|
||||
<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>
|
||||
<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
|
||||
@@ -728,6 +819,35 @@ 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>
|
||||
${
|
||||
this.is_administrator
|
||||
? html`
|
||||
<button
|
||||
class=${'w3-bar-item w3-button w3-circle w3-ripple w3-right' +
|
||||
(this.connections?.some((x) => x.flags.one_shot)
|
||||
? ' w3-spin'
|
||||
: '')}
|
||||
@click=${this.refresh}
|
||||
>
|
||||
↻
|
||||
</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-ripple w3-right"
|
||||
@click=${this.toggle_stay_connected}
|
||||
>
|
||||
${this.stay_connected ? '🔗' : '⛓️💥'}
|
||||
</button>
|
||||
`
|
||||
: undefined
|
||||
}
|
||||
<button class="w3-bar-item w3-button w3-right" @click=${this.search}>🔍<span class="w3-hide-small">Search</span></button>
|
||||
<input type="text" class=${'w3-input w3-bar-item w3-right w3-theme-d1' + (this.tab == 'search' ? ' w3-mobile' : ' w3-hide-small')} placeholder="keywords, @id, #channel" id="search_text" @keydown=${this.search_keydown} value=${this.search_text()}></input>
|
||||
</div>
|
||||
`;
|
||||
let contents = this.guest
|
||||
@@ -751,11 +871,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({
|
||||
@@ -496,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) {
|
||||
@@ -584,6 +603,9 @@ class TfComposeElement extends LitElement {
|
||||
🔐 Encrypt
|
||||
</button>`;
|
||||
let result = html`
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<style>
|
||||
.w3-input:empty::before {
|
||||
content: attr(placeholder);
|
||||
@@ -606,7 +628,7 @@ class TfComposeElement extends LitElement {
|
||||
<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}
|
||||
|
||||
@@ -4,12 +4,14 @@ import {
|
||||
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() {
|
||||
@@ -24,6 +26,7 @@ class TfMessageElement extends LitElement {
|
||||
channel: {type: String},
|
||||
channel_unread: {type: Number},
|
||||
recent_reactions: {type: Array},
|
||||
depth: {type: Number},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,16 +43,20 @@ class TfMessageElement extends LitElement {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -61,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,
|
||||
@@ -179,6 +196,26 @@ class TfMessageElement extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
flag(event) {
|
||||
let reason = prompt(
|
||||
'What is the reason for reporting this content (spam, nsfw, ...)?',
|
||||
'offensive'
|
||||
);
|
||||
if (reason !== undefined) {
|
||||
tfrpc.rpc
|
||||
.appendMessage(this.whoami, {
|
||||
type: 'flag',
|
||||
flag: {
|
||||
link: this.message.id,
|
||||
reason: reason.length ? reason : undefined,
|
||||
},
|
||||
})
|
||||
.catch(function (error) {
|
||||
alert(error?.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
show_image(link) {
|
||||
let div = document.createElement('div');
|
||||
div.style.left = 0;
|
||||
@@ -311,7 +348,9 @@ class TfMessageElement extends LitElement {
|
||||
}
|
||||
|
||||
expanded_key() {
|
||||
return this.message?.id || this.messages?.map((x) => x.id).join(':');
|
||||
return (
|
||||
this.message?.id || this.message?.messages?.map((x) => x.id).join(':')
|
||||
);
|
||||
}
|
||||
|
||||
set_expanded(expanded, tag) {
|
||||
@@ -349,12 +388,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}
|
||||
@@ -363,16 +403,20 @@ class TfMessageElement extends LitElement {
|
||||
channel=${this.channel}
|
||||
channel_unread=${this.channel_unread}
|
||||
.recent_reactions=${this.recent_reactions}
|
||||
></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>`;
|
||||
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;
|
||||
@@ -475,12 +519,16 @@ class TfMessageElement extends LitElement {
|
||||
</button>
|
||||
`
|
||||
: undefined}
|
||||
<button
|
||||
class="w3-button w3-bar-item w3-border-bottom"
|
||||
@click=${this.react}
|
||||
>
|
||||
<button class="w3-button w3-bar-item" @click=${this.react}>
|
||||
👍 React
|
||||
</button>
|
||||
<button
|
||||
id="button_flag"
|
||||
class="w3-button w3-bar-item w3-border-bottom"
|
||||
@click=${this.flag}
|
||||
>
|
||||
🚩 Flag
|
||||
</button>
|
||||
${formats.map(
|
||||
([format, name]) => html`
|
||||
<button
|
||||
@@ -536,8 +584,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>
|
||||
@@ -548,9 +599,11 @@ class TfMessageElement extends LitElement {
|
||||
let self = this;
|
||||
return this.render_frame(html`
|
||||
${self.render_header()}
|
||||
${self.format == 'raw'
|
||||
? html`<div class="w3-container">${self.render_raw()}</div>`
|
||||
: inner}
|
||||
<div class="w3-container">
|
||||
${self.format == 'raw'
|
||||
? html`${self.render_raw()}`
|
||||
: self.render_flagged(inner)}
|
||||
</div>
|
||||
${self.render_votes()}
|
||||
${(self.message.child_messages || []).map(
|
||||
(x) => html`
|
||||
@@ -563,6 +616,7 @@ class TfMessageElement extends LitElement {
|
||||
channel=${self.channel}
|
||||
channel_unread=${self.channel_unread}
|
||||
.recent_reactions=${self.recent_reactions}
|
||||
depth=${self.depth + 1}
|
||||
></tf-message>
|
||||
`
|
||||
)}
|
||||
@@ -598,15 +652,17 @@ class TfMessageElement extends LitElement {
|
||||
let sorted = this.message.messages
|
||||
.map((x) => [
|
||||
x.author,
|
||||
x.content.blocking !== undefined
|
||||
? x.content.blocking
|
||||
? 'is blocking'
|
||||
: 'is no longer blocking'
|
||||
: x.content.following !== undefined
|
||||
? x.content.following
|
||||
? 'is following'
|
||||
: 'is no longer following'
|
||||
: '',
|
||||
x.content.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,
|
||||
])
|
||||
@@ -631,6 +687,35 @@ class TfMessageElement extends LitElement {
|
||||
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 == '@' ||
|
||||
@@ -644,7 +729,39 @@ class TfMessageElement extends LitElement {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
render() {
|
||||
render_flagged(inner) {
|
||||
if (this.message.flags) {
|
||||
return html`
|
||||
<div
|
||||
class="w3-panel w3-round-xlarge w3-theme-l4 w3"
|
||||
style="cursor: pointer"
|
||||
@click=${(x) => this.toggle_expanded(':cw')}
|
||||
>
|
||||
<p>
|
||||
${this.message.flags
|
||||
? html`<p>
|
||||
Caution: This message has been flagged
|
||||
${this.message.flags.length}
|
||||
time${this.message.flags.length == 1 ? '' : 's'}.
|
||||
</p>`
|
||||
: undefined}
|
||||
</p>
|
||||
<p class="w3-small">
|
||||
${inner !== undefined
|
||||
? this.is_expanded(':cw')
|
||||
? 'Show less'
|
||||
: 'Show more'
|
||||
: undefined}
|
||||
</p>
|
||||
</div>
|
||||
${this.is_expanded(':cw') ? inner : undefined}
|
||||
`;
|
||||
} else {
|
||||
return inner;
|
||||
}
|
||||
}
|
||||
|
||||
_render() {
|
||||
let content = this.message?.content;
|
||||
if (this.message?.decrypted?.type == 'post') {
|
||||
content = this.message.decrypted;
|
||||
@@ -665,6 +782,7 @@ class TfMessageElement extends LitElement {
|
||||
.expanded=${this.expanded}
|
||||
channel=${this.channel}
|
||||
channel_unread=${this.channel_unread}
|
||||
depth=${this.depth + 1}
|
||||
></tf-message>`
|
||||
)}
|
||||
</div>
|
||||
@@ -706,6 +824,56 @@ class TfMessageElement extends LitElement {
|
||||
</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>
|
||||
@@ -740,6 +908,7 @@ class TfMessageElement extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w3-container">${this.render_flagged(undefined)}</div>
|
||||
<div>${this.render_votes()}</div>
|
||||
${(this.message.child_messages || []).map(
|
||||
(x) => html`
|
||||
@@ -751,6 +920,7 @@ class TfMessageElement extends LitElement {
|
||||
.expanded=${this.expanded}
|
||||
channel=${this.channel}
|
||||
channel_unread=${this.channel_unread}
|
||||
depth=${this.depth + 1}
|
||||
></tf-message>
|
||||
`
|
||||
)}
|
||||
@@ -789,60 +959,45 @@ class TfMessageElement extends LitElement {
|
||||
</div>
|
||||
`);
|
||||
} else if (content.type == 'contact') {
|
||||
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>
|
||||
<div class="w3-bar-item w3-right">
|
||||
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
|
||||
%
|
||||
</button>
|
||||
<div
|
||||
class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
|
||||
style="right: 48px"
|
||||
>
|
||||
<a
|
||||
target="_top"
|
||||
class="w3-button w3-bar-item"
|
||||
href=${'#' + encodeURIComponent(this.message?.id)}
|
||||
>View Message</a
|
||||
>
|
||||
<button
|
||||
class="w3-button w3-bar-item w3-border-bottom"
|
||||
@click=${this.copy_id}
|
||||
>
|
||||
Copy ID
|
||||
</button>
|
||||
${this.drafts[this.message?.id] === undefined
|
||||
? html`
|
||||
<button
|
||||
class="w3-button w3-bar-item"
|
||||
@click=${this.show_reply}
|
||||
>
|
||||
↩️ Reply
|
||||
</button>
|
||||
`
|
||||
: undefined}
|
||||
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>
|
||||
${this.render_votes()} ${this.render_actions()}
|
||||
</div>
|
||||
`);
|
||||
`);
|
||||
break;
|
||||
}
|
||||
} else if (content.type == 'post') {
|
||||
let self = this;
|
||||
let body;
|
||||
@@ -869,7 +1024,19 @@ class TfMessageElement extends LitElement {
|
||||
style="cursor: pointer"
|
||||
@click=${(x) => this.toggle_expanded(':cw')}
|
||||
>
|
||||
<p>${content.contentWarning}</p>
|
||||
<p>
|
||||
${this.message.flags
|
||||
? html`<p>
|
||||
Caution: This message has been flagged
|
||||
${this.message.flags.length}
|
||||
time${this.message.flags.length == 1 ? '' : 's'}.
|
||||
</p>`
|
||||
: undefined}
|
||||
${content.contentWarning
|
||||
? html`<p>${content.contentWarning}</p>`
|
||||
: undefined}
|
||||
</p>
|
||||
|
||||
<p class="w3-small">
|
||||
${this.is_expanded(':cw') ? 'Show less' : 'Show more'}
|
||||
</p>
|
||||
@@ -880,11 +1047,12 @@ class TfMessageElement extends LitElement {
|
||||
<div @click=${this.body_click}>${body}</div>
|
||||
${this.render_mentions()}
|
||||
`;
|
||||
let payload = content.contentWarning
|
||||
? self.expanded[(this.message.id || '') + ':cw']
|
||||
? html` ${content_warning} ${content_html} `
|
||||
: content_warning
|
||||
: content_html;
|
||||
let payload =
|
||||
this.message.flags || content.contentWarning
|
||||
? self.expanded[(this.message.id || '') + ':cw']
|
||||
? html` ${content_warning} ${content_html} `
|
||||
: content_warning
|
||||
: content_html;
|
||||
return this.render_frame(html`
|
||||
${this.render_header()}
|
||||
<div class="w3-container">${payload}</div>
|
||||
@@ -903,15 +1071,13 @@ class TfMessageElement extends LitElement {
|
||||
`);
|
||||
} else if (content.type === 'blog') {
|
||||
let self = this;
|
||||
tfrpc.rpc.get_blob(content.blog).then(function (data) {
|
||||
self.blog_data = data;
|
||||
self.blog_data = tfrpc.rpc.get_blob(content.blog).then(function (data) {
|
||||
return data
|
||||
? unsafeHTML(tfutils.markdown(data))
|
||||
: html`Blog post content unavailable.`;
|
||||
});
|
||||
let payload = this.expanded[(this.message.id || '') + ':blog']
|
||||
? html`<div>
|
||||
${this.blog_data
|
||||
? unsafeHTML(tfutils.markdown(this.blog_data))
|
||||
: 'Loading...'}
|
||||
</div>`
|
||||
? until(this.blog_data, 'Loading...')
|
||||
: undefined;
|
||||
let body;
|
||||
switch (this.format) {
|
||||
@@ -924,15 +1090,24 @@ class TfMessageElement extends LitElement {
|
||||
case 'message':
|
||||
body = html`
|
||||
<div
|
||||
style="border: 1px solid #fff; border-radius: 1em; padding: 8px; margin: 4px; cursor: pointer"
|
||||
@click=${(x) => self.toggle_expanded(':blog')}>
|
||||
class="w3-border w3-theme-l4 w3-round-xlarge"
|
||||
style="padding: 8px; margin: 4px; cursor: pointer"
|
||||
@click=${(x) => self.toggle_expanded(':blog')}
|
||||
>
|
||||
<h2>${content.title}</h2>
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<img src=/${content.thumbnail}/view></img>
|
||||
<div style="display: flex; flex-direction: row; gap: 8px">
|
||||
${content.thumbnail
|
||||
? html`<img src=/${content.thumbnail}/view style="max-width: 25vw; max-height: 25vw"></img>`
|
||||
: undefined}
|
||||
<span>${content.summary}</span>
|
||||
</div>
|
||||
<p class="w3-small">
|
||||
${this.expanded[(this.message.id || '') + ':blog']
|
||||
? 'Show less'
|
||||
: 'Show more'}
|
||||
</p>
|
||||
</div>
|
||||
${payload}
|
||||
<div class="w3-container">${payload}</div>
|
||||
`;
|
||||
break;
|
||||
}
|
||||
@@ -1001,6 +1176,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() {
|
||||
@@ -37,8 +37,6 @@ class TfNewsElement extends LitElement {
|
||||
let self = this;
|
||||
let messages_by_id = {};
|
||||
|
||||
console.log('processing', messages.length, 'messages');
|
||||
|
||||
function ensure_message(id, rowid) {
|
||||
let found = messages_by_id[id];
|
||||
if (found) {
|
||||
@@ -66,6 +64,16 @@ class TfNewsElement extends LitElement {
|
||||
}
|
||||
parent.votes.push(message);
|
||||
message.parent_message = message.content.vote.link;
|
||||
} else if (message.content.type == 'flag') {
|
||||
let parent = ensure_message(message.content.flag.link, message.rowid);
|
||||
if (!parent.flags) {
|
||||
parent.flags = [];
|
||||
}
|
||||
parent.flags.push(message);
|
||||
parent.flags = Object.values(
|
||||
Object.fromEntries(parent.flags.map((x) => [x.id, x]))
|
||||
);
|
||||
message.parent_message = message.content.flag.link;
|
||||
} else if (message.content.type == 'post') {
|
||||
if (message.content.root) {
|
||||
if (typeof message.content.root === 'string') {
|
||||
@@ -106,6 +114,7 @@ class TfNewsElement extends LitElement {
|
||||
message.parent_message = placeholder.parent_message;
|
||||
message.child_messages = placeholder.child_messages;
|
||||
message.votes = placeholder.votes;
|
||||
message.flags = placeholder.flags;
|
||||
if (
|
||||
placeholder.parent_message &&
|
||||
messages_by_id[placeholder.parent_message]
|
||||
@@ -160,11 +169,29 @@ 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 == 1) {
|
||||
@@ -173,12 +200,13 @@ class TfNewsElement extends LitElement {
|
||||
} 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 == 1) {
|
||||
@@ -187,7 +215,7 @@ class TfNewsElement extends LitElement {
|
||||
} else if (group.length > 1) {
|
||||
result.push({
|
||||
rowid: Math.max(...group.map((x) => x.rowid)),
|
||||
type: 'contact_group',
|
||||
type: `${type}_group`,
|
||||
messages: group,
|
||||
});
|
||||
}
|
||||
@@ -200,7 +228,7 @@ class TfNewsElement extends LitElement {
|
||||
|
||||
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;
|
||||
@@ -212,6 +240,9 @@ class TfNewsElement extends LitElement {
|
||||
}
|
||||
}
|
||||
return html`
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<div>
|
||||
${repeat(
|
||||
final_messages,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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() {
|
||||
@@ -37,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(
|
||||
@@ -55,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;
|
||||
}
|
||||
@@ -238,7 +246,7 @@ class TfProfileElement extends LitElement {
|
||||
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) {
|
||||
@@ -316,7 +324,9 @@ class TfProfileElement extends LitElement {
|
||||
}
|
||||
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>
|
||||
@@ -349,6 +359,9 @@ class TfProfileElement extends LitElement {
|
||||
${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)}
|
||||
|
||||
@@ -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,7 @@ 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},
|
||||
};
|
||||
}
|
||||
@@ -83,7 +84,6 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
`,
|
||||
[JSON.stringify(combined.map((x) => x.id))]
|
||||
);
|
||||
let t0 = new Date();
|
||||
let result = [].concat(
|
||||
combined,
|
||||
await tfrpc.rpc.query(
|
||||
@@ -100,12 +100,16 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
]
|
||||
)
|
||||
);
|
||||
let t1 = new Date();
|
||||
console.log((t1 - t0) / 1000);
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -113,10 +117,11 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
result = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH mentions 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_fts(?1)
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
FROM messages_refs
|
||||
JOIN messages ON messages.id = messages_refs.message
|
||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||
WHERE
|
||||
messages_refs.ref = ?1 AND
|
||||
messages.author != ?1 AND
|
||||
(?3 IS NULL OR messages.timestamp >= ?3) AND messages.timestamp < ?4
|
||||
ORDER BY timestamp DESC limit ?5)
|
||||
@@ -128,7 +133,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
SELECT TRUE AS is_primary, * FROM mentions
|
||||
`,
|
||||
[
|
||||
'"' + this.whoami.replace('"', '""') + '"',
|
||||
this.whoami,
|
||||
JSON.stringify(this.following),
|
||||
start_time,
|
||||
end_time,
|
||||
@@ -168,7 +173,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
|
||||
@@ -196,13 +200,10 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
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
|
||||
@@ -214,13 +215,18 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
ORDER BY messages.rowid DESC LIMIT ?4
|
||||
`,
|
||||
[
|
||||
JSON.stringify(this.private_messages),
|
||||
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);
|
||||
let decrypted = (await this.decrypt(result)).filter((x) => x.decrypted);
|
||||
result = await this._fetch_related_messages(decrypted);
|
||||
} else if (this.hash == '#👍') {
|
||||
result = await tfrpc.rpc.query(
|
||||
`
|
||||
@@ -239,25 +245,50 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
`,
|
||||
[JSON.stringify(this.following), start_time, end_time, k_max_results]
|
||||
);
|
||||
} else if (this.hash == '#🚩') {
|
||||
result = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH flags 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
|
||||
WHERE
|
||||
messages.content ->> 'type' = 'flag' AND
|
||||
(?1 IS NULL OR messages.timestamp >= ?1) AND messages.timestamp < ?2
|
||||
ORDER BY timestamp DESC limit ?3)
|
||||
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 flags
|
||||
JOIN messages ON messages.id = flags.content ->> '$.flag.link'
|
||||
UNION
|
||||
SELECT TRUE AS is_primary, * FROM flags
|
||||
`,
|
||||
[start_time, end_time, k_max_results]
|
||||
);
|
||||
} else {
|
||||
let t0 = new Date();
|
||||
let initial_messages = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH
|
||||
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
|
||||
WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
|
||||
messages.content ->> 'type' != 'vote'
|
||||
FROM messages
|
||||
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, k_max_results]
|
||||
[
|
||||
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;
|
||||
@@ -365,7 +396,13 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
)
|
||||
)
|
||||
);
|
||||
console.log('done loading latest messages.');
|
||||
}
|
||||
|
||||
make_messages_key() {
|
||||
return JSON.stringify([
|
||||
this.hash,
|
||||
Object.keys(this.channels_latest ?? {}).filter((x) => x != '🔐'),
|
||||
]);
|
||||
}
|
||||
|
||||
async load_messages() {
|
||||
@@ -373,12 +410,20 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
let self = this;
|
||||
this.loading++;
|
||||
let messages = [];
|
||||
let original_key = this.make_messages_key();
|
||||
try {
|
||||
if (this._messages_hash !== this.hash) {
|
||||
if (this._messages_key !== original_key) {
|
||||
this.messages = [];
|
||||
this._messages_hash = this.hash;
|
||||
this._messages_key = original_key;
|
||||
}
|
||||
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;
|
||||
@@ -391,11 +436,11 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
} finally {
|
||||
this.loading--;
|
||||
}
|
||||
this.messages = this.merge_messages(this.messages, messages);
|
||||
let current_key = this.make_messages_key();
|
||||
if (current_key === original_key) {
|
||||
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`
|
||||
);
|
||||
}
|
||||
|
||||
mark_all_read() {
|
||||
@@ -417,15 +462,48 @@ 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_key !== this.make_messages_key() ||
|
||||
this._messages_following !== JSON.stringify(this.following) ||
|
||||
(this.hash.startsWith('#🔐') &&
|
||||
this._private_messages !==
|
||||
JSON.stringify([
|
||||
this.private_messages,
|
||||
this.grouped_private_messages,
|
||||
]))
|
||||
) {
|
||||
console.log(
|
||||
`loading messages for ${this.whoami} (following ${this.following.length})`
|
||||
`loading messages for ${this.whoami} (messages=${!this.messages},${this._messages_key != this.make_messages_key()} following=${this._messages_following !== JSON.stringify(this.following)}, 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();
|
||||
}
|
||||
@@ -474,6 +552,9 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
`;
|
||||
}
|
||||
return cache(html`
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
${this.unread_allowed()
|
||||
? html`<button
|
||||
class="w3-button w3-theme-d1"
|
||||
@@ -482,6 +563,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
Mark All Read
|
||||
</button>`
|
||||
: undefined}
|
||||
${this.render_close_chat_button()}
|
||||
<tf-news
|
||||
id="news"
|
||||
whoami=${this.whoami}
|
||||
|
||||
@@ -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,8 @@ 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},
|
||||
@@ -115,6 +117,19 @@ class TfTabNewsElement extends LitElement {
|
||||
) {
|
||||
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 &&
|
||||
@@ -180,6 +195,10 @@ class TfTabNewsElement extends LitElement {
|
||||
await this.check_peer_exchange();
|
||||
}
|
||||
|
||||
is_loading() {
|
||||
return this.shadowRoot?.getElementById('news')?.loading;
|
||||
}
|
||||
|
||||
render_sidebar() {
|
||||
return html`
|
||||
<div
|
||||
@@ -193,35 +212,6 @@ class TfTabNewsElement extends LitElement {
|
||||
>
|
||||
×
|
||||
</div>
|
||||
${this.is_administrator
|
||||
? html`
|
||||
<button
|
||||
class="w3-bar-item w3-button"
|
||||
@click=${() =>
|
||||
this.dispatchEvent(
|
||||
new Event('refresh', {bubbles: true, composed: true})
|
||||
)}
|
||||
>
|
||||
<span style="display: inline-block; width: 1.8em">↻</span>
|
||||
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>
|
||||
`
|
||||
: undefined}
|
||||
${this.hash.startsWith('##') &&
|
||||
this.channels.indexOf(this.hash.substring(2)) == -1
|
||||
? html`
|
||||
@@ -230,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}
|
||||
@@ -254,11 +244,34 @@ class TfTabNewsElement extends LitElement {
|
||||
>${this.unread_status('👍')}👍votes</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('🚩')}🚩flagged</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(
|
||||
@@ -300,11 +313,26 @@ class TfTabNewsElement extends LitElement {
|
||||
↻ Sync now
|
||||
</button>
|
||||
<button
|
||||
class=${'w3-bar-item w3-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}
|
||||
>
|
||||
Enable peer exchange
|
||||
🔍🌐 Use publicly advertised peers
|
||||
</button>
|
||||
`
|
||||
: undefined}
|
||||
@@ -374,9 +402,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"
|
||||
>
|
||||
@@ -397,13 +428,18 @@ class TfTabNewsElement extends LitElement {
|
||||
</p>
|
||||
<div>
|
||||
<div
|
||||
id="show_sidebar"
|
||||
class="w3-button w3-hide-large"
|
||||
@click=${this.show_sidebar}
|
||||
style="width: 100%; max-width: 100%; white-space: nowrap; overflow: hidden"
|
||||
>
|
||||
${this.unread_status()}☰
|
||||
<button
|
||||
id="show_sidebar"
|
||||
class="w3-button w3-hide-large"
|
||||
@click=${this.show_sidebar}
|
||||
>
|
||||
${this.unread_status()}☰
|
||||
</button>
|
||||
Welcome,
|
||||
<tf-user id=${this.whoami} .users=${this.users}></tf-user>!
|
||||
</div>
|
||||
Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
|
||||
${edit_profile}
|
||||
</div>
|
||||
<div>
|
||||
@@ -414,6 +450,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}
|
||||
@@ -430,6 +469,7 @@ 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>
|
||||
|
||||
@@ -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 {LitElement, 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 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,29 +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.renderRoot.getElementById('news').messages = results;
|
||||
}
|
||||
|
||||
search_keydown(event) {
|
||||
if (event.keyCode == 13) {
|
||||
this.query = this.renderRoot.getElementById('search').value;
|
||||
this.error = undefined;
|
||||
this.results = [];
|
||||
this.messages = [];
|
||||
try {
|
||||
if (query.startsWith('sql:')) {
|
||||
this.messages = [];
|
||||
this.results = await tfrpc.rpc.query(
|
||||
query.substring('sql:'.length),
|
||||
[]
|
||||
);
|
||||
} 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)]
|
||||
);
|
||||
search = this.renderRoot.getElementById('search');
|
||||
if (search) {
|
||||
search.value = query;
|
||||
search.focus();
|
||||
search.select();
|
||||
}
|
||||
this.messages = results;
|
||||
}
|
||||
} catch (e) {
|
||||
this.messages = [];
|
||||
this.results = [];
|
||||
this.error = e;
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,18 +101,58 @@ class TfTabSearchElement extends LitElement {
|
||||
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
|
||||
}
|
||||
|
||||
render() {
|
||||
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>`;
|
||||
}
|
||||
}
|
||||
|
||||
async query_results() {
|
||||
if (this.query !== this.last_query) {
|
||||
this.last_query = this.query;
|
||||
this.search(this.query);
|
||||
this._query = this.search(this.query);
|
||||
}
|
||||
let self = this;
|
||||
await this._query;
|
||||
}
|
||||
|
||||
render() {
|
||||
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">
|
||||
${until(
|
||||
this.query_results().then(this.render_results.bind(this)),
|
||||
html`<p>Searching...<span class="w3-animate-fading">🦀</span></p>`
|
||||
)}
|
||||
</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,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 TfUserElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -9,6 +9,7 @@ class TfUserElement extends LitElement {
|
||||
fallback_name: {type: String},
|
||||
icon_only: {type: Boolean},
|
||||
users: {type: Object},
|
||||
nolink: {type: Boolean},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,7 +38,11 @@ class TfUserElement extends LitElement {
|
||||
let name_string = name ?? this.fallback_name ?? this.id;
|
||||
name = this.icon_only
|
||||
? undefined
|
||||
: html`<a target="_top" href=${'#' + this.id}>${name_string}</a>`;
|
||||
: !this.nolink
|
||||
? html`<a target="_top" href=${'#' + encodeURIComponent(this.id)}
|
||||
>${name_string}</a
|
||||
>`
|
||||
: html`<span>${name_string}</span>`;
|
||||
|
||||
if (user) {
|
||||
let image_link = user.image;
|
||||
@@ -55,11 +60,15 @@ class TfUserElement extends LitElement {
|
||||
/>`;
|
||||
}
|
||||
}
|
||||
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>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
5
apps/trace.json
Normal file
5
apps/trace.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "📦",
|
||||
"previous": "&mhBOscDHiJ4VNnod27NOdRVC+4cXYZXIdYjsQBfmTYg=.sha256"
|
||||
}
|
||||
27
apps/trace/app.js
Normal file
27
apps/trace/app.js
Normal file
@@ -0,0 +1,27 @@
|
||||
async function main() {
|
||||
let speedscope_js = await utf8Decode(
|
||||
getFile('speedscope/speedscope-Y2522XSH.js')
|
||||
);
|
||||
speedscope_js = speedscope_js.replace(/alert\(`Cannot load.*?return/, '');
|
||||
app.setDocument(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>speedscope</title>
|
||||
<link rel="stylesheet" href="speedscope/speedscope-GHPHNKXC.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
delete window.localStorage;
|
||||
window.location.hash = '#profileURL=${core.url}../../trace';
|
||||
</script>
|
||||
<script>${speedscope_js}</script>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
}
|
||||
|
||||
main();
|
||||
7
apps/trace/speedscope/favicon-16x16-V2DMIAZS.js.map
Normal file
7
apps/trace/speedscope/favicon-16x16-V2DMIAZS.js.map
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
||||
|
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 679 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
7
apps/trace/speedscope/favicon-32x32-THY3JDJL.js.map
Normal file
7
apps/trace/speedscope/favicon-32x32-THY3JDJL.js.map
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
7
apps/trace/speedscope/favicon-M34RF7BI.js.map
Normal file
7
apps/trace/speedscope/favicon-M34RF7BI.js.map
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
||||
@@ -12,9 +12,7 @@
|
||||
"type": "number"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"C"
|
||||
],
|
||||
"const": "C",
|
||||
"title": "type",
|
||||
"type": "string"
|
||||
}
|
||||
@@ -29,8 +27,8 @@
|
||||
},
|
||||
"FileFormat.EventType": {
|
||||
"enum": [
|
||||
"C",
|
||||
"O"
|
||||
"O",
|
||||
"C"
|
||||
],
|
||||
"title": "FileFormat.EventType",
|
||||
"type": "string"
|
||||
@@ -64,9 +62,7 @@
|
||||
"type": "number"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"evented"
|
||||
],
|
||||
"const": "evented",
|
||||
"title": "type",
|
||||
"type": "string"
|
||||
},
|
||||
@@ -89,9 +85,7 @@
|
||||
"FileFormat.File": {
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"enum": [
|
||||
"https://www.speedscope.app/file-format-schema.json"
|
||||
],
|
||||
"const": "https://www.speedscope.app/file-format-schema.json",
|
||||
"title": "$schema",
|
||||
"type": "string"
|
||||
},
|
||||
@@ -109,14 +103,7 @@
|
||||
},
|
||||
"profiles": {
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/FileFormat.EventedProfile"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/FileFormat.SampledProfile"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/FileFormat.Profile"
|
||||
},
|
||||
"title": "profiles",
|
||||
"type": "array"
|
||||
@@ -192,7 +179,8 @@
|
||||
{
|
||||
"$ref": "#/definitions/FileFormat.SampledProfile"
|
||||
}
|
||||
]
|
||||
],
|
||||
"title": "FileFormat.Profile"
|
||||
},
|
||||
"FileFormat.ProfileType": {
|
||||
"enum": [
|
||||
@@ -227,9 +215,7 @@
|
||||
"type": "number"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"sampled"
|
||||
],
|
||||
"const": "sampled",
|
||||
"title": "type",
|
||||
"type": "string"
|
||||
},
|
||||
@@ -298,9 +284,7 @@
|
||||
"type": "number"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"O"
|
||||
],
|
||||
"const": "O",
|
||||
"title": "type",
|
||||
"type": "string"
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
<link rel="icon" type="image/x-icon" href="favicon-FOKUP5Y5.ico">
|
||||
</head>
|
||||
<body>
|
||||
<script src="speedscope-7YPLLUY2.js"></script>
|
||||
<script src="speedscope-Y2522XSH.js"></script>
|
||||
|
||||
|
||||
|
||||
BIN
apps/trace/speedscope/jfrview_bg-BLJXNNQB.wasm
Normal file
BIN
apps/trace/speedscope/jfrview_bg-BLJXNNQB.wasm
Normal file
Binary file not shown.
3
apps/trace/speedscope/release.txt
Normal file
3
apps/trace/speedscope/release.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
speedscope@1.25.0
|
||||
Wed Dec 3 07:18:39 PM EST 2025
|
||||
810efdf0a4868bb28f1300f4daacd0db6b8a95bd
|
||||
7
apps/trace/speedscope/speedscope-GHPHNKXC.css.map
Normal file
7
apps/trace/speedscope/speedscope-GHPHNKXC.css.map
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": ["../../assets/reset.css", "../../assets/source-code-pro.css"],
|
||||
"sourcesContent": ["/* http://meyerweb.com/eric/tools/css/reset/\n v2.0 | 20110126\n License: none (public domain)\n*/\nhtml, body, div, span, applet, object, iframe,\nh1, h2, h3, h4, h5, h6, p, blockquote, pre,\na, abbr, acronym, address, big, cite, code,\ndel, dfn, em, img, ins, kbd, q, s, samp,\nsmall, strike, strong, sub, sup, tt, var,\nb, u, i, center,\ndl, dt, dd, ol, ul, li,\nfieldset, form, label, legend,\ntable, caption, tbody, tfoot, thead, tr, th, td,\narticle, aside, canvas, details, embed,\nfigure, figcaption, footer, header, hgroup,\nmenu, nav, output, ruby, section, summary,\ntime, mark, audio, video {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tfont-size: 100%;\n\tfont: inherit;\n\tvertical-align: baseline;\n}\n/* HTML5 display-role reset for older browsers */\narticle, aside, details, figcaption, figure,\nfooter, header, hgroup, menu, nav, section {\n\tdisplay: block;\n}\nbody {\n\tline-height: 1;\n}\nol, ul {\n\tlist-style: none;\n}\nblockquote, q {\n\tquotes: none;\n}\nblockquote:before, blockquote:after,\nq:before, q:after {\n\tcontent: '';\n\tcontent: none;\n}\ntable {\n\tborder-collapse: collapse;\n\tborder-spacing: 0;\n}\n\n/* Prevent overscrolling */\n/* https://stackoverflow.com/a/17899813 */\nhtml {\n overflow: hidden;\n height: 100%;\n}\nbody {\n height: 100%;\n overflow: auto;\n}", "@font-face{\n\tfont-family: 'Source Code Pro';\n\tfont-weight: 400;\n\tfont-style: normal;\n\tfont-stretch: normal;\n\tsrc: url('./source-code-pro/SourceCodePro-Regular.ttf.woff2') format('woff2');\n}\n"],
|
||||
"mappings": "AAIA,KAAM,KAAM,IAAK,KAAM,OAAQ,OAAQ,OACvC,GAAI,GAAI,GAAI,GAAI,GAAI,GAAI,EAAG,WAAY,IACvC,EAAG,KAAM,QAAS,QAAS,IAAK,KAAM,KACtC,IAAK,IAAK,GAAI,IAAK,IAAK,IAAK,EAAG,EAAG,KACnC,MAAO,OAAQ,OAAQ,IAAK,IAAK,GAAI,IACrC,EAAG,EAAG,EAAG,OACT,GAAI,GAAI,GAAI,GAAI,GAAI,GACpB,SAAU,KAAM,MAAO,OACvB,MAAO,QAAS,MAAO,MAAO,MAAO,GAAI,GAAI,GAC7C,QAAS,MAAO,OAAQ,QAAS,MACjC,OAAQ,WAAY,OAAQ,OAAQ,OACpC,KAAM,IAAK,OAAQ,KAAM,QAAS,QAClC,KAAM,KAAM,MAAO,MAhBnB,OAiBS,EAjBT,QAkBU,EACT,OAAQ,EACR,UAAW,KACX,KAAM,QACN,eAAgB,QACjB,CAEA,QAAS,MAAO,QAAS,WAAY,OACrC,OAAQ,OAAQ,OAAQ,KAAM,IAAK,QAClC,QAAS,KACV,CACA,KACC,YAAa,CACd,CACA,GAAI,GACH,WAAY,IACb,CACA,WAAY,EACX,OAAQ,IACT,CACA,UAAU,QAAS,UAAU,OAC7B,CAAC,QAAS,CAAC,OACV,QAAS,GACT,QAAS,IACV,CACA,MACC,gBAAiB,SACjB,eAAgB,CACjB,CAIA,KACI,SAAU,OACV,OAAQ,IACZ,CACA,KACI,OAAQ,KACR,SAAU,IACd,CCzDA,WACC,YAAa,gBACb,YAAa,IACb,WAAY,OACZ,aAAc,OACd,IAAK,kDAAyD,OAAO,QACtE",
|
||||
"names": []
|
||||
}
|
||||
189
apps/trace/speedscope/speedscope-Y2522XSH.js
Normal file
189
apps/trace/speedscope/speedscope-Y2522XSH.js
Normal file
File diff suppressed because one or more lines are too long
7
apps/trace/speedscope/speedscope-Y2522XSH.js.map
Normal file
7
apps/trace/speedscope/speedscope-Y2522XSH.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "👋",
|
||||
"previous": "&5NkMRSgcMqCYF3xcLOBmaytkoxfV9zx4br7JladKPTs=.sha256"
|
||||
"previous": "&m7sF3iaAEqf6PtaTBN+upiHNNBPjrq15kZUuxRWiAag=.sha256"
|
||||
}
|
||||
|
||||
@@ -8,14 +8,10 @@
|
||||
<link rel="stylesheet" href="regular.min.css" />
|
||||
<link rel="stylesheet" href="solid.min.css" />
|
||||
<link rel="stylesheet" href="brands.min.css" />
|
||||
|
||||
<style>
|
||||
img {
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
.mySlides {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<base target="_top" />
|
||||
</head>
|
||||
@@ -104,14 +100,14 @@
|
||||
src="googleplay.svg"
|
||||
style="height: 2em; margin: 0"
|
||||
/>
|
||||
Get it on Google Play (Open Testing)
|
||||
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"
|
||||
href="https://apps.apple.com/us/app/tilde-friends/id6742085604"
|
||||
>
|
||||
<img src="ios.svg" style="height: 2em; margin: 0" />
|
||||
Get it on iOS (TestFlight)
|
||||
Get it on iOS
|
||||
</a>
|
||||
</p>
|
||||
<p>Just launch the app.</p>
|
||||
@@ -298,7 +294,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
|
||||
@@ -340,10 +336,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"
|
||||
@@ -351,13 +343,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>
|
||||
@@ -370,9 +362,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>
|
||||
|
||||
221
core/app.js
221
core/app.js
@@ -1,221 +0,0 @@
|
||||
import * as core from './core.js';
|
||||
|
||||
let gSessionIndex = 0;
|
||||
|
||||
function App() {
|
||||
this._send_queue = [];
|
||||
this.calls = {};
|
||||
this._next_call_id = 1;
|
||||
return this;
|
||||
}
|
||||
|
||||
App.prototype.makeFunction = function (api) {
|
||||
let self = this;
|
||||
let result = function () {
|
||||
let id = self._next_call_id++;
|
||||
while (!id || self.calls[id]) {
|
||||
id = self._next_call_id++;
|
||||
}
|
||||
let promise = new Promise(function (resolve, reject) {
|
||||
self.calls[id] = {resolve: resolve, reject: reject};
|
||||
});
|
||||
let message = {
|
||||
action: 'tfrpc',
|
||||
method: api[0],
|
||||
params: [...arguments],
|
||||
id: id,
|
||||
};
|
||||
self.send(message);
|
||||
return promise;
|
||||
};
|
||||
Object.defineProperty(result, 'name', {value: api[0], writable: false});
|
||||
return result;
|
||||
};
|
||||
|
||||
App.prototype.send = function (message) {
|
||||
if (this._send_queue) {
|
||||
if (this._on_output) {
|
||||
this._send_queue.forEach((x) => this._on_output(x));
|
||||
this._send_queue = null;
|
||||
} else if (message) {
|
||||
this._send_queue.push(message);
|
||||
}
|
||||
}
|
||||
if (message && this._on_output) {
|
||||
this._on_output(message);
|
||||
}
|
||||
};
|
||||
|
||||
exports.app_socket = async function socket(request, response) {
|
||||
let process;
|
||||
let options = {};
|
||||
let credentials = await httpd.auth_query(request.headers);
|
||||
|
||||
response.onClose = async function () {
|
||||
if (process && process.task) {
|
||||
process.task.kill();
|
||||
}
|
||||
if (process) {
|
||||
process.timeout = 0;
|
||||
}
|
||||
};
|
||||
|
||||
response.onMessage = async function (event) {
|
||||
if (event.opCode == 0x1 || event.opCode == 0x2) {
|
||||
let message;
|
||||
try {
|
||||
message = JSON.parse(event.data);
|
||||
} catch (error) {
|
||||
print(
|
||||
'WebSocket error:',
|
||||
error,
|
||||
event.data,
|
||||
event.data.length,
|
||||
event.opCode
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!process && message.action == 'hello') {
|
||||
let packageOwner;
|
||||
let packageName;
|
||||
let blobId;
|
||||
let match;
|
||||
let parentApp;
|
||||
if (
|
||||
(match = /^\/([&%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(message.path))
|
||||
) {
|
||||
blobId = match[1];
|
||||
} else if ((match = /^\/\~([^\/]+)\/([^\/]+)\/$/.exec(message.path))) {
|
||||
packageOwner = match[1];
|
||||
packageName = match[2];
|
||||
blobId = await new Database(packageOwner).get('path:' + packageName);
|
||||
if (!blobId) {
|
||||
response.send(
|
||||
JSON.stringify({
|
||||
action: 'tfrpc',
|
||||
method: 'error',
|
||||
params: [message.path + ' not found'],
|
||||
id: -1,
|
||||
}),
|
||||
0x1
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (packageOwner != 'core') {
|
||||
let coreId = await new Database('core').get('path:' + packageName);
|
||||
parentApp = {
|
||||
path: '/~core/' + packageName + '/',
|
||||
id: coreId,
|
||||
};
|
||||
}
|
||||
}
|
||||
response.send(
|
||||
JSON.stringify(
|
||||
Object.assign(
|
||||
{
|
||||
action: 'session',
|
||||
credentials: credentials,
|
||||
parentApp: parentApp,
|
||||
id: blobId,
|
||||
},
|
||||
await ssb.getIdentityInfo(
|
||||
credentials?.session?.name,
|
||||
packageOwner,
|
||||
packageName
|
||||
)
|
||||
)
|
||||
),
|
||||
0x1
|
||||
);
|
||||
|
||||
options.api = message.api || [];
|
||||
options.credentials = credentials;
|
||||
options.packageOwner = packageOwner;
|
||||
options.packageName = packageName;
|
||||
options.url = message.url;
|
||||
let sessionId = 'session_' + (gSessionIndex++).toString();
|
||||
if (blobId) {
|
||||
if (message.edit_only) {
|
||||
response.send(
|
||||
JSON.stringify({action: 'ready', edit_only: true}),
|
||||
0x1
|
||||
);
|
||||
} else {
|
||||
process = await core.getProcessBlob(blobId, sessionId, options);
|
||||
}
|
||||
}
|
||||
if (process) {
|
||||
process.client_api.tfrpc = function (message) {
|
||||
if (message.id) {
|
||||
let calls = process?.app?.calls;
|
||||
if (calls) {
|
||||
let call = calls[message.id];
|
||||
if (call) {
|
||||
if (message.error !== undefined) {
|
||||
call.reject(message.error);
|
||||
} else {
|
||||
call.resolve(message.result);
|
||||
}
|
||||
delete calls[message.id];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
process.app._on_output = (message) =>
|
||||
response.send(JSON.stringify(message), 0x1);
|
||||
process.app.send();
|
||||
}
|
||||
|
||||
let ping = function () {
|
||||
let now = Date.now();
|
||||
let again = true;
|
||||
if (now - process.lastActive < process.timeout) {
|
||||
// Active.
|
||||
} else if (process.lastPing > process.lastActive) {
|
||||
// We lost them.
|
||||
if (process.task) {
|
||||
process.task.kill();
|
||||
}
|
||||
again = false;
|
||||
} else {
|
||||
// Idle. Ping them.
|
||||
response.send('', 0x9);
|
||||
process.lastPing = now;
|
||||
}
|
||||
|
||||
if (again && process.timeout) {
|
||||
setTimeout(ping, process.timeout);
|
||||
}
|
||||
};
|
||||
|
||||
if (process && process.timeout > 0) {
|
||||
setTimeout(ping, process.timeout);
|
||||
}
|
||||
} else {
|
||||
if (process) {
|
||||
if (process.client_api[message.action]) {
|
||||
process.client_api[message.action](message);
|
||||
} else if (process.eventHandlers['message']) {
|
||||
await core.invoke(process.eventHandlers['message'], [message]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (event.opCode == 0x8) {
|
||||
// Close.
|
||||
if (process && process.task) {
|
||||
process.task.kill();
|
||||
}
|
||||
response.send(event.data, 0x8);
|
||||
} else if (event.opCode == 0xa) {
|
||||
// PONG
|
||||
}
|
||||
|
||||
if (process) {
|
||||
process.lastActive = Date.now();
|
||||
}
|
||||
};
|
||||
|
||||
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>
|
||||
`;
|
||||
}
|
||||
|
||||
432
core/client.js
432
core/client.js
@@ -9,18 +9,17 @@
|
||||
import {LitElement, html, css, svg} from '/lit/lit-all.min.js';
|
||||
|
||||
let cm6;
|
||||
let gSocket;
|
||||
|
||||
let gCurrentFile;
|
||||
let gFiles = {};
|
||||
let gApp = {files: {}, emoji: '📦'};
|
||||
let gEditor;
|
||||
let gOriginalInput;
|
||||
let gUnloading;
|
||||
let g_socket;
|
||||
let g_current_file;
|
||||
let g_files = {};
|
||||
let g_app = {files: {}, emoji: '📦'};
|
||||
let g_editor;
|
||||
let g_unloading;
|
||||
|
||||
let kErrorColor = '#dc322f';
|
||||
let kDisconnectColor = '#f00';
|
||||
let kStatusColor = '#fff';
|
||||
let k_color_error = '#dc322f';
|
||||
let k_color_disconnect = '#f00';
|
||||
let k_color_status = '#fff';
|
||||
/** \endcond */
|
||||
|
||||
/** Functions that server-side app code can call through the app object. */
|
||||
@@ -30,7 +29,10 @@ const k_api = {
|
||||
error: {args: ['error'], func: api_error},
|
||||
localStorageSet: {args: ['key', 'value'], func: api_localStorageSet},
|
||||
localStorageGet: {args: ['key'], func: api_localStorageGet},
|
||||
requestPermission: {args: ['permission', 'id'], func: api_requestPermission},
|
||||
requestPermission: {
|
||||
args: ['permission', 'id', 'description'],
|
||||
func: api_requestPermission,
|
||||
},
|
||||
print: {args: ['...'], func: api_print},
|
||||
setHash: {args: ['hash'], func: api_setHash},
|
||||
};
|
||||
@@ -72,7 +74,7 @@ class TfNavigationElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Toggle editor visibility.
|
||||
* @param event The HTML event.
|
||||
*/
|
||||
toggle_edit(event) {
|
||||
@@ -85,7 +87,7 @@ class TfNavigationElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Remove a stored permission.
|
||||
* @param key The permission to reset.
|
||||
*/
|
||||
reset_permission(key) {
|
||||
@@ -93,7 +95,7 @@ class TfNavigationElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Get or create a spark line.
|
||||
* @param key The spark line identifier.
|
||||
* @param options Spark line options.
|
||||
* @return A spark line HTML element.
|
||||
@@ -262,8 +264,8 @@ class TfNavigationElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Render the permissions popup.
|
||||
* @return Lit HTML.
|
||||
*/
|
||||
render_permissions() {
|
||||
if (this.show_permissions) {
|
||||
@@ -312,8 +314,8 @@ class TfNavigationElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Render the navigation bar.
|
||||
* @return Lit HTML.
|
||||
*/
|
||||
render() {
|
||||
let self = this;
|
||||
@@ -370,16 +372,15 @@ class TfNavigationElement extends LitElement {
|
||||
>${this.version?.number}</span
|
||||
>
|
||||
<a
|
||||
class="w3-bar-item"
|
||||
class="w3-bar-item w3-button w3-text-white"
|
||||
accesskey="h"
|
||||
@mouseover=${set_access_key_title}
|
||||
data-tip="Open home app."
|
||||
href="/"
|
||||
style="color: #fff; white-space: nowrap"
|
||||
>TF</a
|
||||
>
|
||||
<a
|
||||
class="w3-bar-item"
|
||||
class="w3-bar-item w3-button w3-text-light-gray"
|
||||
accesskey="a"
|
||||
@mouseover=${set_access_key_title}
|
||||
data-tip="Open apps list."
|
||||
@@ -387,7 +388,7 @@ class TfNavigationElement extends LitElement {
|
||||
>apps</a
|
||||
>
|
||||
<a
|
||||
class="w3-bar-item"
|
||||
class="w3-bar-item w3-button w3-text-light-gray"
|
||||
accesskey="e"
|
||||
@mouseover=${set_access_key_title}
|
||||
data-tip="Toggle the app editor."
|
||||
@@ -396,7 +397,7 @@ class TfNavigationElement extends LitElement {
|
||||
>edit</a
|
||||
>
|
||||
<a
|
||||
class="w3-bar-item"
|
||||
class="w3-bar-item w3-button"
|
||||
accesskey="p"
|
||||
@mouseover=${set_access_key_title}
|
||||
data-tip="View and change permissions."
|
||||
@@ -410,7 +411,7 @@ class TfNavigationElement extends LitElement {
|
||||
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
|
||||
<div
|
||||
class="w3-bar-item"
|
||||
style="color: ${this.status.color ?? kStatusColor}"
|
||||
style="color: ${this.status.color ?? k_color_status}"
|
||||
>
|
||||
${this.status.message}
|
||||
</div>
|
||||
@@ -429,7 +430,7 @@ class TfNavigationElement extends LitElement {
|
||||
<div class="w3-model w3-animate-top" style="position: absolute; left: 50%; transform: translate(-50%); z-index: 1">
|
||||
<dijv class="w3-modal-content w3-card-4" style="display: block; padding: 1em">
|
||||
<span id="close_error" @click=${self.clear_error} class="w3-button w3-display-topright">×</span>
|
||||
<div style="color: ${this.status.color ?? kErrorColor}"><b>ERROR:</b><p id="error" style="white-space: pre">${this.status.message}</p></div>
|
||||
<div style="color: ${this.status.color ?? k_color_error}"><b>ERROR:</b><p id="error" style="white-space: pre">${this.status.message}</p></div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
@@ -438,10 +439,13 @@ class TfNavigationElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tf-navigation element.
|
||||
*/
|
||||
customElements.define('tf-navigation', TfNavigationElement);
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* A file in the files sidebar.
|
||||
*/
|
||||
class TfFilesElement extends LitElement {
|
||||
/**
|
||||
@@ -467,7 +471,7 @@ class TfFilesElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Select a clicked file.
|
||||
* @param file The file.
|
||||
*/
|
||||
file_click(file) {
|
||||
@@ -483,9 +487,9 @@ class TfFilesElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Render a single file in the file list.
|
||||
* @param file The file.
|
||||
* @returns Lit HTML.
|
||||
* @return Lit HTML.
|
||||
*/
|
||||
render_file(file) {
|
||||
let classes = ['file'];
|
||||
@@ -507,7 +511,7 @@ class TfFilesElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Create a file entry for a dropped file.
|
||||
* @param event The event.
|
||||
*/
|
||||
async drop(event) {
|
||||
@@ -518,7 +522,7 @@ class TfFilesElement extends LitElement {
|
||||
for (let file of event.dataTransfer.files) {
|
||||
let buffer = await file.arrayBuffer();
|
||||
let text = new TextDecoder('latin1').decode(buffer);
|
||||
gFiles[file.name] = {
|
||||
g_files[file.name] = {
|
||||
doc: cm6.EditorState.create({
|
||||
doc: text,
|
||||
extensions: cm6.extensions,
|
||||
@@ -526,14 +530,14 @@ class TfFilesElement extends LitElement {
|
||||
buffer: buffer,
|
||||
isNew: true,
|
||||
};
|
||||
gCurrentFile = file.name;
|
||||
g_current_file = file.name;
|
||||
}
|
||||
openFile(gCurrentFile);
|
||||
openFile(g_current_file);
|
||||
updateFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Called when a file starts being dragged over the file.
|
||||
* @param event The event.
|
||||
*/
|
||||
drag_enter(event) {
|
||||
@@ -543,7 +547,7 @@ class TfFilesElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Called when a file stops being dragged over the file.
|
||||
* @param event The event.
|
||||
*/
|
||||
drag_leave(event) {
|
||||
@@ -554,7 +558,7 @@ class TfFilesElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* Drag over event.
|
||||
* Called when a file is being dragged over the file.
|
||||
* @param event The event.
|
||||
*/
|
||||
drag_over(event) {
|
||||
@@ -562,8 +566,8 @@ class TfFilesElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Render the file.
|
||||
* @return Lit HTML.
|
||||
*/
|
||||
render() {
|
||||
let self = this;
|
||||
@@ -610,7 +614,7 @@ class TfFilesElement extends LitElement {
|
||||
customElements.define('tf-files', TfFilesElement);
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* The files pane element.
|
||||
*/
|
||||
class TfFilesPaneElement extends LitElement {
|
||||
/**
|
||||
@@ -635,7 +639,7 @@ class TfFilesPaneElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Set whether the files pane is expanded.
|
||||
* @param expanded Whether the files pane is expanded.
|
||||
*/
|
||||
set_expanded(expanded) {
|
||||
@@ -644,8 +648,8 @@ class TfFilesPaneElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Render the files pane element.
|
||||
* @return Lit HTML.
|
||||
*/
|
||||
render() {
|
||||
let self = this;
|
||||
@@ -704,7 +708,7 @@ class TfFilesPaneElement extends LitElement {
|
||||
customElements.define('tf-files-pane', TfFilesPaneElement);
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* A tiny graph.
|
||||
*/
|
||||
class TfSparkLineElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -724,9 +728,9 @@ class TfSparkLineElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} key
|
||||
* @param {*} value
|
||||
* Add a data point to the graph.
|
||||
* @param key The line to which the point applies.
|
||||
* @param value The numeric value of the data point.
|
||||
*/
|
||||
append(key, value) {
|
||||
let line = null;
|
||||
@@ -753,9 +757,9 @@ class TfSparkLineElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} line
|
||||
* @returns
|
||||
* Render a single series line.
|
||||
* @param line The line data.
|
||||
* @return Lit HTML.
|
||||
*/
|
||||
render_line(line) {
|
||||
if (line?.values?.length >= 2) {
|
||||
@@ -771,8 +775,8 @@ class TfSparkLineElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Render the graph.
|
||||
* @return Lit HTML.
|
||||
*/
|
||||
render() {
|
||||
let max =
|
||||
@@ -799,7 +803,9 @@ class TfSparkLineElement extends LitElement {
|
||||
|
||||
customElements.define('tf-sparkline', TfSparkLineElement);
|
||||
|
||||
// TODOC
|
||||
/**
|
||||
* A keyboard key is pressed down.
|
||||
*/
|
||||
window.addEventListener('keydown', function (event) {
|
||||
if (event.keyCode == 83 && (event.altKey || event.ctrlKey)) {
|
||||
if (editing()) {
|
||||
@@ -860,24 +866,23 @@ function ensureLoaded(nodes, callback) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Check whether the editior is currently visible.
|
||||
* @return true if the editor is visible.
|
||||
*/
|
||||
function editing() {
|
||||
return document.getElementById('editPane').style.display != 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Check whether only the editor is visible and the app is hidden.
|
||||
* @return true if the editor is visible and the app is not.
|
||||
*/
|
||||
function is_edit_only() {
|
||||
return window.location.search == '?editonly=1' || window.innerWidth < 1024;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Show the editor.
|
||||
*/
|
||||
async function edit() {
|
||||
if (editing()) {
|
||||
@@ -891,11 +896,11 @@ async function edit() {
|
||||
: 'flex';
|
||||
|
||||
try {
|
||||
if (!gEditor) {
|
||||
if (!g_editor) {
|
||||
cm6 = await import('/codemirror/cm6.js');
|
||||
gEditor = cm6.TildeFriendsEditorView(document.getElementById('editor'));
|
||||
g_editor = cm6.TildeFriendsEditorView(document.getElementById('editor'));
|
||||
}
|
||||
gEditor.onDocChange = updateFiles;
|
||||
g_editor.onDocChange = updateFiles;
|
||||
await load();
|
||||
} catch (error) {
|
||||
alert(`${error.message}\n\n${error.stack}`);
|
||||
@@ -904,10 +909,10 @@ async function edit() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Open a performance trace.
|
||||
*/
|
||||
function trace() {
|
||||
window.open(`/speedscope/#profileURL=${encodeURIComponent('/trace')}`);
|
||||
window.open(`/~core/trace/`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -928,13 +933,13 @@ function loadFile(name, id) {
|
||||
return response.text();
|
||||
})
|
||||
.then(function (text) {
|
||||
gFiles[name].doc = cm6.EditorState.create({
|
||||
g_files[name].doc = cm6.EditorState.create({
|
||||
doc: text,
|
||||
extensions: cm6.extensions,
|
||||
});
|
||||
gFiles[name].original = gFiles[name].doc.doc.toString();
|
||||
if (!Object.values(gFiles).some((x) => !x.doc)) {
|
||||
openFile(Object.keys(gFiles).sort()[0]);
|
||||
g_files[name].original = g_files[name].doc.doc.toString();
|
||||
if (!Object.values(g_files).some((x) => !x.doc)) {
|
||||
openFile(Object.keys(g_files).sort()[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -952,37 +957,37 @@ async function load(path) {
|
||||
} else if (response.status != 404) {
|
||||
throw new Error(response.status + ' ' + response.statusText);
|
||||
}
|
||||
gFiles = {};
|
||||
g_files = {};
|
||||
let isApp = false;
|
||||
let promises = [];
|
||||
|
||||
if (json && json['type'] == 'tildefriends-app') {
|
||||
isApp = true;
|
||||
Object.keys(json['files']).forEach(function (name) {
|
||||
gFiles[name] = {};
|
||||
g_files[name] = {};
|
||||
promises.push(loadFile(name, json['files'][name]));
|
||||
});
|
||||
if (Object.keys(json['files']).length == 0) {
|
||||
document.getElementById('editPane').style.display = 'flex';
|
||||
}
|
||||
gApp = json;
|
||||
gApp.emoji = gApp.emoji || '📦';
|
||||
document.getElementById('icon').innerHTML = gApp.emoji;
|
||||
g_app = json;
|
||||
g_app.emoji = g_app.emoji || '📦';
|
||||
document.getElementById('icon').innerHTML = g_app.emoji;
|
||||
}
|
||||
if (!isApp) {
|
||||
document.getElementById('editPane').style.display = 'flex';
|
||||
let text = '// New script.\n';
|
||||
gCurrentFile = 'app.js';
|
||||
gFiles[gCurrentFile] = {
|
||||
g_current_file = 'app.js';
|
||||
g_files[g_current_file] = {
|
||||
doc: cm6.EditorState.create({doc: text, extensions: cm6.extensions}),
|
||||
};
|
||||
openFile(gCurrentFile);
|
||||
openFile(g_current_file);
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Hide the editor.
|
||||
*/
|
||||
function closeEditor() {
|
||||
window.localStorage.setItem('editing', '0');
|
||||
@@ -990,14 +995,6 @@ function closeEditor() {
|
||||
document.getElementById('viewPane').style.display = 'flex';
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
*/
|
||||
function explodePath() {
|
||||
return /^\/~([^\/]+)\/([^\/]+)(.*)/.exec(window.location.pathname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the app.
|
||||
* @param save_to An optional path to which to save the app.
|
||||
@@ -1005,13 +1002,14 @@ function explodePath() {
|
||||
*/
|
||||
function save(save_to) {
|
||||
document.getElementById('save').disabled = true;
|
||||
if (gCurrentFile) {
|
||||
gFiles[gCurrentFile].doc = gEditor.state;
|
||||
if (g_current_file) {
|
||||
g_files[g_current_file].doc = g_editor.state;
|
||||
if (
|
||||
!gFiles[gCurrentFile].isNew &&
|
||||
!gFiles[gCurrentFile].doc.doc.toString() == gFiles[gCurrentFile].original
|
||||
!g_files[g_current_file].isNew &&
|
||||
!g_files[g_current_file].doc.doc.toString() ==
|
||||
g_files[g_current_file].original
|
||||
) {
|
||||
delete gFiles[gCurrentFile].buffer;
|
||||
delete g_files[g_current_file].buffer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1026,8 +1024,8 @@ function save(save_to) {
|
||||
}
|
||||
|
||||
let promises = [];
|
||||
for (let name of Object.keys(gFiles)) {
|
||||
let file = gFiles[name];
|
||||
for (let name of Object.keys(g_files)) {
|
||||
let file = g_files[name];
|
||||
if (!file.isNew && file.doc.doc.toString() == file.original) {
|
||||
continue;
|
||||
}
|
||||
@@ -1069,14 +1067,14 @@ function save(save_to) {
|
||||
let app = {
|
||||
type: 'tildefriends-app',
|
||||
files: Object.fromEntries(
|
||||
Object.keys(gFiles).map((x) => [x, gFiles[x].id || gApp.files[x]])
|
||||
Object.keys(g_files).map((x) => [x, g_files[x].id || g_app.files[x]])
|
||||
),
|
||||
emoji: gApp.emoji || '📦',
|
||||
emoji: g_app.emoji || '📦',
|
||||
};
|
||||
Object.values(gFiles).forEach(function (file) {
|
||||
Object.values(g_files).forEach(function (file) {
|
||||
delete file.id;
|
||||
});
|
||||
gApp = JSON.parse(JSON.stringify(app));
|
||||
g_app = JSON.parse(JSON.stringify(app));
|
||||
|
||||
return fetch(save_path + 'save', {
|
||||
method: 'POST',
|
||||
@@ -1091,7 +1089,7 @@ function save(save_to) {
|
||||
|
||||
if (save_path != window.location.pathname) {
|
||||
alert('Saved to ' + save_path + '.');
|
||||
} else if (!gFiles['app.js']) {
|
||||
} else if (!g_files['app.js']) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
reconnect(save_path);
|
||||
@@ -1103,7 +1101,7 @@ function save(save_to) {
|
||||
})
|
||||
.finally(function () {
|
||||
document.getElementById('save').disabled = false;
|
||||
Object.values(gFiles).forEach(function (file) {
|
||||
Object.values(g_files).forEach(function (file) {
|
||||
file.original = file.doc.doc.toString();
|
||||
});
|
||||
updateFiles();
|
||||
@@ -1111,18 +1109,18 @@ function save(save_to) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Prompt to set the app icon.
|
||||
*/
|
||||
function changeIcon() {
|
||||
let value = prompt('Enter a new app icon emoji:');
|
||||
if (value !== undefined) {
|
||||
gApp.emoji = value || '📦';
|
||||
document.getElementById('icon').innerHTML = gApp.emoji;
|
||||
g_app.emoji = value || '📦';
|
||||
document.getElementById('icon').innerHTML = g_app.emoji;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Prompt to delete the current app.
|
||||
*/
|
||||
function deleteApp() {
|
||||
let name = document.getElementById('name');
|
||||
@@ -1143,8 +1141,8 @@ function deleteApp() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Get the current app URL.
|
||||
* @return The app URL.
|
||||
*/
|
||||
function url() {
|
||||
let hash = window.location.href.indexOf('#');
|
||||
@@ -1162,8 +1160,8 @@ function url() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Get the window hash without the lone '#' if it is empty.
|
||||
* @return The hash.
|
||||
*/
|
||||
function hash() {
|
||||
return window.location.hash != '#' ? window.location.hash : '';
|
||||
@@ -1188,15 +1186,18 @@ function api_postMessage(message) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Show an error.
|
||||
* @param error The error.
|
||||
*/
|
||||
function api_error(error) {
|
||||
if (error) {
|
||||
if (typeof error == 'string') {
|
||||
setStatusMessage('⚠️ ' + error, kErrorColor);
|
||||
setStatusMessage('⚠️ ' + error, k_color_error);
|
||||
} else {
|
||||
setStatusMessage('⚠️ ' + error.message + '\n' + error.stack, kErrorColor);
|
||||
setStatusMessage(
|
||||
'⚠️ ' + error.message + '\n' + error.stack,
|
||||
k_color_error
|
||||
);
|
||||
}
|
||||
}
|
||||
console.log('error', error);
|
||||
@@ -1223,10 +1224,10 @@ function api_localStorageGet(key) {
|
||||
/**
|
||||
* Request a permission
|
||||
* @param permission The permission to request.
|
||||
* @param id The id requeesting the permission.
|
||||
* @param description An optional human-readable description of the action for which the permission is being requested.
|
||||
* @return A promise fulfilled if the permission was granted.
|
||||
*/
|
||||
function api_requestPermission(permission, id) {
|
||||
function api_requestPermission(permission, description) {
|
||||
let outer = document.createElement('div');
|
||||
outer.classList.add('permissions');
|
||||
|
||||
@@ -1243,6 +1244,18 @@ function api_requestPermission(permission, id) {
|
||||
div.appendChild(span);
|
||||
container.appendChild(div);
|
||||
|
||||
if (description) {
|
||||
container.appendChild(document.createTextNode('for the action:'));
|
||||
let description_div = document.createElement('div');
|
||||
description_div.classList.add('w3-border');
|
||||
description_div.classList.add('w3-padding');
|
||||
description_div.style.backgroundColor = '#666';
|
||||
description_div.style.maxHeight = '3em';
|
||||
description_div.style.overflow = 'auto';
|
||||
description_div.appendChild(document.createTextNode(description));
|
||||
container.appendChild(description_div);
|
||||
}
|
||||
|
||||
div = document.createElement('div');
|
||||
div.style = 'padding: 1em';
|
||||
let check = document.createElement('input');
|
||||
@@ -1293,7 +1306,7 @@ function api_requestPermission(permission, id) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Log from the app to the console.
|
||||
*/
|
||||
function api_print() {
|
||||
console.log('app>', ...arguments);
|
||||
@@ -1308,12 +1321,12 @@ function api_setHash(hash) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Process an incoming WebSocket message.
|
||||
* @param message The message.
|
||||
*/
|
||||
function _receive_websocket_message(message) {
|
||||
if (message && message.action == 'session') {
|
||||
setStatusMessage('🟢 Executing...', kStatusColor);
|
||||
setStatusMessage('🟢 Executing...', k_color_status);
|
||||
let navigation = document.getElementsByTagName('tf-navigation')[0];
|
||||
navigation.credentials = message.credentials;
|
||||
navigation.identities = message.identities;
|
||||
@@ -1413,7 +1426,7 @@ function setStatusMessage(message, color) {
|
||||
document.getElementsByTagName('tf-navigation')[0].status = {
|
||||
message: message,
|
||||
color: color,
|
||||
is_error: color == kErrorColor,
|
||||
is_error: color == k_color_error,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1423,26 +1436,26 @@ function setStatusMessage(message, color) {
|
||||
*/
|
||||
function send(value) {
|
||||
try {
|
||||
if (gSocket && gSocket.readyState == gSocket.OPEN) {
|
||||
gSocket.send(JSON.stringify(value));
|
||||
if (g_socket && g_socket.readyState == g_socket.OPEN) {
|
||||
g_socket.send(JSON.stringify(value));
|
||||
}
|
||||
} catch (error) {
|
||||
setStatusMessage('🤷 Send failed: ' + error.toString(), kErrorColor);
|
||||
setStatusMessage('🤷 Send failed: ' + error.toString(), k_color_error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Notify the app of the window hash changing.
|
||||
*/
|
||||
function hashChange() {
|
||||
send({event: 'hashChange', hash: window.location.hash});
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Make sure the app is connected on window focus, and notify the app.
|
||||
*/
|
||||
function focus() {
|
||||
if (gSocket && gSocket.readyState == gSocket.CLOSED) {
|
||||
if (g_socket && g_socket.readyState == g_socket.CLOSED) {
|
||||
connectSocket();
|
||||
} else {
|
||||
send({event: 'focus'});
|
||||
@@ -1450,12 +1463,10 @@ function focus() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Notify the app of lost focus.
|
||||
*/
|
||||
function blur() {
|
||||
if (gSocket && gSocket.readyState == gSocket.OPEN) {
|
||||
send({event: 'blur'});
|
||||
}
|
||||
send({event: 'blur'});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1463,48 +1474,7 @@ function blur() {
|
||||
* @param event The message.
|
||||
*/
|
||||
function message(event) {
|
||||
if (
|
||||
event.data &&
|
||||
event.data.event == 'resizeMe' &&
|
||||
event.data.width &&
|
||||
event.data.height
|
||||
) {
|
||||
let iframe = document.getElementById('iframe_' + event.data.name);
|
||||
iframe.setAttribute('width', event.data.width);
|
||||
iframe.setAttribute('height', event.data.height);
|
||||
} else if (event.data && event.data.action == 'setHash') {
|
||||
window.location.hash = event.data.hash;
|
||||
} else if (event.data && event.data.action == 'storeBlob') {
|
||||
fetch('/save', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/binary',
|
||||
},
|
||||
body: event.data.blob.buffer,
|
||||
})
|
||||
.then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error(response.status + ' ' + response.statusText);
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then(function (text) {
|
||||
let iframe = document.getElementById('document');
|
||||
iframe.contentWindow.postMessage(
|
||||
{
|
||||
storeBlobComplete: {
|
||||
name: event.data.blob.name,
|
||||
path: text,
|
||||
type: event.data.blob.type,
|
||||
context: event.data.context,
|
||||
},
|
||||
},
|
||||
'*'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
send({event: 'message', message: event.data});
|
||||
}
|
||||
send({event: 'message', message: event.data});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1512,8 +1482,8 @@ function message(event) {
|
||||
* @param path The path to which the WebSocket should be connected.
|
||||
*/
|
||||
function reconnect(path) {
|
||||
let oldSocket = gSocket;
|
||||
gSocket = null;
|
||||
let oldSocket = g_socket;
|
||||
g_socket = null;
|
||||
if (oldSocket) {
|
||||
oldSocket.onopen = null;
|
||||
oldSocket.onclose = null;
|
||||
@@ -1528,24 +1498,24 @@ function reconnect(path) {
|
||||
* @param path The path to which to connect.
|
||||
*/
|
||||
function connectSocket(path) {
|
||||
if (!gSocket || gSocket.readyState != gSocket.OPEN) {
|
||||
if (gSocket) {
|
||||
gSocket.onopen = null;
|
||||
gSocket.onclose = null;
|
||||
gSocket.onmessage = null;
|
||||
gSocket.close();
|
||||
if (!g_socket || g_socket.readyState != g_socket.OPEN) {
|
||||
if (g_socket) {
|
||||
g_socket.onopen = null;
|
||||
g_socket.onclose = null;
|
||||
g_socket.onmessage = null;
|
||||
g_socket.close();
|
||||
}
|
||||
setStatusMessage('⚪ Connecting...', kStatusColor);
|
||||
gSocket = new WebSocket(
|
||||
setStatusMessage('⚪ Connecting...', k_color_status);
|
||||
g_socket = new WebSocket(
|
||||
(window.location.protocol == 'https:' ? 'wss://' : 'ws://') +
|
||||
window.location.hostname +
|
||||
(window.location.port.length ? ':' + window.location.port : '') +
|
||||
'/app/socket'
|
||||
);
|
||||
gSocket.onopen = function () {
|
||||
setStatusMessage('🟡 Authenticating...', kStatusColor);
|
||||
g_socket.onopen = function () {
|
||||
setStatusMessage('🟡 Authenticating...', k_color_status);
|
||||
let connect_path = path ?? window.location.pathname;
|
||||
gSocket.send(
|
||||
g_socket.send(
|
||||
JSON.stringify({
|
||||
action: 'hello',
|
||||
path: connect_path,
|
||||
@@ -1557,12 +1527,12 @@ function connectSocket(path) {
|
||||
})
|
||||
);
|
||||
};
|
||||
gSocket.onmessage = function (event) {
|
||||
g_socket.onmessage = function (event) {
|
||||
_receive_websocket_message(JSON.parse(event.data));
|
||||
};
|
||||
gSocket.onclose = function (event) {
|
||||
if (gUnloading) {
|
||||
setStatusMessage('⚪ Closing...', kStatusColor);
|
||||
g_socket.onclose = function (event) {
|
||||
if (g_unloading) {
|
||||
setStatusMessage('⚪ Closing...', k_color_status);
|
||||
} else {
|
||||
const k_codes = {
|
||||
1000: 'Normal closure',
|
||||
@@ -1583,59 +1553,79 @@ function connectSocket(path) {
|
||||
};
|
||||
setStatusMessage(
|
||||
'🔴 Closed: ' + (k_codes[event.code] || event.code),
|
||||
kDisconnectColor
|
||||
k_color_disconnect
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine a CodeMirror language mode from filename.
|
||||
* @param name Filename.
|
||||
* @return The mode name.
|
||||
*/
|
||||
function modeFromName(name) {
|
||||
switch (name.split('.').pop()) {
|
||||
case 'md':
|
||||
return 'markdown';
|
||||
case 'css':
|
||||
return 'css';
|
||||
case 'js':
|
||||
return 'javascript';
|
||||
case 'xml':
|
||||
case 'svg':
|
||||
return 'xml';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a file by name.
|
||||
* @param name The file to open.
|
||||
*/
|
||||
function openFile(name) {
|
||||
let newDoc =
|
||||
name && gFiles[name]
|
||||
? gFiles[name].doc
|
||||
name && g_files[name]
|
||||
? g_files[name].doc
|
||||
: cm6.EditorState.create({doc: '', extensions: cm6.extensions});
|
||||
let oldDoc = gEditor.state;
|
||||
gEditor.setState(newDoc);
|
||||
let oldDoc = g_editor.state;
|
||||
g_editor.setState(newDoc);
|
||||
cm6.setEditorMode(g_editor, modeFromName(name));
|
||||
|
||||
if (gFiles[gCurrentFile]) {
|
||||
gFiles[gCurrentFile].doc = oldDoc;
|
||||
if (g_files[g_current_file]) {
|
||||
g_files[g_current_file].doc = oldDoc;
|
||||
if (
|
||||
!gFiles[gCurrentFile].isNew &&
|
||||
gFiles[gCurrentFile].doc.doc.toString() == oldDoc.doc.toString()
|
||||
!g_files[g_current_file].isNew &&
|
||||
g_files[g_current_file].doc.doc.toString() == oldDoc.doc.toString()
|
||||
) {
|
||||
delete gFiles[gCurrentFile].buffer;
|
||||
delete g_files[g_current_file].buffer;
|
||||
}
|
||||
}
|
||||
gCurrentFile = name;
|
||||
g_current_file = name;
|
||||
updateFiles();
|
||||
gEditor.focus();
|
||||
g_editor.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Refresh the files list.
|
||||
*/
|
||||
function updateFiles() {
|
||||
let files = document.getElementsByTagName('tf-files-pane')[0];
|
||||
if (files) {
|
||||
files.files = Object.fromEntries(
|
||||
Object.keys(gFiles).map((file) => [
|
||||
Object.keys(g_files).map((file) => [
|
||||
file,
|
||||
{
|
||||
clean:
|
||||
(file == gCurrentFile
|
||||
? gEditor.state.doc.toString()
|
||||
: gFiles[file].doc.doc.toString()) == gFiles[file].original,
|
||||
(file == g_current_file
|
||||
? g_editor.state.doc.toString()
|
||||
: g_files[file].doc.doc.toString()) == g_files[file].original,
|
||||
},
|
||||
])
|
||||
);
|
||||
files.current = gCurrentFile;
|
||||
files.current = g_current_file;
|
||||
}
|
||||
gEditor.focus();
|
||||
g_editor.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1643,34 +1633,34 @@ function updateFiles() {
|
||||
* @param name The file's name.
|
||||
*/
|
||||
function makeNewFile(name) {
|
||||
gFiles[name] = {
|
||||
g_files[name] = {
|
||||
doc: cm6.EditorState.create({extensions: cm6.extensions}),
|
||||
};
|
||||
openFile(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Prompt to create a new file.
|
||||
*/
|
||||
function newFile() {
|
||||
let name = prompt('Name of new file:', 'file.js');
|
||||
if (name && !gFiles[name]) {
|
||||
if (name && !g_files[name]) {
|
||||
makeNewFile(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Prompt to remove a file.
|
||||
*/
|
||||
function removeFile() {
|
||||
if (confirm('Remove ' + gCurrentFile + '?')) {
|
||||
delete gFiles[gCurrentFile];
|
||||
openFile(Object.keys(gFiles)[0]);
|
||||
if (confirm('Remove ' + g_current_file + '?')) {
|
||||
delete g_files[g_current_file];
|
||||
openFile(Object.keys(g_files)[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Export the app to a zip file, which is downloaded by the browser.
|
||||
*/
|
||||
async function appExport() {
|
||||
let JsZip = (await import('/static/jszip.min.js')).default;
|
||||
@@ -1681,13 +1671,13 @@ async function appExport() {
|
||||
`${name}.json`,
|
||||
JSON.stringify({
|
||||
type: 'tildefriends-app',
|
||||
emoji: gApp.emoji || '📦',
|
||||
emoji: g_app.emoji || '📦',
|
||||
})
|
||||
);
|
||||
for (let file of Object.keys(gFiles)) {
|
||||
for (let file of Object.keys(g_files)) {
|
||||
zip.file(
|
||||
`${name}/${file}`,
|
||||
gFiles[file].buffer ?? gFiles[file].doc.doc.toString()
|
||||
g_files[file].buffer ?? g_files[file].doc.doc.toString()
|
||||
);
|
||||
}
|
||||
let content = await zip.generateAsync({
|
||||
@@ -1728,7 +1718,7 @@ async function save_file_to_blob_id(name, file) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Prompt to import an app from a zip file.
|
||||
*/
|
||||
async function appImport() {
|
||||
let JsZip = (await import('/static/jszip.min.js')).default;
|
||||
@@ -1806,9 +1796,9 @@ async function sourcePretty() {
|
||||
let babel = (await import('/prettier/babel.mjs')).default;
|
||||
let estree = (await import('/prettier/estree.mjs')).default;
|
||||
let prettier_html = (await import('/prettier/html.mjs')).default;
|
||||
let source = gEditor.state.doc.toString();
|
||||
let source = g_editor.state.doc.toString();
|
||||
let formatted = await prettier.format(source, {
|
||||
parser: gCurrentFile?.toLowerCase()?.endsWith('.html') ? 'html' : 'babel',
|
||||
parser: g_current_file?.toLowerCase()?.endsWith('.html') ? 'html' : 'babel',
|
||||
plugins: [babel, estree, prettier_html],
|
||||
trailingComma: 'es5',
|
||||
useTabs: true,
|
||||
@@ -1817,10 +1807,10 @@ async function sourcePretty() {
|
||||
bracketSpacing: false,
|
||||
});
|
||||
if (source !== formatted) {
|
||||
gEditor.dispatch({
|
||||
g_editor.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: gEditor.state.doc.length,
|
||||
to: g_editor.state.doc.length,
|
||||
insert: formatted,
|
||||
},
|
||||
});
|
||||
@@ -1855,7 +1845,9 @@ function toggleVisibleWhitespace() {
|
||||
}
|
||||
}
|
||||
|
||||
// TODOC
|
||||
/**
|
||||
* Register event handlers and connect the WebSocket on load.
|
||||
*/
|
||||
window.addEventListener('load', function () {
|
||||
window.addEventListener('hashchange', hashChange);
|
||||
window.addEventListener('focus', focus);
|
||||
@@ -1863,7 +1855,7 @@ window.addEventListener('load', function () {
|
||||
window.addEventListener('message', message, false);
|
||||
window.addEventListener('online', connectSocket);
|
||||
window.addEventListener('beforeunload', function () {
|
||||
gUnloading = true;
|
||||
g_unloading = true;
|
||||
});
|
||||
document.getElementById('name').value = window.location.pathname;
|
||||
document
|
||||
|
||||
283
core/core.js
283
core/core.js
@@ -5,21 +5,72 @@
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** \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;
|
||||
/** Time between pings, in milliseconds. */
|
||||
const k_ping_interval = 60 * 1000;
|
||||
/** Whether updating accounts information is currently scheduled. */
|
||||
let g_update_accounts_scheduled;
|
||||
|
||||
/**
|
||||
** App constructor.
|
||||
** @return An app instance.
|
||||
*/
|
||||
function App() {
|
||||
this._send_queue = [];
|
||||
this.calls = {};
|
||||
this._next_call_id = 1;
|
||||
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 () {
|
||||
let id = self._next_call_id++;
|
||||
while (!id || self.calls[id]) {
|
||||
id = self._next_call_id++;
|
||||
}
|
||||
let promise = new Promise(function (resolve, reject) {
|
||||
self.calls[id] = {resolve: resolve, reject: reject};
|
||||
});
|
||||
let message = {
|
||||
action: 'tfrpc',
|
||||
method: api[0],
|
||||
params: [...arguments],
|
||||
id: id,
|
||||
};
|
||||
self.send(message);
|
||||
return promise;
|
||||
};
|
||||
Object.defineProperty(result, 'name', {value: api[0], writable: false});
|
||||
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) {
|
||||
this._send_queue.forEach((x) => this._on_output(x));
|
||||
this._send_queue = null;
|
||||
} else if (message) {
|
||||
this._send_queue.push(message);
|
||||
}
|
||||
}
|
||||
if (message && this._on_output) {
|
||||
this._on_output(message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Print an error.
|
||||
@@ -166,7 +217,7 @@ function postMessageInternal(from, to, message) {
|
||||
* @param options Other options.
|
||||
* @return The process.
|
||||
*/
|
||||
async function getProcessBlob(blobId, key, options) {
|
||||
exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
||||
let process = gProcesses[key];
|
||||
if (!process && !(options && 'create' in options && !options.create)) {
|
||||
let resolveReady;
|
||||
@@ -179,67 +230,24 @@ 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();
|
||||
process.app = new App();
|
||||
}
|
||||
process.lastActive = Date.now();
|
||||
process.lastPing = null;
|
||||
process.timeout = k_ping_interval;
|
||||
process.ready = new Promise(function (resolve, reject) {
|
||||
resolveReady = resolve;
|
||||
rejectReady = reject;
|
||||
});
|
||||
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();
|
||||
@@ -253,12 +261,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
return settings.userPermissions[user];
|
||||
}
|
||||
},
|
||||
permissionsForUser: async function (user) {
|
||||
let settings = await loadSettings();
|
||||
return settings?.permissions?.[user] ?? [];
|
||||
},
|
||||
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) {
|
||||
@@ -285,7 +288,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(
|
||||
@@ -318,26 +321,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 (
|
||||
@@ -374,7 +377,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
|
||||
@@ -386,44 +389,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
throw new Error('Must be signed-in to create an account.');
|
||||
}
|
||||
};
|
||||
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');
|
||||
print('Setting', key, value);
|
||||
let settings = await loadSettings();
|
||||
settings[key] = value;
|
||||
await new Database('core').set('settings', JSON.stringify(settings));
|
||||
print('Done.');
|
||||
};
|
||||
imports.core.deleteUser = async function (user) {
|
||||
await imports.core.permissionTest('delete_user');
|
||||
let db = new Database('auth');
|
||||
db.remove('user:' + user);
|
||||
let users = new Set();
|
||||
let users_original = await db.get('users');
|
||||
try {
|
||||
users = new Set(JSON.parse(users_original));
|
||||
} catch {}
|
||||
users.delete(user);
|
||||
users = JSON.stringify([...users].sort());
|
||||
if (users !== users_original) {
|
||||
await db.set('users', users);
|
||||
}
|
||||
};
|
||||
}
|
||||
if (options.api) {
|
||||
imports.app = {};
|
||||
for (let i in options.api) {
|
||||
@@ -481,26 +446,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 &&
|
||||
@@ -520,8 +465,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,
|
||||
@@ -558,26 +513,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
);
|
||||
}
|
||||
};
|
||||
imports.ssb.swapWithServerIdentity = function (id) {
|
||||
if (
|
||||
process.credentials &&
|
||||
process.credentials.session &&
|
||||
process.credentials.session.name
|
||||
) {
|
||||
return ssb.swapWithServerIdentity(
|
||||
process.credentials.session.name,
|
||||
id
|
||||
);
|
||||
}
|
||||
};
|
||||
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 &&
|
||||
process.credentials.session &&
|
||||
@@ -652,6 +587,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
},
|
||||
};
|
||||
ssb.registerImports(imports, process);
|
||||
process.imports = imports;
|
||||
process.task.setImports(imports);
|
||||
process.task.activate();
|
||||
let source = await ssb.blobGet(blobId);
|
||||
@@ -680,7 +616,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();
|
||||
@@ -701,17 +636,41 @@ async function getProcessBlob(blobId, key, options) {
|
||||
}
|
||||
}
|
||||
return process;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.addEventListener('message', function () {
|
||||
/**
|
||||
* 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', []);
|
||||
});
|
||||
|
||||
|
||||
67
core/eula.html
Normal file
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. You
|
||||
moderate for your friends and vice versa.
|
||||
</p>
|
||||
<p>
|
||||
You can also flag a message to report to the operators of services with
|
||||
which you interact that the it should be considered for removal.
|
||||
</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. This is also where a
|
||||
local blocklist can be managed.
|
||||
</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>
|
||||
<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
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%;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ let g_next_id = 1;
|
||||
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 {
|
||||
|
||||
@@ -25,14 +25,14 @@
|
||||
}:
|
||||
pkgs.stdenv.mkDerivation rec {
|
||||
pname = "tildefriends";
|
||||
version = "0.0.32";
|
||||
version = "0.2025.11";
|
||||
|
||||
src = pkgs.fetchFromGitea {
|
||||
domain = "dev.tildefriends.net";
|
||||
owner = "cory";
|
||||
repo = "tildefriends";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-Dk0NOEQIg2LeENySK0+MgpZEtfsClGq6dZL+eOOpE0U=";
|
||||
hash = "sha256-z4v4ghKOBTMv+agTUKg+HU8zfE4imluXFsozQCT4qX8=";
|
||||
fetchSubmodules = true;
|
||||
};
|
||||
|
||||
|
||||
2
deps/c-ares
vendored
2
deps/c-ares
vendored
Submodule deps/c-ares updated: d3a507e920...3ac47ee46e
2
deps/codemirror/cm6.js
vendored
2
deps/codemirror/cm6.js
vendored
File diff suppressed because one or more lines are too long
33
deps/codemirror_src/editor.mjs
vendored
33
deps/codemirror_src/editor.mjs
vendored
@@ -1,12 +1,14 @@
|
||||
import {EditorState} from "@codemirror/state"
|
||||
import {EditorState, Compartment} from "@codemirror/state"
|
||||
import {EditorView} from '@codemirror/view';
|
||||
import {javascript} from "@codemirror/lang-javascript"
|
||||
import {html} from "@codemirror/lang-html"
|
||||
import {javascriptLanguage} from "@codemirror/lang-javascript"
|
||||
import {htmlLanguage, html} from "@codemirror/lang-html"
|
||||
import {css} from "@codemirror/lang-css"
|
||||
import {markdown} from "@codemirror/lang-markdown"
|
||||
import {xml} from "@codemirror/lang-xml"
|
||||
import {search} from "@codemirror/search"
|
||||
import {oneDark} from "./theme-tf-dark.js"
|
||||
import {lineNumbers, highlightActiveLineGutter, highlightSpecialChars, highlightTrailingWhitespace, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightActiveLine, keymap, highlightWhitespace} from '@codemirror/view';
|
||||
import {foldGutter, indentUnit, indentOnInput, syntaxHighlighting, defaultHighlightStyle, bracketMatching, foldKeymap} from '@codemirror/language';
|
||||
import {language, foldGutter, indentUnit, indentOnInput, syntaxHighlighting, defaultHighlightStyle, bracketMatching, foldKeymap} from '@codemirror/language';
|
||||
import {history, defaultKeymap, historyKeymap, indentWithTab} from '@codemirror/commands';
|
||||
import {highlightSelectionMatches, searchKeymap} from '@codemirror/search';
|
||||
import {autocompletion, closeBracketsKeymap, completionKeymap} from '@codemirror/autocomplete';
|
||||
@@ -18,6 +20,9 @@ let updateListenerExtension = EditorView.updateListener.of((update) => {
|
||||
}
|
||||
});
|
||||
|
||||
/* https://codemirror.net/examples/config/ */
|
||||
const languageConfig = new Compartment();
|
||||
|
||||
const extensions = [
|
||||
lineNumbers(),
|
||||
highlightActiveLineGutter(),
|
||||
@@ -47,9 +52,7 @@ const extensions = [
|
||||
...lintKeymap,
|
||||
indentWithTab,
|
||||
]),
|
||||
javascript(),
|
||||
html(),
|
||||
css(),
|
||||
languageConfig.of(javascriptLanguage),
|
||||
search(),
|
||||
oneDark,
|
||||
updateListenerExtension,
|
||||
@@ -60,11 +63,25 @@ function TildeFriendsEditorView(parent) {
|
||||
extensions: extensions,
|
||||
parent: parent,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function setEditorMode(view, mode) {
|
||||
const k_modes = {
|
||||
'css': css(),
|
||||
'html': html(),
|
||||
'javascript': javascriptLanguage,
|
||||
'markdown': markdown(),
|
||||
'xml': xml(),
|
||||
};
|
||||
view.dispatch({
|
||||
effects: languageConfig.reconfigure(k_modes[mode])
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
TildeFriendsEditorView,
|
||||
EditorState,
|
||||
EditorView,
|
||||
extensions,
|
||||
setEditorMode,
|
||||
};
|
||||
|
||||
370
deps/codemirror_src/package-lock.json
generated
vendored
370
deps/codemirror_src/package-lock.json
generated
vendored
@@ -9,6 +9,8 @@
|
||||
"@codemirror/lang-html": "^6.4.8",
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-markdown": "^6.0.1",
|
||||
"@codemirror/lang-xml": "^6.0.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"codemirror": "^6.0.1",
|
||||
@@ -19,9 +21,9 @@
|
||||
}
|
||||
},
|
||||
"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.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz",
|
||||
"integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
@@ -31,9 +33,9 @@
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -56,9 +58,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-html": {
|
||||
"version": "6.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
|
||||
"integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
|
||||
"version": "6.4.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",
|
||||
@@ -69,7 +71,7 @@
|
||||
"@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": {
|
||||
@@ -97,10 +99,39 @@
|
||||
"@lezer/json": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-markdown": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz",
|
||||
"integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.7.1",
|
||||
"@codemirror/lang-html": "^6.0.0",
|
||||
"@codemirror/language": "^6.3.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.2.1",
|
||||
"@lezer/markdown": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-xml": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz",
|
||||
"integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.4.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/xml": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"version": "6.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz",
|
||||
"integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==",
|
||||
"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",
|
||||
@@ -112,9 +143,9 @@
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -155,9 +186,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.38.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz",
|
||||
"integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==",
|
||||
"version": "6.39.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.3.tgz",
|
||||
"integrity": "sha512-ZR32LYnPMpf7XZcrYJpSrHJUHNZPTj73/amTtZLhAwzYhSKiDI2OZmCiXbTRvxL1T8X7QTHnCG+KfnRJvH/QsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.5.0",
|
||||
@@ -167,9 +198,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
|
||||
"integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
|
||||
"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": {
|
||||
@@ -188,9 +219,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz",
|
||||
"integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==",
|
||||
"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": {
|
||||
@@ -199,16 +230,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
|
||||
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
|
||||
"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.29",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
|
||||
"integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
|
||||
"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": {
|
||||
@@ -217,9 +248,9 @@
|
||||
}
|
||||
},
|
||||
"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.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.4.0.tgz",
|
||||
"integrity": "sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@lezer/css": {
|
||||
@@ -234,18 +265,18 @@
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -254,9 +285,9 @@
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -276,14 +307,35 @@
|
||||
}
|
||||
},
|
||||
"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.5",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.5.tgz",
|
||||
"integrity": "sha512-/YTRKP5yPPSo1xImYQk7AZZMAgap0kegzqCSYHjAL9x1AZ0ZQW+IpcEzMKagCsbTsLnVeWkxYrCNeXG8xEPrjg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/markdown": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.1.tgz",
|
||||
"integrity": "sha512-72ah+Sml7lD8Wn7lnz9vwYmZBo9aQT+I2gjK/0epI+gjdwUbWw3MJ/ZBGEqG1UfrIauRqH37/c5mVHXeCTGXtA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/highlight": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/xml": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz",
|
||||
"integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -338,9 +390,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz",
|
||||
"integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==",
|
||||
"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",
|
||||
@@ -360,9 +412,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.0.tgz",
|
||||
"integrity": "sha512-9f3nSTFI2ivfxc7/tHBHcJ8pRnp8ROrELvsVprlQPVvcZ+j5zztYd+PTJGpyIOAdTvNwNrpCXswKSeoQcyGjMQ==",
|
||||
"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"
|
||||
],
|
||||
@@ -373,9 +425,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.0.tgz",
|
||||
"integrity": "sha512-tFZSEhqJ8Yrpe50TzOdeoYi72gi/jsnT7y8Qrozf3cNu28WX+s6I3XzEPUAqoaT9SAS8Xz9AzGTFlxxCH/w20w==",
|
||||
"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"
|
||||
],
|
||||
@@ -386,9 +438,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.0.tgz",
|
||||
"integrity": "sha512-+DikIIs+p6yU2hF51UaWG8BnHbq90X0QIOt5zqSKSZxY+G3qqdLih214e9InJal21af2PuuxkDectetGfbVPJw==",
|
||||
"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"
|
||||
],
|
||||
@@ -399,9 +451,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.0.tgz",
|
||||
"integrity": "sha512-5a+NofhdEB/WimSlFMskbFQn1vqz1FWryYpA99trmZGO6qEmiS0IsX6w4B3d91U878Q2ZQdiaFF1gxX4P147og==",
|
||||
"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"
|
||||
],
|
||||
@@ -412,9 +464,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.0.tgz",
|
||||
"integrity": "sha512-igr/RlKPS3OCy4jD3XBmAmo3UAcNZkJSubRsw1JeM8bAbwf15k/3eMZXD91bnjheijJiOJcga3kfCLKjV8IXNg==",
|
||||
"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"
|
||||
],
|
||||
@@ -425,9 +477,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.0.tgz",
|
||||
"integrity": "sha512-MdigWzPSHlQzB1xZ+MdFDWTAH+kcn7UxjEBoOKuaso7z1DRlnAnrknB1mTtNOQ+GdPI8xgExAGwHeqQjntR0Cg==",
|
||||
"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"
|
||||
],
|
||||
@@ -438,9 +490,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.0.tgz",
|
||||
"integrity": "sha512-dmZseE0ZwA/4yy1+BwFrDqFTjjNg24GO9xSrb1weVbt6AFkhp5pz1gVS7IMtfIvoWy8yp6q/zN0bKnefRUImvQ==",
|
||||
"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"
|
||||
],
|
||||
@@ -451,9 +503,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.0.tgz",
|
||||
"integrity": "sha512-fzhfn6p9Cfm3W8UrWKIa4l7Wfjs/KGdgaswMBBE3KY3Ta43jg2XsPrAtfezHpsRk0Nx+TFuS3hZk/To2N5kFPQ==",
|
||||
"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"
|
||||
],
|
||||
@@ -464,9 +516,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-vVDD+iPDPmJQ5nAQ5Tifq3ywdv60FartglFI8VOCK+hcU9aoG0qlQTsDJP97O5yiTaTqlneZWoARMcVC5nyUoQ==",
|
||||
"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"
|
||||
],
|
||||
@@ -477,9 +529,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.0.tgz",
|
||||
"integrity": "sha512-0d0jx08fzDHCzXqrtCMEEyxKU0SvJrWmUjUDE2/KDQ2UDJql0tfiwYvEx1oHELClKO8CNdE+AGJj+RqXscZpdQ==",
|
||||
"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"
|
||||
],
|
||||
@@ -489,10 +541,10 @@
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-XBYu9oW9eKJadWn8M7hkTZsD4yG+RrsTrVEgyKwb4L72cpJjRbRboTG9Lg9fec8MxJp/cfTHAocg4mnismQR8A==",
|
||||
"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"
|
||||
],
|
||||
@@ -503,9 +555,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-wJaRvcT17PoOK6Ggcfo3nouFlybHvARBS4jzT0PC/lg17fIJHcDS2fZz3sD+iA4nRlho2zE6OGbU0HvwATdokQ==",
|
||||
"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"
|
||||
],
|
||||
@@ -516,9 +568,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-GZ5bkMFteAGkcmh8x0Ok4LSa+L62Ez0tMsHPX6JtR0wl4Xc3bQcrFHDiR5DGLEDFtGrXih4Nd/UDaFqs968/wA==",
|
||||
"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"
|
||||
],
|
||||
@@ -529,9 +581,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.0.tgz",
|
||||
"integrity": "sha512-7CjPw6FflFsVOUfWOrVrREiV3IYXG4RzZ1ZQUaT3BtSK8YXN6x286o+sruPZJESIaPebYuFowmg54ZdrkVBYog==",
|
||||
"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"
|
||||
],
|
||||
@@ -542,9 +594,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-nmvnl0ZiuysltcB/cKjUh40Rx4FbSyueERDsl2FLvLYr6pCgSsvGr3SocUT84svSpmloS7f1DRWqtRha74Gi1w==",
|
||||
"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"
|
||||
],
|
||||
@@ -555,9 +607,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-Cv+moII5C8RM6gZbR3cb21o6rquVDZrN2o81maROg1LFzBz2dZUwIQSxFA8GtGZ/F2KtsqQ2z3eFPBb6akvQNg==",
|
||||
"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"
|
||||
],
|
||||
@@ -568,9 +620,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.0.tgz",
|
||||
"integrity": "sha512-PHcMG8DZTM9RCIjp8QIfN0VYtX0TtBPnWOTRurFhoCDoi9zptUZL2k7pCs+5rgut7JAiUsYy+huyhVKPcmxoog==",
|
||||
"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"
|
||||
],
|
||||
@@ -580,10 +632,23 @@
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.0.tgz",
|
||||
"integrity": "sha512-1SI/Rd47e8aQJeFWMDg16ET+fjvCcD/CzeaRmIEPmb05hx+3cCcwIF4ebUag4yTt/D1peE+Mgp0+Po3M358cAA==",
|
||||
"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"
|
||||
],
|
||||
@@ -594,9 +659,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.0.tgz",
|
||||
"integrity": "sha512-JwOCYxmumFDfDhx4kNyz6kTVK3gWzBIvVdMNzQMRDubcoGRDniOOmo6DDNP42qwZx3Bp9/6vWJ+kNzNqXoHmeA==",
|
||||
"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"
|
||||
],
|
||||
@@ -606,10 +671,23 @@
|
||||
"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.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.0.tgz",
|
||||
"integrity": "sha512-IPMIfrfkG1GaEXi+JSsQEx8x9b4b+hRZXO7KYc2pKio3zO2/VDXDs6B9Ts/nnO+25Fk1tdAVtUn60HKKPPzDig==",
|
||||
"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"
|
||||
],
|
||||
@@ -779,12 +857,12 @@
|
||||
}
|
||||
},
|
||||
"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"
|
||||
},
|
||||
@@ -799,9 +877,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.0.tgz",
|
||||
"integrity": "sha512-ONmkT3Ud3IfW15nl7l4qAZko5/2iZ5ALVBDh02ZSZ5IGVLJSYkRcRa3iB58VyEIyoofs9m2xdVrm+lTi97+3pw==",
|
||||
"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.8"
|
||||
@@ -814,26 +892,28 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.46.0",
|
||||
"@rollup/rollup-android-arm64": "4.46.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.46.0",
|
||||
"@rollup/rollup-darwin-x64": "4.46.0",
|
||||
"@rollup/rollup-freebsd-arm64": "4.46.0",
|
||||
"@rollup/rollup-freebsd-x64": "4.46.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.46.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.46.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.46.0",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.46.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.46.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.46.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.46.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.46.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"
|
||||
}
|
||||
},
|
||||
@@ -897,9 +977,9 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@@ -915,14 +995,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.43.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz",
|
||||
"integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==",
|
||||
"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.14.0",
|
||||
"acorn": "^8.15.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
|
||||
2
deps/codemirror_src/package.json
vendored
2
deps/codemirror_src/package.json
vendored
@@ -7,6 +7,8 @@
|
||||
"@codemirror/lang-html": "^6.4.8",
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-markdown": "^6.0.1",
|
||||
"@codemirror/lang-xml": "^6.0.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"codemirror": "^6.0.1",
|
||||
|
||||
2
deps/libbacktrace
vendored
2
deps/libbacktrace
vendored
Submodule deps/libbacktrace updated: f1104f3270...b9e40069c0
1
deps/openssl_src
vendored
1
deps/openssl_src
vendored
Submodule deps/openssl_src deleted from aea7aaf2ab
2
deps/quickjs
vendored
2
deps/quickjs
vendored
Submodule deps/quickjs updated: 19abf1888d...fa628f8c52
21
deps/speedscope/LICENSE
vendored
21
deps/speedscope/LICENSE
vendored
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Jamie Wong
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
2
deps/speedscope/README
vendored
2
deps/speedscope/README
vendored
@@ -1,2 +0,0 @@
|
||||
This is a self-contained release of https://github.com/jlfwong/speedscope.
|
||||
To use it, open index.html in Chrome or Firefox.
|
||||
3
deps/speedscope/release.txt
vendored
3
deps/speedscope/release.txt
vendored
@@ -1,3 +0,0 @@
|
||||
speedscope@1.23.0
|
||||
Sun Jul 6 20:04:28 PDT 2025
|
||||
aa9bef50789a2989746b576fff182b6f01dfce6a
|
||||
189
deps/speedscope/speedscope-7YPLLUY2.js
vendored
189
deps/speedscope/speedscope-7YPLLUY2.js
vendored
File diff suppressed because one or more lines are too long
2531
deps/sqlite/shell.c
vendored
2531
deps/sqlite/shell.c
vendored
File diff suppressed because it is too large
Load Diff
5713
deps/sqlite/sqlite3.c
vendored
5713
deps/sqlite/sqlite3.c
vendored
File diff suppressed because it is too large
Load Diff
415
deps/sqlite/sqlite3.h
vendored
415
deps/sqlite/sqlite3.h
vendored
@@ -146,9 +146,12 @@ extern "C" {
|
||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.50.4"
|
||||
#define SQLITE_VERSION_NUMBER 3050004
|
||||
#define SQLITE_SOURCE_ID "2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3"
|
||||
#define SQLITE_VERSION "3.51.1"
|
||||
#define SQLITE_VERSION_NUMBER 3051001
|
||||
#define SQLITE_SOURCE_ID "2025-11-28 17:28:25 281fc0e9afc38674b9b0991943b9e9d1e64c6cbdb133d35f6f5c87ff6af38a88"
|
||||
#define SQLITE_SCM_BRANCH "branch-3.51"
|
||||
#define SQLITE_SCM_TAGS "release version-3.51.1"
|
||||
#define SQLITE_SCM_DATETIME "2025-11-28T17:28:25.933Z"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
@@ -168,9 +171,9 @@ extern "C" {
|
||||
** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 );
|
||||
** </pre></blockquote>)^
|
||||
**
|
||||
** ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION]
|
||||
** macro. ^The sqlite3_libversion() function returns a pointer to the
|
||||
** to the sqlite3_version[] string constant. The sqlite3_libversion()
|
||||
** ^The sqlite3_version[] string constant contains the text of the
|
||||
** [SQLITE_VERSION] macro. ^The sqlite3_libversion() function returns a
|
||||
** pointer to the sqlite3_version[] string constant. The sqlite3_libversion()
|
||||
** function is provided for use in DLLs since DLL users usually do not have
|
||||
** direct access to string constants within the DLL. ^The
|
||||
** sqlite3_libversion_number() function returns an integer equal to
|
||||
@@ -370,7 +373,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
|
||||
** without having to use a lot of C code.
|
||||
**
|
||||
** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded,
|
||||
** semicolon-separate SQL statements passed into its 2nd argument,
|
||||
** semicolon-separated SQL statements passed into its 2nd argument,
|
||||
** in the context of the [database connection] passed in as its 1st
|
||||
** argument. ^If the callback function of the 3rd argument to
|
||||
** sqlite3_exec() is not NULL, then it is invoked for each result row
|
||||
@@ -403,7 +406,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
|
||||
** result row is NULL then the corresponding string pointer for the
|
||||
** sqlite3_exec() callback is a NULL pointer. ^The 4th argument to the
|
||||
** sqlite3_exec() callback is an array of pointers to strings where each
|
||||
** entry represents the name of corresponding result column as obtained
|
||||
** entry represents the name of a corresponding result column as obtained
|
||||
** from [sqlite3_column_name()].
|
||||
**
|
||||
** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer
|
||||
@@ -497,6 +500,9 @@ SQLITE_API int sqlite3_exec(
|
||||
#define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8))
|
||||
#define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8))
|
||||
#define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8))
|
||||
#define SQLITE_ERROR_RESERVESIZE (SQLITE_ERROR | (4<<8))
|
||||
#define SQLITE_ERROR_KEY (SQLITE_ERROR | (5<<8))
|
||||
#define SQLITE_ERROR_UNABLE (SQLITE_ERROR | (6<<8))
|
||||
#define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8))
|
||||
#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8))
|
||||
#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8))
|
||||
@@ -531,6 +537,8 @@ SQLITE_API int sqlite3_exec(
|
||||
#define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8))
|
||||
#define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8))
|
||||
#define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8))
|
||||
#define SQLITE_IOERR_BADKEY (SQLITE_IOERR | (35<<8))
|
||||
#define SQLITE_IOERR_CODEC (SQLITE_IOERR | (36<<8))
|
||||
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
|
||||
#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8))
|
||||
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
|
||||
@@ -589,7 +597,7 @@ SQLITE_API int sqlite3_exec(
|
||||
** Note in particular that passing the SQLITE_OPEN_EXCLUSIVE flag into
|
||||
** [sqlite3_open_v2()] does *not* cause the underlying database file
|
||||
** to be opened using O_EXCL. Passing SQLITE_OPEN_EXCLUSIVE into
|
||||
** [sqlite3_open_v2()] has historically be a no-op and might become an
|
||||
** [sqlite3_open_v2()] has historically been a no-op and might become an
|
||||
** error in future versions of SQLite.
|
||||
*/
|
||||
#define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */
|
||||
@@ -683,7 +691,7 @@ SQLITE_API int sqlite3_exec(
|
||||
** SQLite uses one of these integer values as the second
|
||||
** argument to calls it makes to the xLock() and xUnlock() methods
|
||||
** of an [sqlite3_io_methods] object. These values are ordered from
|
||||
** lest restrictive to most restrictive.
|
||||
** least restrictive to most restrictive.
|
||||
**
|
||||
** The argument to xLock() is always SHARED or higher. The argument to
|
||||
** xUnlock is either SHARED or NONE.
|
||||
@@ -924,7 +932,7 @@ struct sqlite3_io_methods {
|
||||
** connection. See also [SQLITE_FCNTL_FILE_POINTER].
|
||||
**
|
||||
** <li>[[SQLITE_FCNTL_SYNC_OMITTED]]
|
||||
** No longer in use.
|
||||
** The SQLITE_FCNTL_SYNC_OMITTED file-control is no longer used.
|
||||
**
|
||||
** <li>[[SQLITE_FCNTL_SYNC]]
|
||||
** The [SQLITE_FCNTL_SYNC] opcode is generated internally by SQLite and
|
||||
@@ -999,7 +1007,7 @@ struct sqlite3_io_methods {
|
||||
**
|
||||
** <li>[[SQLITE_FCNTL_VFSNAME]]
|
||||
** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of
|
||||
** all [VFSes] in the VFS stack. The names are of all VFS shims and the
|
||||
** all [VFSes] in the VFS stack. The names of all VFS shims and the
|
||||
** final bottom-level VFS are written into memory obtained from
|
||||
** [sqlite3_malloc()] and the result is stored in the char* variable
|
||||
** that the fourth parameter of [sqlite3_file_control()] points to.
|
||||
@@ -1013,7 +1021,7 @@ struct sqlite3_io_methods {
|
||||
** ^The [SQLITE_FCNTL_VFS_POINTER] opcode finds a pointer to the top-level
|
||||
** [VFSes] currently in use. ^(The argument X in
|
||||
** sqlite3_file_control(db,SQLITE_FCNTL_VFS_POINTER,X) must be
|
||||
** of type "[sqlite3_vfs] **". This opcodes will set *X
|
||||
** of type "[sqlite3_vfs] **". This opcode will set *X
|
||||
** to a pointer to the top-level VFS.)^
|
||||
** ^When there are multiple VFS shims in the stack, this opcode finds the
|
||||
** upper-most shim only.
|
||||
@@ -1203,7 +1211,7 @@ struct sqlite3_io_methods {
|
||||
** <li>[[SQLITE_FCNTL_EXTERNAL_READER]]
|
||||
** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect
|
||||
** whether or not there is a database client in another process with a wal-mode
|
||||
** transaction open on the database or not. It is only available on unix.The
|
||||
** transaction open on the database or not. It is only available on unix. The
|
||||
** (void*) argument passed with this file-control should be a pointer to a
|
||||
** value of type (int). The integer value is set to 1 if the database is a wal
|
||||
** mode database and there exists at least one client in another process that
|
||||
@@ -1221,6 +1229,15 @@ struct sqlite3_io_methods {
|
||||
** database is not a temp db, then the [SQLITE_FCNTL_RESET_CACHE] file-control
|
||||
** purges the contents of the in-memory page cache. If there is an open
|
||||
** transaction, or if the db is a temp-db, this opcode is a no-op, not an error.
|
||||
**
|
||||
** <li>[[SQLITE_FCNTL_FILESTAT]]
|
||||
** The [SQLITE_FCNTL_FILESTAT] opcode returns low-level diagnostic information
|
||||
** about the [sqlite3_file] objects used access the database and journal files
|
||||
** for the given schema. The fourth parameter to [sqlite3_file_control()]
|
||||
** should be an initialized [sqlite3_str] pointer. JSON text describing
|
||||
** various aspects of the sqlite3_file object is appended to the sqlite3_str.
|
||||
** The SQLITE_FCNTL_FILESTAT opcode is usually a no-op, unless compile-time
|
||||
** options are used to enable it.
|
||||
** </ul>
|
||||
*/
|
||||
#define SQLITE_FCNTL_LOCKSTATE 1
|
||||
@@ -1266,6 +1283,7 @@ struct sqlite3_io_methods {
|
||||
#define SQLITE_FCNTL_RESET_CACHE 42
|
||||
#define SQLITE_FCNTL_NULL_IO 43
|
||||
#define SQLITE_FCNTL_BLOCK_ON_CONNECT 44
|
||||
#define SQLITE_FCNTL_FILESTAT 45
|
||||
|
||||
/* deprecated names */
|
||||
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
|
||||
@@ -1628,7 +1646,7 @@ struct sqlite3_vfs {
|
||||
** SQLite interfaces so that an application usually does not need to
|
||||
** invoke sqlite3_initialize() directly. For example, [sqlite3_open()]
|
||||
** calls sqlite3_initialize() so the SQLite library will be automatically
|
||||
** initialized when [sqlite3_open()] is called if it has not be initialized
|
||||
** initialized when [sqlite3_open()] is called if it has not been initialized
|
||||
** already. ^However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT]
|
||||
** compile-time option, then the automatic calls to sqlite3_initialize()
|
||||
** are omitted and the application must call sqlite3_initialize() directly
|
||||
@@ -1885,21 +1903,21 @@ struct sqlite3_mem_methods {
|
||||
** The [sqlite3_mem_methods]
|
||||
** structure is filled with the currently defined memory allocation routines.)^
|
||||
** This option can be used to overload the default memory allocation
|
||||
** routines with a wrapper that simulations memory allocation failure or
|
||||
** routines with a wrapper that simulates memory allocation failure or
|
||||
** tracks memory usage, for example. </dd>
|
||||
**
|
||||
** [[SQLITE_CONFIG_SMALL_MALLOC]] <dt>SQLITE_CONFIG_SMALL_MALLOC</dt>
|
||||
** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes single argument of
|
||||
** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes a single argument of
|
||||
** type int, interpreted as a boolean, which if true provides a hint to
|
||||
** SQLite that it should avoid large memory allocations if possible.
|
||||
** SQLite will run faster if it is free to make large memory allocations,
|
||||
** but some application might prefer to run slower in exchange for
|
||||
** but some applications might prefer to run slower in exchange for
|
||||
** guarantees about memory fragmentation that are possible if large
|
||||
** allocations are avoided. This hint is normally off.
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt>
|
||||
** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int,
|
||||
** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes a single argument of type int,
|
||||
** interpreted as a boolean, which enables or disables the collection of
|
||||
** memory allocation statistics. ^(When memory allocation statistics are
|
||||
** disabled, the following SQLite interfaces become non-operational:
|
||||
@@ -1944,7 +1962,7 @@ struct sqlite3_mem_methods {
|
||||
** ^If pMem is NULL and N is non-zero, then each database connection
|
||||
** does an initial bulk allocation for page cache memory
|
||||
** from [sqlite3_malloc()] sufficient for N cache lines if N is positive or
|
||||
** of -1024*N bytes if N is negative, . ^If additional
|
||||
** of -1024*N bytes if N is negative. ^If additional
|
||||
** page cache memory is needed beyond what is provided by the initial
|
||||
** allocation, then SQLite goes to [sqlite3_malloc()] separately for each
|
||||
** additional cache line. </dd>
|
||||
@@ -1973,7 +1991,7 @@ struct sqlite3_mem_methods {
|
||||
** <dd> ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a
|
||||
** pointer to an instance of the [sqlite3_mutex_methods] structure.
|
||||
** The argument specifies alternative low-level mutex routines to be used
|
||||
** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of
|
||||
** in place of the mutex routines built into SQLite.)^ ^SQLite makes a copy of
|
||||
** the content of the [sqlite3_mutex_methods] structure before the call to
|
||||
** [sqlite3_config()] returns. ^If SQLite is compiled with
|
||||
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
|
||||
@@ -2015,7 +2033,7 @@ struct sqlite3_mem_methods {
|
||||
**
|
||||
** [[SQLITE_CONFIG_GETPCACHE2]] <dt>SQLITE_CONFIG_GETPCACHE2</dt>
|
||||
** <dd> ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which
|
||||
** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of
|
||||
** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies off
|
||||
** the current page cache implementation into that object.)^ </dd>
|
||||
**
|
||||
** [[SQLITE_CONFIG_LOG]] <dt>SQLITE_CONFIG_LOG</dt>
|
||||
@@ -2032,7 +2050,7 @@ struct sqlite3_mem_methods {
|
||||
** the logger function is a copy of the first parameter to the corresponding
|
||||
** [sqlite3_log()] call and is intended to be a [result code] or an
|
||||
** [extended result code]. ^The third parameter passed to the logger is
|
||||
** log message after formatting via [sqlite3_snprintf()].
|
||||
** a log message after formatting via [sqlite3_snprintf()].
|
||||
** The SQLite logging interface is not reentrant; the logger function
|
||||
** supplied by the application must not invoke any SQLite interface.
|
||||
** In a multi-threaded application, the application-defined logger
|
||||
@@ -2223,7 +2241,7 @@ struct sqlite3_mem_methods {
|
||||
** These constants are the available integer configuration options that
|
||||
** can be passed as the second parameter to the [sqlite3_db_config()] interface.
|
||||
**
|
||||
** The [sqlite3_db_config()] interface is a var-args functions. It takes a
|
||||
** The [sqlite3_db_config()] interface is a var-args function. It takes a
|
||||
** variable number of parameters, though always at least two. The number of
|
||||
** parameters passed into sqlite3_db_config() depends on which of these
|
||||
** constants is given as the second parameter. This documentation page
|
||||
@@ -2335,17 +2353,20 @@ struct sqlite3_mem_methods {
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]]
|
||||
** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt>
|
||||
** <dd> ^This option is used to enable or disable the
|
||||
** [fts3_tokenizer()] function which is part of the
|
||||
** [FTS3] full-text search engine extension.
|
||||
** There must be two additional arguments.
|
||||
** The first argument is an integer which is 0 to disable fts3_tokenizer() or
|
||||
** positive to enable fts3_tokenizer() or negative to leave the setting
|
||||
** unchanged.
|
||||
** The second parameter is a pointer to an integer into which
|
||||
** is written 0 or 1 to indicate whether fts3_tokenizer is disabled or enabled
|
||||
** following this call. The second parameter may be a NULL pointer, in
|
||||
** which case the new setting is not reported back. </dd>
|
||||
** <dd> ^This option is used to enable or disable using the
|
||||
** [fts3_tokenizer()] function - part of the [FTS3] full-text search engine
|
||||
** extension - without using bound parameters as the parameters. Doing so
|
||||
** is disabled by default. There must be two additional arguments. The first
|
||||
** argument is an integer. If it is passed 0, then using fts3_tokenizer()
|
||||
** without bound parameters is disabled. If it is passed a positive value,
|
||||
** then calling fts3_tokenizer without bound parameters is enabled. If it
|
||||
** is passed a negative value, this setting is not modified - this can be
|
||||
** used to query for the current setting. The second parameter is a pointer
|
||||
** to an integer into which is written 0 or 1 to indicate the current value
|
||||
** of this setting (after it is modified, if applicable). The second
|
||||
** parameter may be a NULL pointer, in which case the value of the setting
|
||||
** is not reported back. Refer to [FTS3] documentation for further details.
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]]
|
||||
** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt>
|
||||
@@ -2357,8 +2378,8 @@ struct sqlite3_mem_methods {
|
||||
** When the first argument to this interface is 1, then only the C-API is
|
||||
** enabled and the SQL function remains disabled. If the first argument to
|
||||
** this interface is 0, then both the C-API and the SQL function are disabled.
|
||||
** If the first argument is -1, then no changes are made to state of either the
|
||||
** C-API or the SQL function.
|
||||
** If the first argument is -1, then no changes are made to the state of either
|
||||
** the C-API or the SQL function.
|
||||
** The second parameter is a pointer to an integer into which
|
||||
** is written 0 or 1 to indicate whether [sqlite3_load_extension()] interface
|
||||
** is disabled or enabled following this call. The second parameter may
|
||||
@@ -2476,7 +2497,7 @@ struct sqlite3_mem_methods {
|
||||
** [[SQLITE_DBCONFIG_LEGACY_ALTER_TABLE]]
|
||||
** <dt>SQLITE_DBCONFIG_LEGACY_ALTER_TABLE</dt>
|
||||
** <dd>The SQLITE_DBCONFIG_LEGACY_ALTER_TABLE option activates or deactivates
|
||||
** the legacy behavior of the [ALTER TABLE RENAME] command such it
|
||||
** the legacy behavior of the [ALTER TABLE RENAME] command such that it
|
||||
** behaves as it did prior to [version 3.24.0] (2018-06-04). See the
|
||||
** "Compatibility Notice" on the [ALTER TABLE RENAME documentation] for
|
||||
** additional information. This feature can also be turned on and off
|
||||
@@ -2525,7 +2546,7 @@ struct sqlite3_mem_methods {
|
||||
** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</dt>
|
||||
** <dd>The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates
|
||||
** the legacy file format flag. When activated, this flag causes all newly
|
||||
** created database file to have a schema format version number (the 4-byte
|
||||
** created database files to have a schema format version number (the 4-byte
|
||||
** integer found at offset 44 into the database header) of 1. This in turn
|
||||
** means that the resulting database file will be readable and writable by
|
||||
** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting,
|
||||
@@ -2552,7 +2573,7 @@ struct sqlite3_mem_methods {
|
||||
** the database handle both when the SQL statement is prepared and when it
|
||||
** is stepped. The flag is set (collection of statistics is enabled)
|
||||
** by default. <p>This option takes two arguments: an integer and a pointer to
|
||||
** an integer.. The first argument is 1, 0, or -1 to enable, disable, or
|
||||
** an integer. The first argument is 1, 0, or -1 to enable, disable, or
|
||||
** leave unchanged the statement scanstatus option. If the second argument
|
||||
** is not NULL, then the value of the statement scanstatus setting after
|
||||
** processing the first argument is written into the integer that the second
|
||||
@@ -2595,8 +2616,8 @@ struct sqlite3_mem_methods {
|
||||
** <dd>The SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE option enables or disables the
|
||||
** ability of the [ATTACH DATABASE] SQL command to open a database for writing.
|
||||
** This capability is enabled by default. Applications can disable or
|
||||
** reenable this capability using the current DBCONFIG option. If the
|
||||
** the this capability is disabled, the [ATTACH] command will still work,
|
||||
** reenable this capability using the current DBCONFIG option. If
|
||||
** this capability is disabled, the [ATTACH] command will still work,
|
||||
** but the database will be opened read-only. If this option is disabled,
|
||||
** then the ability to create a new database using [ATTACH] is also disabled,
|
||||
** regardless of the value of the [SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE]
|
||||
@@ -2630,7 +2651,7 @@ struct sqlite3_mem_methods {
|
||||
**
|
||||
** <p>Most of the SQLITE_DBCONFIG options take two arguments, so that the
|
||||
** overall call to [sqlite3_db_config()] has a total of four parameters.
|
||||
** The first argument (the third parameter to sqlite3_db_config()) is a integer.
|
||||
** The first argument (the third parameter to sqlite3_db_config()) is an integer.
|
||||
** The second argument is a pointer to an integer. If the first argument is 1,
|
||||
** then the option becomes enabled. If the first integer argument is 0, then the
|
||||
** option is disabled. If the first argument is -1, then the option setting
|
||||
@@ -2920,7 +2941,7 @@ SQLITE_API int sqlite3_is_interrupted(sqlite3*);
|
||||
** ^These routines return 0 if the statement is incomplete. ^If a
|
||||
** memory allocation fails, then SQLITE_NOMEM is returned.
|
||||
**
|
||||
** ^These routines do not parse the SQL statements thus
|
||||
** ^These routines do not parse the SQL statements and thus
|
||||
** will not detect syntactically incorrect SQL.
|
||||
**
|
||||
** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior
|
||||
@@ -3037,7 +3058,7 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
|
||||
** indefinitely if possible. The results of passing any other negative value
|
||||
** are undefined.
|
||||
**
|
||||
** Internally, each SQLite database handle store two timeout values - the
|
||||
** Internally, each SQLite database handle stores two timeout values - the
|
||||
** busy-timeout (used for rollback mode databases, or if the VFS does not
|
||||
** support blocking locks) and the setlk-timeout (used for blocking locks
|
||||
** on wal-mode databases). The sqlite3_busy_timeout() method sets both
|
||||
@@ -3067,7 +3088,7 @@ SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags);
|
||||
** This is a legacy interface that is preserved for backwards compatibility.
|
||||
** Use of this interface is not recommended.
|
||||
**
|
||||
** Definition: A <b>result table</b> is memory data structure created by the
|
||||
** Definition: A <b>result table</b> is a memory data structure created by the
|
||||
** [sqlite3_get_table()] interface. A result table records the
|
||||
** complete query results from one or more queries.
|
||||
**
|
||||
@@ -3210,7 +3231,7 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list);
|
||||
** ^Calling sqlite3_free() with a pointer previously returned
|
||||
** by sqlite3_malloc() or sqlite3_realloc() releases that memory so
|
||||
** that it might be reused. ^The sqlite3_free() routine is
|
||||
** a no-op if is called with a NULL pointer. Passing a NULL pointer
|
||||
** a no-op if it is called with a NULL pointer. Passing a NULL pointer
|
||||
** to sqlite3_free() is harmless. After being freed, memory
|
||||
** should neither be read nor written. Even reading previously freed
|
||||
** memory might result in a segmentation fault or other severe error.
|
||||
@@ -3228,13 +3249,13 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list);
|
||||
** sqlite3_free(X).
|
||||
** ^sqlite3_realloc(X,N) returns a pointer to a memory allocation
|
||||
** of at least N bytes in size or NULL if insufficient memory is available.
|
||||
** ^If M is the size of the prior allocation, then min(N,M) bytes
|
||||
** of the prior allocation are copied into the beginning of buffer returned
|
||||
** ^If M is the size of the prior allocation, then min(N,M) bytes of the
|
||||
** prior allocation are copied into the beginning of the buffer returned
|
||||
** by sqlite3_realloc(X,N) and the prior allocation is freed.
|
||||
** ^If sqlite3_realloc(X,N) returns NULL and N is positive, then the
|
||||
** prior allocation is not freed.
|
||||
**
|
||||
** ^The sqlite3_realloc64(X,N) interfaces works the same as
|
||||
** ^The sqlite3_realloc64(X,N) interface works the same as
|
||||
** sqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead
|
||||
** of a 32-bit signed integer.
|
||||
**
|
||||
@@ -3284,7 +3305,7 @@ SQLITE_API sqlite3_uint64 sqlite3_msize(void*);
|
||||
** was last reset. ^The values returned by [sqlite3_memory_used()] and
|
||||
** [sqlite3_memory_highwater()] include any overhead
|
||||
** added by SQLite in its implementation of [sqlite3_malloc()],
|
||||
** but not overhead added by the any underlying system library
|
||||
** but not overhead added by any underlying system library
|
||||
** routines that [sqlite3_malloc()] may call.
|
||||
**
|
||||
** ^The memory high-water mark is reset to the current value of
|
||||
@@ -3736,7 +3757,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
|
||||
** there is no harm in trying.)
|
||||
**
|
||||
** ^(<dt>[SQLITE_OPEN_SHAREDCACHE]</dt>
|
||||
** <dd>The database is opened [shared cache] enabled, overriding
|
||||
** <dd>The database is opened with [shared cache] enabled, overriding
|
||||
** the default shared cache setting provided by
|
||||
** [sqlite3_enable_shared_cache()].)^
|
||||
** The [use of shared cache mode is discouraged] and hence shared cache
|
||||
@@ -3744,7 +3765,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
|
||||
** this option is a no-op.
|
||||
**
|
||||
** ^(<dt>[SQLITE_OPEN_PRIVATECACHE]</dt>
|
||||
** <dd>The database is opened [shared cache] disabled, overriding
|
||||
** <dd>The database is opened with [shared cache] disabled, overriding
|
||||
** the default shared cache setting provided by
|
||||
** [sqlite3_enable_shared_cache()].)^
|
||||
**
|
||||
@@ -4162,7 +4183,7 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename);
|
||||
** subsequent calls to other SQLite interface functions.)^
|
||||
**
|
||||
** ^The sqlite3_errstr(E) interface returns the English-language text
|
||||
** that describes the [result code] E, as UTF-8, or NULL if E is not an
|
||||
** that describes the [result code] E, as UTF-8, or NULL if E is not a
|
||||
** result code for which a text error message is available.
|
||||
** ^(Memory to hold the error message string is managed internally
|
||||
** and must not be freed by the application)^.
|
||||
@@ -4170,7 +4191,7 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename);
|
||||
** ^If the most recent error references a specific token in the input
|
||||
** SQL, the sqlite3_error_offset() interface returns the byte offset
|
||||
** of the start of that token. ^The byte offset returned by
|
||||
** sqlite3_error_offset() assumes that the input SQL is UTF8.
|
||||
** sqlite3_error_offset() assumes that the input SQL is UTF-8.
|
||||
** ^If the most recent error does not reference a specific token in the input
|
||||
** SQL, then the sqlite3_error_offset() function returns -1.
|
||||
**
|
||||
@@ -4195,6 +4216,34 @@ SQLITE_API const void *sqlite3_errmsg16(sqlite3*);
|
||||
SQLITE_API const char *sqlite3_errstr(int);
|
||||
SQLITE_API int sqlite3_error_offset(sqlite3 *db);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Set Error Codes And Message
|
||||
** METHOD: sqlite3
|
||||
**
|
||||
** Set the error code of the database handle passed as the first argument
|
||||
** to errcode, and the error message to a copy of nul-terminated string
|
||||
** zErrMsg. If zErrMsg is passed NULL, then the error message is set to
|
||||
** the default message associated with the supplied error code. Subsequent
|
||||
** calls to [sqlite3_errcode()] and [sqlite3_errmsg()] and similar will
|
||||
** return the values set by this routine in place of what was previously
|
||||
** set by SQLite itself.
|
||||
**
|
||||
** This function returns SQLITE_OK if the error code and error message are
|
||||
** successfully set, SQLITE_NOMEM if an OOM occurs, and SQLITE_MISUSE if
|
||||
** the database handle is NULL or invalid.
|
||||
**
|
||||
** The error code and message set by this routine remains in effect until
|
||||
** they are changed, either by another call to this routine or until they are
|
||||
** changed to by SQLite itself to reflect the result of some subsquent
|
||||
** API call.
|
||||
**
|
||||
** This function is intended for use by SQLite extensions or wrappers. The
|
||||
** idea is that an extension or wrapper can use this routine to set error
|
||||
** messages and error codes and thus behave more like a core SQLite
|
||||
** feature from the point of view of an application.
|
||||
*/
|
||||
SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zErrMsg);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Prepared Statement Object
|
||||
** KEYWORDS: {prepared statement} {prepared statements}
|
||||
@@ -4269,8 +4318,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
|
||||
**
|
||||
** These constants define various performance limits
|
||||
** that can be lowered at run-time using [sqlite3_limit()].
|
||||
** The synopsis of the meanings of the various limits is shown below.
|
||||
** Additional information is available at [limits | Limits in SQLite].
|
||||
** A concise description of these limits follows, and additional information
|
||||
** is available at [limits | Limits in SQLite].
|
||||
**
|
||||
** <dl>
|
||||
** [[SQLITE_LIMIT_LENGTH]] ^(<dt>SQLITE_LIMIT_LENGTH</dt>
|
||||
@@ -4335,7 +4384,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
|
||||
/*
|
||||
** CAPI3REF: Prepare Flags
|
||||
**
|
||||
** These constants define various flags that can be passed into
|
||||
** These constants define various flags that can be passed into the
|
||||
** "prepFlags" parameter of the [sqlite3_prepare_v3()] and
|
||||
** [sqlite3_prepare16_v3()] interfaces.
|
||||
**
|
||||
@@ -4422,7 +4471,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
|
||||
** there is a small performance advantage to passing an nByte parameter that
|
||||
** is the number of bytes in the input string <i>including</i>
|
||||
** the nul-terminator.
|
||||
** Note that nByte measure the length of the input in bytes, not
|
||||
** Note that nByte measures the length of the input in bytes, not
|
||||
** characters, even for the UTF-16 interfaces.
|
||||
**
|
||||
** ^If pzTail is not NULL then *pzTail is made to point to the first byte
|
||||
@@ -4556,7 +4605,7 @@ SQLITE_API int sqlite3_prepare16_v3(
|
||||
**
|
||||
** ^The sqlite3_expanded_sql() interface returns NULL if insufficient memory
|
||||
** is available to hold the result, or if the result would exceed the
|
||||
** the maximum string length determined by the [SQLITE_LIMIT_LENGTH].
|
||||
** maximum string length determined by the [SQLITE_LIMIT_LENGTH].
|
||||
**
|
||||
** ^The [SQLITE_TRACE_SIZE_LIMIT] compile-time option limits the size of
|
||||
** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time
|
||||
@@ -4744,7 +4793,7 @@ typedef struct sqlite3_value sqlite3_value;
|
||||
**
|
||||
** The context in which an SQL function executes is stored in an
|
||||
** sqlite3_context object. ^A pointer to an sqlite3_context object
|
||||
** is always first parameter to [application-defined SQL functions].
|
||||
** is always the first parameter to [application-defined SQL functions].
|
||||
** The application-defined SQL function implementation will pass this
|
||||
** pointer through into calls to [sqlite3_result_int | sqlite3_result()],
|
||||
** [sqlite3_aggregate_context()], [sqlite3_user_data()],
|
||||
@@ -4868,9 +4917,11 @@ typedef struct sqlite3_context sqlite3_context;
|
||||
** associated with the pointer P of type T. ^D is either a NULL pointer or
|
||||
** a pointer to a destructor function for P. ^SQLite will invoke the
|
||||
** destructor D with a single argument of P when it is finished using
|
||||
** P. The T parameter should be a static string, preferably a string
|
||||
** literal. The sqlite3_bind_pointer() routine is part of the
|
||||
** [pointer passing interface] added for SQLite 3.20.0.
|
||||
** P, even if the call to sqlite3_bind_pointer() fails. Due to a
|
||||
** historical design quirk, results are undefined if D is
|
||||
** SQLITE_TRANSIENT. The T parameter should be a static string,
|
||||
** preferably a string literal. The sqlite3_bind_pointer() routine is
|
||||
** part of the [pointer passing interface] added for SQLite 3.20.0.
|
||||
**
|
||||
** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer
|
||||
** for the [prepared statement] or with a prepared statement for which
|
||||
@@ -5481,7 +5532,7 @@ SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol);
|
||||
**
|
||||
** ^The sqlite3_finalize() function is called to delete a [prepared statement].
|
||||
** ^If the most recent evaluation of the statement encountered no errors
|
||||
** or if the statement is never been evaluated, then sqlite3_finalize() returns
|
||||
** or if the statement has never been evaluated, then sqlite3_finalize() returns
|
||||
** SQLITE_OK. ^If the most recent evaluation of statement S failed, then
|
||||
** sqlite3_finalize(S) returns the appropriate [error code] or
|
||||
** [extended error code].
|
||||
@@ -5713,7 +5764,7 @@ SQLITE_API int sqlite3_create_window_function(
|
||||
/*
|
||||
** CAPI3REF: Text Encodings
|
||||
**
|
||||
** These constant define integer codes that represent the various
|
||||
** These constants define integer codes that represent the various
|
||||
** text encodings supported by SQLite.
|
||||
*/
|
||||
#define SQLITE_UTF8 1 /* IMP: R-37514-35566 */
|
||||
@@ -5805,7 +5856,7 @@ SQLITE_API int sqlite3_create_window_function(
|
||||
** result.
|
||||
** Every function that invokes [sqlite3_result_subtype()] should have this
|
||||
** property. If it does not, then the call to [sqlite3_result_subtype()]
|
||||
** might become a no-op if the function is used as term in an
|
||||
** might become a no-op if the function is used as a term in an
|
||||
** [expression index]. On the other hand, SQL functions that never invoke
|
||||
** [sqlite3_result_subtype()] should avoid setting this property, as the
|
||||
** purpose of this property is to disable certain optimizations that are
|
||||
@@ -5932,7 +5983,7 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6
|
||||
** sqlite3_value_nochange(X) interface returns true if and only if
|
||||
** the column corresponding to X is unchanged by the UPDATE operation
|
||||
** that the xUpdate method call was invoked to implement and if
|
||||
** and the prior [xColumn] method call that was invoked to extracted
|
||||
** the prior [xColumn] method call that was invoked to extract
|
||||
** the value for that column returned without setting a result (probably
|
||||
** because it queried [sqlite3_vtab_nochange()] and found that the column
|
||||
** was unchanging). ^Within an [xUpdate] method, any value for which
|
||||
@@ -6205,6 +6256,7 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi
|
||||
** or a NULL pointer if there were no prior calls to
|
||||
** sqlite3_set_clientdata() with the same values of D and N.
|
||||
** Names are compared using strcmp() and are thus case sensitive.
|
||||
** It returns 0 on success and SQLITE_NOMEM on allocation failure.
|
||||
**
|
||||
** If P and X are both non-NULL, then the destructor X is invoked with
|
||||
** argument P on the first of the following occurrences:
|
||||
@@ -8881,9 +8933,18 @@ SQLITE_API int sqlite3_status64(
|
||||
** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a
|
||||
** non-zero [error code] on failure.
|
||||
**
|
||||
** ^The sqlite3_db_status64(D,O,C,H,R) routine works exactly the same
|
||||
** way as sqlite3_db_status(D,O,C,H,R) routine except that the C and H
|
||||
** parameters are pointer to 64-bit integers (type: sqlite3_int64) instead
|
||||
** of pointers to 32-bit integers, which allows larger status values
|
||||
** to be returned. If a status value exceeds 2,147,483,647 then
|
||||
** sqlite3_db_status() will truncate the value whereas sqlite3_db_status64()
|
||||
** will return the full value.
|
||||
**
|
||||
** See also: [sqlite3_status()] and [sqlite3_stmt_status()].
|
||||
*/
|
||||
SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg);
|
||||
SQLITE_API int sqlite3_db_status64(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Status Parameters for database connections
|
||||
@@ -8980,6 +9041,10 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
||||
** If an IO or other error occurs while writing a page to disk, the effect
|
||||
** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The
|
||||
** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0.
|
||||
** <p>
|
||||
** ^(There is overlap between the quantities measured by this parameter
|
||||
** (SQLITE_DBSTATUS_CACHE_WRITE) and SQLITE_DBSTATUS_TEMPBUF_SPILL.
|
||||
** Resetting one will reduce the other.)^
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt>
|
||||
@@ -8995,6 +9060,18 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
||||
** <dd>This parameter returns zero for the current value if and only if
|
||||
** all foreign key constraints (deferred or immediate) have been
|
||||
** resolved.)^ ^The highwater mark is always 0.
|
||||
**
|
||||
** [[SQLITE_DBSTATUS_TEMPBUF_SPILL] ^(<dt>SQLITE_DBSTATUS_TEMPBUF_SPILL</dt>
|
||||
** <dd>^(This parameter returns the number of bytes written to temporary
|
||||
** files on disk that could have been kept in memory had sufficient memory
|
||||
** been available. This value includes writes to intermediate tables that
|
||||
** are part of complex queries, external sorts that spill to disk, and
|
||||
** writes to TEMP tables.)^
|
||||
** ^The highwater mark is always 0.
|
||||
** <p>
|
||||
** ^(There is overlap between the quantities measured by this parameter
|
||||
** (SQLITE_DBSTATUS_TEMPBUF_SPILL) and SQLITE_DBSTATUS_CACHE_WRITE.
|
||||
** Resetting one will reduce the other.)^
|
||||
** </dd>
|
||||
** </dl>
|
||||
*/
|
||||
@@ -9011,7 +9088,8 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
||||
#define SQLITE_DBSTATUS_DEFERRED_FKS 10
|
||||
#define SQLITE_DBSTATUS_CACHE_USED_SHARED 11
|
||||
#define SQLITE_DBSTATUS_CACHE_SPILL 12
|
||||
#define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */
|
||||
#define SQLITE_DBSTATUS_TEMPBUF_SPILL 13
|
||||
#define SQLITE_DBSTATUS_MAX 13 /* Largest defined DBSTATUS */
|
||||
|
||||
|
||||
/*
|
||||
@@ -9776,7 +9854,7 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
|
||||
** is the number of pages currently in the write-ahead log file,
|
||||
** including those that were just committed.
|
||||
**
|
||||
** The callback function should normally return [SQLITE_OK]. ^If an error
|
||||
** ^The callback function should normally return [SQLITE_OK]. ^If an error
|
||||
** code is returned, that error will propagate back up through the
|
||||
** SQLite code base to cause the statement that provoked the callback
|
||||
** to report an error, though the commit will have still occurred. If the
|
||||
@@ -9784,13 +9862,26 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
|
||||
** that does not correspond to any valid SQLite error code, the results
|
||||
** are undefined.
|
||||
**
|
||||
** A single database handle may have at most a single write-ahead log callback
|
||||
** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any
|
||||
** previously registered write-ahead log callback. ^The return value is
|
||||
** a copy of the third parameter from the previous call, if any, or 0.
|
||||
** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the
|
||||
** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will
|
||||
** overwrite any prior [sqlite3_wal_hook()] settings.
|
||||
** ^A single database handle may have at most a single write-ahead log
|
||||
** callback registered at one time. ^Calling [sqlite3_wal_hook()]
|
||||
** replaces the default behavior or previously registered write-ahead
|
||||
** log callback.
|
||||
**
|
||||
** ^The return value is a copy of the third parameter from the
|
||||
** previous call, if any, or 0.
|
||||
**
|
||||
** ^The [sqlite3_wal_autocheckpoint()] interface and the
|
||||
** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and
|
||||
** will overwrite any prior [sqlite3_wal_hook()] settings.
|
||||
**
|
||||
** ^If a write-ahead log callback is set using this function then
|
||||
** [sqlite3_wal_checkpoint_v2()] or [PRAGMA wal_checkpoint]
|
||||
** should be invoked periodically to keep the write-ahead log file
|
||||
** from growing without bound.
|
||||
**
|
||||
** ^Passing a NULL pointer for the callback disables automatic
|
||||
** checkpointing entirely. To re-enable the default behavior, call
|
||||
** sqlite3_wal_autocheckpoint(db,1000) or use [PRAGMA wal_checkpoint].
|
||||
*/
|
||||
SQLITE_API void *sqlite3_wal_hook(
|
||||
sqlite3*,
|
||||
@@ -9807,7 +9898,7 @@ SQLITE_API void *sqlite3_wal_hook(
|
||||
** to automatically [checkpoint]
|
||||
** after committing a transaction if there are N or
|
||||
** more frames in the [write-ahead log] file. ^Passing zero or
|
||||
** a negative value as the nFrame parameter disables automatic
|
||||
** a negative value as the N parameter disables automatic
|
||||
** checkpoints entirely.
|
||||
**
|
||||
** ^The callback registered by this function replaces any existing callback
|
||||
@@ -9823,9 +9914,10 @@ SQLITE_API void *sqlite3_wal_hook(
|
||||
**
|
||||
** ^Every new [database connection] defaults to having the auto-checkpoint
|
||||
** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT]
|
||||
** pages. The use of this interface
|
||||
** is only necessary if the default setting is found to be suboptimal
|
||||
** for a particular application.
|
||||
** pages.
|
||||
**
|
||||
** ^The use of this interface is only necessary if the default setting
|
||||
** is found to be suboptimal for a particular application.
|
||||
*/
|
||||
SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N);
|
||||
|
||||
@@ -9890,6 +9982,11 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);
|
||||
** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the
|
||||
** addition that it also truncates the log file to zero bytes just prior
|
||||
** to a successful return.
|
||||
**
|
||||
** <dt>SQLITE_CHECKPOINT_NOOP<dd>
|
||||
** ^This mode always checkpoints zero frames. The only reason to invoke
|
||||
** a NOOP checkpoint is to access the values returned by
|
||||
** sqlite3_wal_checkpoint_v2() via output parameters *pnLog and *pnCkpt.
|
||||
** </dl>
|
||||
**
|
||||
** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in
|
||||
@@ -9960,6 +10057,7 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
|
||||
** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the
|
||||
** meaning of each of these checkpoint modes.
|
||||
*/
|
||||
#define SQLITE_CHECKPOINT_NOOP -1 /* Do no work at all */
|
||||
#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */
|
||||
#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */
|
||||
#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */
|
||||
@@ -10328,7 +10426,7 @@ SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle);
|
||||
** ){
|
||||
** // do something with pVal
|
||||
** }
|
||||
** if( rc!=SQLITE_OK ){
|
||||
** if( rc!=SQLITE_DONE ){
|
||||
** // an error has occurred
|
||||
** }
|
||||
** </pre></blockquote>)^
|
||||
@@ -10787,7 +10885,7 @@ typedef struct sqlite3_snapshot {
|
||||
** The [sqlite3_snapshot_get()] interface is only available when the
|
||||
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
|
||||
*/
|
||||
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
|
||||
SQLITE_API int sqlite3_snapshot_get(
|
||||
sqlite3 *db,
|
||||
const char *zSchema,
|
||||
sqlite3_snapshot **ppSnapshot
|
||||
@@ -10836,7 +10934,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
|
||||
** The [sqlite3_snapshot_open()] interface is only available when the
|
||||
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
|
||||
*/
|
||||
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
|
||||
SQLITE_API int sqlite3_snapshot_open(
|
||||
sqlite3 *db,
|
||||
const char *zSchema,
|
||||
sqlite3_snapshot *pSnapshot
|
||||
@@ -10853,7 +10951,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
|
||||
** The [sqlite3_snapshot_free()] interface is only available when the
|
||||
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
|
||||
*/
|
||||
SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
|
||||
SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot*);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Compare the ages of two snapshot handles.
|
||||
@@ -10880,7 +10978,7 @@ SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
|
||||
** This interface is only available if SQLite is compiled with the
|
||||
** [SQLITE_ENABLE_SNAPSHOT] option.
|
||||
*/
|
||||
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
|
||||
SQLITE_API int sqlite3_snapshot_cmp(
|
||||
sqlite3_snapshot *p1,
|
||||
sqlite3_snapshot *p2
|
||||
);
|
||||
@@ -10908,7 +11006,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
|
||||
** This interface is only available if SQLite is compiled with the
|
||||
** [SQLITE_ENABLE_SNAPSHOT] option.
|
||||
*/
|
||||
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
|
||||
SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Serialize a database
|
||||
@@ -10982,12 +11080,13 @@ SQLITE_API unsigned char *sqlite3_serialize(
|
||||
**
|
||||
** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the
|
||||
** [database connection] D to disconnect from database S and then
|
||||
** reopen S as an in-memory database based on the serialization contained
|
||||
** in P. The serialized database P is N bytes in size. M is the size of
|
||||
** the buffer P, which might be larger than N. If M is larger than N, and
|
||||
** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is
|
||||
** permitted to add content to the in-memory database as long as the total
|
||||
** size does not exceed M bytes.
|
||||
** reopen S as an in-memory database based on the serialization
|
||||
** contained in P. If S is a NULL pointer, the main database is
|
||||
** used. The serialized database P is N bytes in size. M is the size
|
||||
** of the buffer P, which might be larger than N. If M is larger than
|
||||
** N, and the SQLITE_DESERIALIZE_READONLY bit is not set in F, then
|
||||
** SQLite is permitted to add content to the in-memory database as
|
||||
** long as the total size does not exceed M bytes.
|
||||
**
|
||||
** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will
|
||||
** invoke sqlite3_free() on the serialization buffer when the database
|
||||
@@ -11054,6 +11153,54 @@ SQLITE_API int sqlite3_deserialize(
|
||||
#define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */
|
||||
#define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */
|
||||
|
||||
/*
|
||||
** CAPI3REF: Bind array values to the CARRAY table-valued function
|
||||
**
|
||||
** The sqlite3_carray_bind(S,I,P,N,F,X) interface binds an array value to
|
||||
** one of the first argument of the [carray() table-valued function]. The
|
||||
** S parameter is a pointer to the [prepared statement] that uses the carray()
|
||||
** functions. I is the parameter index to be bound. P is a pointer to the
|
||||
** array to be bound, and N is the number of eements in the array. The
|
||||
** F argument is one of constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64],
|
||||
** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], or [SQLITE_CARRAY_BLOB] to
|
||||
** indicate the datatype of the array being bound. The X argument is not a
|
||||
** NULL pointer, then SQLite will invoke the function X on the P parameter
|
||||
** after it has finished using P, even if the call to
|
||||
** sqlite3_carray_bind() fails. The special-case finalizer
|
||||
** SQLITE_TRANSIENT has no effect here.
|
||||
*/
|
||||
SQLITE_API int sqlite3_carray_bind(
|
||||
sqlite3_stmt *pStmt, /* Statement to be bound */
|
||||
int i, /* Parameter index */
|
||||
void *aData, /* Pointer to array data */
|
||||
int nData, /* Number of data elements */
|
||||
int mFlags, /* CARRAY flags */
|
||||
void (*xDel)(void*) /* Destructor for aData */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Datatypes for the CARRAY table-valued function
|
||||
**
|
||||
** The fifth argument to the [sqlite3_carray_bind()] interface musts be
|
||||
** one of the following constants, to specify the datatype of the array
|
||||
** that is being bound into the [carray table-valued function].
|
||||
*/
|
||||
#define SQLITE_CARRAY_INT32 0 /* Data is 32-bit signed integers */
|
||||
#define SQLITE_CARRAY_INT64 1 /* Data is 64-bit signed integers */
|
||||
#define SQLITE_CARRAY_DOUBLE 2 /* Data is doubles */
|
||||
#define SQLITE_CARRAY_TEXT 3 /* Data is char* */
|
||||
#define SQLITE_CARRAY_BLOB 4 /* Data is struct iovec */
|
||||
|
||||
/*
|
||||
** Versions of the above #defines that omit the initial SQLITE_, for
|
||||
** legacy compatibility.
|
||||
*/
|
||||
#define CARRAY_INT32 0 /* Data is 32-bit signed integers */
|
||||
#define CARRAY_INT64 1 /* Data is 64-bit signed integers */
|
||||
#define CARRAY_DOUBLE 2 /* Data is doubles */
|
||||
#define CARRAY_TEXT 3 /* Data is char* */
|
||||
#define CARRAY_BLOB 4 /* Data is struct iovec */
|
||||
|
||||
/*
|
||||
** Undo the hack that converts floating point types to integer for
|
||||
** builds on processors without floating point support.
|
||||
@@ -12313,14 +12460,32 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*);
|
||||
** update the "main" database attached to handle db with the changes found in
|
||||
** the changeset passed via the second and third arguments.
|
||||
**
|
||||
** All changes made by these functions are enclosed in a savepoint transaction.
|
||||
** If any other error (aside from a constraint failure when attempting to
|
||||
** write to the target database) occurs, then the savepoint transaction is
|
||||
** rolled back, restoring the target database to its original state, and an
|
||||
** SQLite error code returned. Additionally, starting with version 3.51.0,
|
||||
** an error code and error message that may be accessed using the
|
||||
** [sqlite3_errcode()] and [sqlite3_errmsg()] APIs are left in the database
|
||||
** handle.
|
||||
**
|
||||
** The fourth argument (xFilter) passed to these functions is the "filter
|
||||
** callback". If it is not NULL, then for each table affected by at least one
|
||||
** change in the changeset, the filter callback is invoked with
|
||||
** the table name as the second argument, and a copy of the context pointer
|
||||
** passed as the sixth argument as the first. If the "filter callback"
|
||||
** returns zero, then no attempt is made to apply any changes to the table.
|
||||
** Otherwise, if the return value is non-zero or the xFilter argument to
|
||||
** is NULL, all changes related to the table are attempted.
|
||||
** callback". This may be passed NULL, in which case all changes in the
|
||||
** changeset are applied to the database. For sqlite3changeset_apply() and
|
||||
** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once
|
||||
** for each table affected by at least one change in the changeset. In this
|
||||
** case the table name is passed as the second argument, and a copy of
|
||||
** the context pointer passed as the sixth argument to apply() or apply_v2()
|
||||
** as the first. If the "filter callback" returns zero, then no attempt is
|
||||
** made to apply any changes to the table. Otherwise, if the return value is
|
||||
** non-zero, all changes related to the table are attempted.
|
||||
**
|
||||
** For sqlite3_changeset_apply_v3(), the xFilter callback is invoked once
|
||||
** per change. The second argument in this case is an sqlite3_changeset_iter
|
||||
** that may be queried using the usual APIs for the details of the current
|
||||
** change. If the "filter callback" returns zero in this case, then no attempt
|
||||
** is made to apply the current change. If it returns non-zero, the change
|
||||
** is applied.
|
||||
**
|
||||
** For each table that is not excluded by the filter callback, this function
|
||||
** tests that the target database contains a compatible table. A table is
|
||||
@@ -12341,11 +12506,11 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*);
|
||||
** one such warning is issued for each table in the changeset.
|
||||
**
|
||||
** For each change for which there is a compatible table, an attempt is made
|
||||
** to modify the table contents according to the UPDATE, INSERT or DELETE
|
||||
** change. If a change cannot be applied cleanly, the conflict handler
|
||||
** function passed as the fifth argument to sqlite3changeset_apply() may be
|
||||
** invoked. A description of exactly when the conflict handler is invoked for
|
||||
** each type of change is below.
|
||||
** to modify the table contents according to each UPDATE, INSERT or DELETE
|
||||
** change that is not excluded by a filter callback. If a change cannot be
|
||||
** applied cleanly, the conflict handler function passed as the fifth argument
|
||||
** to sqlite3changeset_apply() may be invoked. A description of exactly when
|
||||
** the conflict handler is invoked for each type of change is below.
|
||||
**
|
||||
** Unlike the xFilter argument, xConflict may not be passed NULL. The results
|
||||
** of passing anything other than a valid function pointer as the xConflict
|
||||
@@ -12441,12 +12606,6 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*);
|
||||
** This can be used to further customize the application's conflict
|
||||
** resolution strategy.
|
||||
**
|
||||
** All changes made by these functions are enclosed in a savepoint transaction.
|
||||
** If any other error (aside from a constraint failure when attempting to
|
||||
** write to the target database) occurs, then the savepoint transaction is
|
||||
** rolled back, restoring the target database to its original state, and an
|
||||
** SQLite error code returned.
|
||||
**
|
||||
** If the output parameters (ppRebase) and (pnRebase) are non-NULL and
|
||||
** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2()
|
||||
** may set (*ppRebase) to point to a "rebase" that may be used with the
|
||||
@@ -12496,6 +12655,23 @@ SQLITE_API int sqlite3changeset_apply_v2(
|
||||
void **ppRebase, int *pnRebase, /* OUT: Rebase data */
|
||||
int flags /* SESSION_CHANGESETAPPLY_* flags */
|
||||
);
|
||||
SQLITE_API int sqlite3changeset_apply_v3(
|
||||
sqlite3 *db, /* Apply change to "main" db of this handle */
|
||||
int nChangeset, /* Size of changeset in bytes */
|
||||
void *pChangeset, /* Changeset blob */
|
||||
int(*xFilter)(
|
||||
void *pCtx, /* Copy of sixth arg to _apply() */
|
||||
sqlite3_changeset_iter *p /* Handle describing change */
|
||||
),
|
||||
int(*xConflict)(
|
||||
void *pCtx, /* Copy of sixth arg to _apply() */
|
||||
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
|
||||
sqlite3_changeset_iter *p /* Handle describing change and conflict */
|
||||
),
|
||||
void *pCtx, /* First argument passed to xConflict */
|
||||
void **ppRebase, int *pnRebase, /* OUT: Rebase data */
|
||||
int flags /* SESSION_CHANGESETAPPLY_* flags */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Flags for sqlite3changeset_apply_v2
|
||||
@@ -12915,6 +13091,23 @@ SQLITE_API int sqlite3changeset_apply_v2_strm(
|
||||
void **ppRebase, int *pnRebase,
|
||||
int flags
|
||||
);
|
||||
SQLITE_API int sqlite3changeset_apply_v3_strm(
|
||||
sqlite3 *db, /* Apply change to "main" db of this handle */
|
||||
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
|
||||
void *pIn, /* First arg for xInput */
|
||||
int(*xFilter)(
|
||||
void *pCtx, /* Copy of sixth arg to _apply() */
|
||||
sqlite3_changeset_iter *p
|
||||
),
|
||||
int(*xConflict)(
|
||||
void *pCtx, /* Copy of sixth arg to _apply() */
|
||||
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
|
||||
sqlite3_changeset_iter *p /* Handle describing change and conflict */
|
||||
),
|
||||
void *pCtx, /* First argument passed to xConflict */
|
||||
void **ppRebase, int *pnRebase,
|
||||
int flags
|
||||
);
|
||||
SQLITE_API int sqlite3changeset_concat_strm(
|
||||
int (*xInputA)(void *pIn, void *pData, int *pnData),
|
||||
void *pInA,
|
||||
|
||||
7
deps/sqlite/sqlite3ext.h
vendored
7
deps/sqlite/sqlite3ext.h
vendored
@@ -368,6 +368,10 @@ struct sqlite3_api_routines {
|
||||
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);
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -703,6 +707,9 @@ typedef int (*sqlite3_loadext_entry)(
|
||||
#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)
|
||||
|
||||
4
docs/app_development.md
Normal file
4
docs/app_development.md
Normal file
@@ -0,0 +1,4 @@
|
||||
@page app_development App Development
|
||||
|
||||
- @subpage app_development_cheat_sheet
|
||||
- @subpage app_development_guide
|
||||
@@ -1,4 +1,4 @@
|
||||
# App Development Cheat Sheet
|
||||
@page app_development_cheat_sheet App Development Cheat Sheet
|
||||
|
||||
Making apps for the impatient tilde friend.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# App Development Guide
|
||||
@page app_development_guide App Development Guide
|
||||
|
||||
A Tilde Friends application starts with code that runs on a Tilde Friends server, possibly far away from where you wrote it, in a little JavaScript environment, in its own restricted process, with the only access to the outside world being the ability to send messages to the server. This document gives some recipes showing how that can be used to build a functional user-facing application in light of the unique constraints present.
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@page connecting_manyverse How to Connect Manyverse
|
||||
|
||||
# Connecting with Manyverse
|
||||
|
||||
Communication with [Manyverse](https://www.manyver.se/) should Just Work (tm).
|
||||
|
||||
5
docs/howto.md
Normal file
5
docs/howto.md
Normal file
@@ -0,0 +1,5 @@
|
||||
@page howto How To
|
||||
|
||||
- @subpage upgrading
|
||||
- @subpage transfer_account
|
||||
- @subpage connecting_manyverse
|
||||
@@ -1,4 +1,4 @@
|
||||
# Inspiration
|
||||
@page inspiration Inspiration
|
||||
|
||||
This is an ever-growing list of software that is similar to what Tilde Friends tries to be but as far as I can tell don't quite fit the same niche.
|
||||
|
||||
|
||||
32
docs/model.md
Normal file
32
docs/model.md
Normal file
@@ -0,0 +1,32 @@
|
||||
@page model 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"];
|
||||
}
|
||||
```
|
||||
5
docs/overview.md
Normal file
5
docs/overview.md
Normal file
@@ -0,0 +1,5 @@
|
||||
@page overview Overview
|
||||
|
||||
- @subpage inspiration
|
||||
- @subpage model
|
||||
- @subpage vision
|
||||
18
docs/transfer_account.md
Normal file
18
docs/transfer_account.md
Normal file
@@ -0,0 +1,18 @@
|
||||
@page transfer_account How to Transfer an Account
|
||||
|
||||
Secure Scuttlebutt accounts can be easily transferred between apps and devices.
|
||||
|
||||
However, it is not recommended to use an account on multiple devices. If you
|
||||
author a message on one device without having received all messages authored
|
||||
from another, your account may become irrecoverably forked. Other clients may
|
||||
stop receiving your messages if this happens.
|
||||
|
||||
1. In Tilde Friends, the _identity_ app will let you export and import your identity as a series of twelve English words for copying and pasting. Keep these words secret!
|
||||
|
||||
2. The _sneaker_ app will let you export and import your feed to a file, but it's likely easier and faster to use your initial account on the receiver as a throwaway account to connect, follow yourself, and do the initial replication.
|
||||
|
||||
Your identity and messages on the target device is all you need to resume posting. Deleting the identity from the source to avoid accidentally using it is probably a wise idea.
|
||||
|
||||
If you are moving accounts between applications on the same device, note that they may not be able to see each other if they attempt to use the same network port number.
|
||||
|
||||
You may also wish to make your account the server identity in the _identity_ app so that other devices on the network see and connect to you by your well-known identity.
|
||||
@@ -1,4 +1,4 @@
|
||||
# Upgrading
|
||||
@page upgrading Upgrading
|
||||
|
||||
Tilde Friends can be upgraded simply by running a new executable against an
|
||||
existing database.
|
||||
|
||||
@@ -41,11 +41,9 @@ options:
|
||||
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.
|
||||
https_port (default: 0): Port on which to listen for secure 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.
|
||||
fetch_hosts (default: ""): Comma-separated list of host names to which HTTP fetch requests are allowed. None if empty.
|
||||
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/"
|
||||
@@ -62,6 +60,7 @@ options:
|
||||
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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Vision
|
||||
@page vision Vision
|
||||
|
||||
Tilde Friends is a tool for making and sharing.
|
||||
|
||||
|
||||
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1750622754,
|
||||
"narHash": "sha256-kMhs+YzV4vPGfuTpD3mwzibWUE6jotw5Al2wczI0Pv8=",
|
||||
"lastModified": 1763948260,
|
||||
"narHash": "sha256-dY9qLD0H0zOUgU3vWacPY6Qc421BeQAfm8kBuBtPVE0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c7ab75210cb8cb16ddd8f290755d9558edde7ee1",
|
||||
"rev": "1c8ba8d3f7634acac4a2094eef7c32ad9106532c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
9
metadata/en-US/changelogs/42.txt
Normal file
9
metadata/en-US/changelogs/42.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
* Private messages interface overhaul in progress.
|
||||
* Added a loading indicator.
|
||||
* Documented the core JavaScript.
|
||||
* Fixed @-completion.
|
||||
* Covered up launch on Android with the splash screen.
|
||||
* Update:
|
||||
* CodeMirror
|
||||
* OpenSSL 3.5.2
|
||||
* speedscope 1.23.1
|
||||
8
metadata/en-US/changelogs/43.txt
Normal file
8
metadata/en-US/changelogs/43.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
* Fixed multiple issues with blob replication.
|
||||
* Fixed some link encoding issues.
|
||||
* Fixed some context menus being cut off.
|
||||
* Minor Android fixes.
|
||||
* Updates:
|
||||
* CodeMirror
|
||||
* OpenSSL 3.5.3
|
||||
* QuickJS 2025-09-13
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user