Compare commits
2 Commits
v0.2025.12
...
tasiaiso-n
| Author | SHA1 | Date | |
|---|---|---|---|
|
d67e47ae4b
|
|||
|
b43b8da9ab
|
@@ -3,36 +3,6 @@ run-name: ${{ gitea.actor }} running 🚀
|
|||||||
on: [push]
|
on: [push]
|
||||||
|
|
||||||
jobs:
|
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:
|
Build-All:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
@@ -78,8 +48,9 @@ jobs:
|
|||||||
- name: Build documentation
|
- name: Build documentation
|
||||||
run: |
|
run: |
|
||||||
mkdir -p out/html/ ~/.ssh/
|
mkdir -p out/html/ ~/.ssh/
|
||||||
make -j`nproc` docs
|
make docs
|
||||||
echo 'pildefriends ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKD3Kde5vDO0TrMBDK0IGGeNGe/XinWAZkSQ/rXxwUjt' >> ~/.ssh/known_hosts
|
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
|
- name: Setup JDK
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
@@ -88,13 +59,11 @@ jobs:
|
|||||||
- name: Setup Android SDK
|
- name: Setup Android SDK
|
||||||
uses: android-actions/setup-android@v3
|
uses: android-actions/setup-android@v3
|
||||||
with:
|
with:
|
||||||
packages: 'tools platform-tools build-tools;35.0.0 platforms;android-35 ndk;27.2.12479018'
|
packages: 'tools platform-tools build-tools;34.0.0 platforms;android-34 ndk;26.3.11579264'
|
||||||
- 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
|
- name: Docker build
|
||||||
run: DOCKER_BUILDKIT=1 docker build .
|
run: DOCKER_BUILDKIT=1 docker build .
|
||||||
|
- name: Build
|
||||||
|
run: ANDROID_SDK=$HOME/.android/sdk make -j`nproc` all dist docs
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -19,6 +19,10 @@
|
|||||||
[submodule "deps/picohttpparser"]
|
[submodule "deps/picohttpparser"]
|
||||||
path = deps/picohttpparser
|
path = deps/picohttpparser
|
||||||
url = https://github.com/h2o/picohttpparser.git
|
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"]
|
[submodule "deps/c-ares"]
|
||||||
path = deps/c-ares
|
path = deps/c-ares
|
||||||
url = https://github.com/c-ares/c-ares.git
|
url = https://github.com/c-ares/c-ares.git
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ src
|
|||||||
deps
|
deps
|
||||||
.clang-format
|
.clang-format
|
||||||
flake.lock
|
flake.lock
|
||||||
apps/trace/speedscope/**
|
|
||||||
|
|
||||||
# Minified files
|
# Minified files
|
||||||
**/*.min.css
|
**/*.min.css
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ RUN apt-get update && \
|
|||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
gcc \
|
gcc \
|
||||||
libc6-dev \
|
libc6-dev \
|
||||||
|
perl \
|
||||||
make
|
make
|
||||||
|
|
||||||
COPY . /app
|
COPY . /app
|
||||||
|
|||||||
369
Doxyfile
369
Doxyfile
@@ -1,4 +1,4 @@
|
|||||||
# Doxyfile 1.9.4
|
# Doxyfile 1.9.8
|
||||||
|
|
||||||
# This file describes the settings to be used by the documentation system
|
# This file describes the settings to be used by the documentation system
|
||||||
# doxygen (www.doxygen.org) for a project.
|
# doxygen (www.doxygen.org) for a project.
|
||||||
@@ -19,7 +19,8 @@
|
|||||||
# configuration file:
|
# configuration file:
|
||||||
# doxygen -x [configFile]
|
# doxygen -x [configFile]
|
||||||
# Use doxygen to compare the used configuration file with the template
|
# Use doxygen to compare the used configuration file with the template
|
||||||
# configuration file without replacing the environment variables:
|
# configuration file without replacing the environment variables or CMake type
|
||||||
|
# replacement variables:
|
||||||
# doxygen -x_noenv [configFile]
|
# doxygen -x_noenv [configFile]
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
@@ -85,7 +86,7 @@ CREATE_SUBDIRS = NO
|
|||||||
# level increment doubles the number of directories, resulting in 4096
|
# level increment doubles the number of directories, resulting in 4096
|
||||||
# directories at level 8 which is the default and also the maximum value. The
|
# 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
|
# sub-directories are organized in 2 levels, the first level always has a fixed
|
||||||
# numer of 16 directories.
|
# number of 16 directories.
|
||||||
# Minimum value: 0, maximum value: 8, default value: 8.
|
# Minimum value: 0, maximum value: 8, default value: 8.
|
||||||
# This tag requires that the tag CREATE_SUBDIRS is set to YES.
|
# This tag requires that the tag CREATE_SUBDIRS is set to YES.
|
||||||
|
|
||||||
@@ -341,7 +342,7 @@ OPTIMIZE_OUTPUT_SLICE = NO
|
|||||||
#
|
#
|
||||||
# Note see also the list of default file extension mappings.
|
# Note see also the list of default file extension mappings.
|
||||||
|
|
||||||
EXTENSION_MAPPING = js=javascript
|
EXTENSION_MAPPING =
|
||||||
|
|
||||||
# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
|
# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
|
||||||
# according to the Markdown format, which allows for more readable
|
# according to the Markdown format, which allows for more readable
|
||||||
@@ -362,6 +363,17 @@ MARKDOWN_SUPPORT = YES
|
|||||||
|
|
||||||
TOC_INCLUDE_HEADINGS = 5
|
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
|
# When enabled doxygen tries to link words that correspond to documented
|
||||||
# classes, or namespaces to their corresponding documentation. Such a link can
|
# 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
|
# be prevented in individual cases by putting a % sign in front of the word or
|
||||||
@@ -486,6 +498,14 @@ LOOKUP_CACHE_SIZE = 0
|
|||||||
|
|
||||||
NUM_PROC_THREADS = 1
|
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
|
# Build related configuration options
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
@@ -567,7 +587,8 @@ HIDE_UNDOC_MEMBERS = NO
|
|||||||
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
|
# 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
|
# 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
|
# to NO, these classes will be included in the various overviews. This option
|
||||||
# has no effect if EXTRACT_ALL is enabled.
|
# will also hide undocumented C++ concepts if enabled. This option has no effect
|
||||||
|
# if EXTRACT_ALL is enabled.
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
HIDE_UNDOC_CLASSES = NO
|
HIDE_UNDOC_CLASSES = NO
|
||||||
@@ -605,7 +626,8 @@ INTERNAL_DOCS = NO
|
|||||||
# Windows (including Cygwin) and MacOS, users should typically set this option
|
# 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
|
# to NO, whereas on Linux or other Unix flavors it should typically be set to
|
||||||
# YES.
|
# YES.
|
||||||
# The default value is: system dependent.
|
# Possible values are: SYSTEM, NO and YES.
|
||||||
|
# The default value is: SYSTEM.
|
||||||
|
|
||||||
CASE_SENSE_NAMES = YES
|
CASE_SENSE_NAMES = YES
|
||||||
|
|
||||||
@@ -857,11 +879,26 @@ WARN_IF_INCOMPLETE_DOC = YES
|
|||||||
|
|
||||||
WARN_NO_PARAMDOC = NO
|
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
|
# 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
|
# 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
|
# 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.
|
# at the end of the doxygen process doxygen will return with a non-zero status.
|
||||||
# Possible values are: NO, YES and FAIL_ON_WARNINGS.
|
# 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.
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
WARN_AS_ERROR = NO
|
WARN_AS_ERROR = NO
|
||||||
@@ -906,22 +943,28 @@ WARN_LOGFILE =
|
|||||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||||
# Note: If this tag is empty the current directory is searched.
|
# Note: If this tag is empty the current directory is searched.
|
||||||
|
|
||||||
INPUT = README.md \
|
INPUT = README.md docs/ src/
|
||||||
core/client.js \
|
|
||||||
core/core.js \
|
|
||||||
core/tfrpc.js \
|
|
||||||
docs/ \
|
|
||||||
src/
|
|
||||||
|
|
||||||
# This tag can be used to specify the character encoding of the source files
|
# This tag can be used to specify the character encoding of the source files
|
||||||
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
||||||
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
|
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
|
||||||
# documentation (see:
|
# documentation (see:
|
||||||
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
|
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
|
||||||
|
# See also: INPUT_FILE_ENCODING
|
||||||
# The default value is: UTF-8.
|
# The default value is: UTF-8.
|
||||||
|
|
||||||
INPUT_ENCODING = 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
|
# 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
|
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
|
||||||
# *.h) to filter out the source-files in the directories.
|
# *.h) to filter out the source-files in the directories.
|
||||||
@@ -933,15 +976,14 @@ INPUT_ENCODING = UTF-8
|
|||||||
# Note the list of default checked file patterns might differ from the list of
|
# Note the list of default checked file patterns might differ from the list of
|
||||||
# default file extension mappings.
|
# default file extension mappings.
|
||||||
#
|
#
|
||||||
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
|
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm,
|
||||||
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
|
# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl,
|
||||||
# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
|
# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php,
|
||||||
# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
|
# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be
|
||||||
# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
|
# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
|
||||||
# *.vhdl, *.ucf, *.qsf and *.ice.
|
# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
|
||||||
|
|
||||||
FILE_PATTERNS = *.h \
|
FILE_PATTERNS = *.h \
|
||||||
*.js \
|
|
||||||
*.md
|
*.md
|
||||||
|
|
||||||
# The RECURSIVE tag can be used to specify whether or not subdirectories should
|
# The RECURSIVE tag can be used to specify whether or not subdirectories should
|
||||||
@@ -980,9 +1022,6 @@ EXCLUDE_PATTERNS =
|
|||||||
# output. The symbol name can be a fully qualified name, a word, or if the
|
# output. The symbol name can be a fully qualified name, a word, or if the
|
||||||
# wildcard * is used, a substring. Examples: ANamespace, AClass,
|
# wildcard * is used, a substring. Examples: ANamespace, AClass,
|
||||||
# ANamespace::AClass, ANamespace::*Test
|
# 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 =
|
EXCLUDE_SYMBOLS =
|
||||||
|
|
||||||
@@ -1010,7 +1049,7 @@ EXAMPLE_RECURSIVE = NO
|
|||||||
# that contain images that are to be included in the documentation (see the
|
# that contain images that are to be included in the documentation (see the
|
||||||
# \image command).
|
# \image command).
|
||||||
|
|
||||||
IMAGE_PATH = docs/images/
|
IMAGE_PATH =
|
||||||
|
|
||||||
# The INPUT_FILTER tag can be used to specify a program that doxygen should
|
# The INPUT_FILTER tag can be used to specify a program that doxygen should
|
||||||
# invoke to filter for each input file. Doxygen will invoke the filter program
|
# invoke to filter for each input file. Doxygen will invoke the filter program
|
||||||
@@ -1027,6 +1066,11 @@ IMAGE_PATH = docs/images/
|
|||||||
# code is scanned, but not when the output code is generated. If lines are added
|
# code is scanned, but not when the output code is generated. If lines are added
|
||||||
# or removed, the anchors will not be placed correctly.
|
# 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
|
# 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
|
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
|
||||||
# properly processed by doxygen.
|
# properly processed by doxygen.
|
||||||
@@ -1068,6 +1112,15 @@ FILTER_SOURCE_PATTERNS =
|
|||||||
|
|
||||||
USE_MDFILE_AS_MAINPAGE = README.md
|
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
|
# Configuration options related to source browsing
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
@@ -1205,10 +1258,11 @@ CLANG_DATABASE_PATH =
|
|||||||
|
|
||||||
ALPHABETICAL_INDEX = YES
|
ALPHABETICAL_INDEX = YES
|
||||||
|
|
||||||
# In case all classes in a project start with a common prefix, all classes will
|
# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes)
|
||||||
# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
|
# that should be ignored while generating the index headers. The IGNORE_PREFIX
|
||||||
# can be used to specify a prefix (or a list of prefixes) that should be ignored
|
# tag works for classes, function and member names. The entity will be placed in
|
||||||
# while generating the index headers.
|
# the alphabetical list under the first letter of the entity name that remains
|
||||||
|
# after removing the prefix.
|
||||||
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
|
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
|
||||||
|
|
||||||
IGNORE_PREFIX =
|
IGNORE_PREFIX =
|
||||||
@@ -1287,7 +1341,12 @@ HTML_STYLESHEET =
|
|||||||
# Doxygen will copy the style sheet files to the output directory.
|
# 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
|
# 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
|
# style sheet in the list overrules the setting of the previous ones in the
|
||||||
# list). For an example see the documentation.
|
# 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.
|
||||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||||
|
|
||||||
HTML_EXTRA_STYLESHEET =
|
HTML_EXTRA_STYLESHEET =
|
||||||
@@ -1302,6 +1361,19 @@ HTML_EXTRA_STYLESHEET =
|
|||||||
|
|
||||||
HTML_EXTRA_FILES =
|
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
|
# 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
|
# 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
|
# this color. Hue is specified as an angle on a color-wheel, see
|
||||||
@@ -1332,15 +1404,6 @@ HTML_COLORSTYLE_SAT = 100
|
|||||||
|
|
||||||
HTML_COLORSTYLE_GAMMA = 80
|
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
|
# 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
|
# documentation will contain a main index with vertical navigation menus that
|
||||||
# are dynamically created via JavaScript. If disabled, the navigation index will
|
# are dynamically created via JavaScript. If disabled, the navigation index will
|
||||||
@@ -1360,6 +1423,13 @@ HTML_DYNAMIC_MENUS = YES
|
|||||||
|
|
||||||
HTML_DYNAMIC_SECTIONS = NO
|
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
|
# 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
|
# shown in the various tree structured indices initially; the user can expand
|
||||||
# and collapse entries dynamically later on. Doxygen will expand the tree to
|
# and collapse entries dynamically later on. Doxygen will expand the tree to
|
||||||
@@ -1490,6 +1560,16 @@ BINARY_TOC = NO
|
|||||||
|
|
||||||
TOC_EXPAND = 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
|
# 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
|
# 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
|
# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
|
||||||
@@ -1665,17 +1745,6 @@ HTML_FORMULA_FORMAT = png
|
|||||||
|
|
||||||
FORMULA_FONTSIZE = 10
|
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
|
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
|
||||||
# to create new LaTeX commands to be used in formulas as building blocks. See
|
# to create new LaTeX commands to be used in formulas as building blocks. See
|
||||||
# the section "Including formulas" for details.
|
# the section "Including formulas" for details.
|
||||||
@@ -1737,8 +1806,8 @@ MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2
|
|||||||
|
|
||||||
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
|
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
|
||||||
# extension names that should be enabled during MathJax rendering. For example
|
# 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
|
# for MathJax version 2 (see
|
||||||
# #tex-and-latex-extensions):
|
# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
|
||||||
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
|
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
|
||||||
# For example for MathJax version 3 (see
|
# For example for MathJax version 3 (see
|
||||||
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
|
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
|
||||||
@@ -1989,9 +2058,16 @@ PDF_HYPERLINKS = YES
|
|||||||
|
|
||||||
USE_PDFLATEX = YES
|
USE_PDFLATEX = YES
|
||||||
|
|
||||||
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
|
# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error.
|
||||||
# command to the generated LaTeX files. This will instruct LaTeX to keep running
|
# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch
|
||||||
# if errors occur, instead of asking the user for help.
|
# 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.
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||||
|
|
||||||
@@ -2012,14 +2088,6 @@ LATEX_HIDE_INDICES = NO
|
|||||||
|
|
||||||
LATEX_BIB_STYLE = plain
|
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)
|
# 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,
|
# 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
|
# it will be relative to the LATEX_OUTPUT directory. If left blank the
|
||||||
@@ -2185,13 +2253,39 @@ DOCBOOK_OUTPUT = docbook
|
|||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
|
# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
|
||||||
# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
|
# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures
|
||||||
# the structure of the code including all documentation. Note that this feature
|
# the structure of the code including all documentation. Note that this feature
|
||||||
# is still experimental and incomplete at the moment.
|
# is still experimental and incomplete at the moment.
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
GENERATE_AUTOGEN_DEF = 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
|
# Configuration options related to the Perl module output
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
@@ -2334,15 +2428,15 @@ TAGFILES =
|
|||||||
|
|
||||||
GENERATE_TAGFILE =
|
GENERATE_TAGFILE =
|
||||||
|
|
||||||
# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
|
# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces
|
||||||
# the class index. If set to NO, only the inherited external classes will be
|
# will be listed in the class and namespace index. If set to NO, only the
|
||||||
# listed.
|
# inherited external classes will be listed.
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
ALLEXTERNALS = NO
|
ALLEXTERNALS = NO
|
||||||
|
|
||||||
# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
|
# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
|
||||||
# in the modules index. If set to NO, only the current project's groups will be
|
# in the topic index. If set to NO, only the current project's groups will be
|
||||||
# listed.
|
# listed.
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
|
|
||||||
@@ -2356,16 +2450,9 @@ EXTERNAL_GROUPS = YES
|
|||||||
EXTERNAL_PAGES = YES
|
EXTERNAL_PAGES = YES
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
# Configuration options related to the dot tool
|
# Configuration options related to diagram generator tools
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
# 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
|
# 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.
|
# and usage relations if the target is undocumented or is not a class.
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
@@ -2374,10 +2461,10 @@ HIDE_UNDOC_RELATIONS = YES
|
|||||||
|
|
||||||
# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
|
# 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:
|
# available from the path. This tool is part of Graphviz (see:
|
||||||
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
|
# https://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
|
# Bell Labs. The other options in this section have no effect if this option is
|
||||||
# set to NO
|
# set to NO
|
||||||
# The default value is: NO.
|
# The default value is: YES.
|
||||||
|
|
||||||
HAVE_DOT = YES
|
HAVE_DOT = YES
|
||||||
|
|
||||||
@@ -2391,37 +2478,51 @@ HAVE_DOT = YES
|
|||||||
|
|
||||||
DOT_NUM_THREADS = 0
|
DOT_NUM_THREADS = 0
|
||||||
|
|
||||||
# When you want a differently looking font in the dot files that doxygen
|
# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of
|
||||||
# generates you can specify the font name using DOT_FONTNAME. You need to make
|
# subgraphs. When you want a differently looking font in the dot files that
|
||||||
# sure dot is able to find the font, which can be done by putting it in a
|
# doxygen generates you can specify fontname, fontcolor and fontsize attributes.
|
||||||
# standard location or by setting the DOTFONTPATH environment variable or by
|
# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node,
|
||||||
# setting DOT_FONTPATH to the directory containing the font.
|
# Edge and Graph Attributes specification</a> You need to make sure dot is able
|
||||||
# The default value is: Helvetica.
|
# 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.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
#DOT_FONTNAME = Helvetica
|
DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10"
|
||||||
|
|
||||||
# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
|
# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can
|
||||||
# dot graphs.
|
# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a
|
||||||
# Minimum value: 4, maximum value: 24, default value: 10.
|
# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about
|
||||||
|
# arrows shapes.</a>
|
||||||
|
# The default value is: labelfontname=Helvetica,labelfontsize=10.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
#DOT_FONTSIZE = 10
|
DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10"
|
||||||
|
|
||||||
# By default doxygen will tell dot to use the default font as specified with
|
# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes
|
||||||
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
|
# around nodes set 'shape=plain' or 'shape=plaintext' <a
|
||||||
# the path where dot can find it using this tag.
|
# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a>
|
||||||
|
# The default value is: shape=box,height=0.2,width=0.4.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
#DOT_FONTPATH =
|
DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
|
||||||
|
|
||||||
# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
|
# You can set the path where dot can find font specified with fontname in
|
||||||
# graph for each documented class showing the direct and indirect inheritance
|
# DOT_COMMON_ATTR and others dot attributes.
|
||||||
# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
# 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 /
|
DOT_FONTPATH =
|
||||||
# links.
|
|
||||||
# Possible values are: NO, YES, TEXT and GRAPH.
|
# 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.
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
|
|
||||||
CLASS_GRAPH = YES
|
CLASS_GRAPH = YES
|
||||||
@@ -2429,15 +2530,21 @@ CLASS_GRAPH = YES
|
|||||||
# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
|
# 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
|
# graph for each documented class showing the direct and indirect implementation
|
||||||
# dependencies (inheritance, containment, and class references variables) of the
|
# dependencies (inheritance, containment, and class references variables) of the
|
||||||
# class with other documented classes.
|
# 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.
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
COLLABORATION_GRAPH = YES
|
COLLABORATION_GRAPH = YES
|
||||||
|
|
||||||
# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
|
# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
|
||||||
# groups, showing the direct groups dependencies. See also the chapter Grouping
|
# groups, showing the direct groups dependencies. Explicit enabling a group
|
||||||
# in the manual.
|
# 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.
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
@@ -2497,7 +2604,9 @@ TEMPLATE_RELATIONS = NO
|
|||||||
# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
|
# 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
|
# YES then doxygen will generate a graph for each documented file showing the
|
||||||
# direct and indirect include dependencies of the file with other documented
|
# direct and indirect include dependencies of the file with other documented
|
||||||
# files.
|
# 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.
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
@@ -2506,7 +2615,10 @@ INCLUDE_GRAPH = YES
|
|||||||
# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
|
# 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
|
# 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
|
# the direct and indirect include dependencies of the file with other documented
|
||||||
# files.
|
# 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.
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
@@ -2546,7 +2658,10 @@ GRAPHICAL_HIERARCHY = YES
|
|||||||
# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
|
# 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
|
# dependencies a directory has on other directories in a graphical way. The
|
||||||
# dependency relations are determined by the #include relations between the
|
# dependency relations are determined by the #include relations between the
|
||||||
# files in the directories.
|
# 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.
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
@@ -2562,12 +2677,13 @@ DIR_GRAPH_MAX_DEPTH = 1
|
|||||||
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
|
# 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
|
# generated by dot. For an explanation of the image formats see the section
|
||||||
# output formats in the documentation of the dot tool (Graphviz (see:
|
# output formats in the documentation of the dot tool (Graphviz (see:
|
||||||
# http://www.graphviz.org/)).
|
# https://www.graphviz.org/)).
|
||||||
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
|
# 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
|
# to make the SVG files visible in IE 9+ (other browsers do not have this
|
||||||
# requirement).
|
# requirement).
|
||||||
# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
|
# Possible values are: png, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd,
|
||||||
# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
|
# 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
|
||||||
# png:gdiplus:gdiplus.
|
# png:gdiplus:gdiplus.
|
||||||
# The default value is: png.
|
# The default value is: png.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
@@ -2599,11 +2715,12 @@ DOT_PATH =
|
|||||||
|
|
||||||
DOTFILE_DIRS =
|
DOTFILE_DIRS =
|
||||||
|
|
||||||
# The MSCFILE_DIRS tag can be used to specify one or more directories that
|
# You can include diagrams made with dia in doxygen documentation. Doxygen will
|
||||||
# contain msc files that are included in the documentation (see the \mscfile
|
# then run dia to produce the diagram and insert it in the documentation. The
|
||||||
# command).
|
# 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.
|
||||||
|
|
||||||
MSCFILE_DIRS =
|
DIA_PATH =
|
||||||
|
|
||||||
# The DIAFILE_DIRS tag can be used to specify one or more directories that
|
# 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
|
# contain dia files that are included in the documentation (see the \diafile
|
||||||
@@ -2653,18 +2770,6 @@ DOT_GRAPH_MAX_NODES = 50
|
|||||||
|
|
||||||
MAX_DOT_GRAPH_DEPTH = 0
|
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
|
# 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
|
# 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
|
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
|
||||||
@@ -2692,3 +2797,19 @@ GENERATE_LEGEND = YES
|
|||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
|
|
||||||
DOT_CLEANUP = 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 =
|
||||||
|
|||||||
268
GNUmakefile
268
GNUmakefile
@@ -16,16 +16,17 @@ MAKEFLAGS += --no-builtin-rules
|
|||||||
## LD := Linker.
|
## LD := Linker.
|
||||||
## ANDROID_SDK := Path to the Android SDK.
|
## ANDROID_SDK := Path to the Android SDK.
|
||||||
|
|
||||||
VERSION_CODE := 49
|
VERSION_CODE := 33
|
||||||
VERSION_NUMBER := 0.2025.12
|
VERSION_CODE_IOS := 8
|
||||||
|
VERSION_NUMBER := 0.0.28-wip
|
||||||
VERSION_NAME := This program kills fascists.
|
VERSION_NAME := This program kills fascists.
|
||||||
|
|
||||||
IPHONEOS_VERSION_MIN=14.5
|
IPHONEOS_VERSION_MIN=14.0
|
||||||
|
|
||||||
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3510100.zip
|
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3490100.zip
|
||||||
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.18.3/bundletool-all-1.18.3.jar
|
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar
|
||||||
APPIMAGETOOL_URL := https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-x86_64.AppImage
|
APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||||
APPIMAGETOOL_MD5 := 43264887ffe43cdc02171b3463912168 out/appimagetool
|
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
|
||||||
|
|
||||||
PROJECT = tildefriends
|
PROJECT = tildefriends
|
||||||
BUILD_DIR ?= out
|
BUILD_DIR ?= out
|
||||||
@@ -37,16 +38,13 @@ BUNDLETOOL = out/bundletool.jar
|
|||||||
|
|
||||||
HAVE_WIN :=
|
HAVE_WIN :=
|
||||||
HAVE_CROSS_AARCH64 :=
|
HAVE_CROSS_AARCH64 :=
|
||||||
|
USE_SYSTEM_SSL :=
|
||||||
|
|
||||||
export SOURCE_DATE_EPOCH=1
|
export SOURCE_DATE_EPOCH=1
|
||||||
export TZ=UTC
|
export TZ=UTC
|
||||||
|
|
||||||
ifeq ($(UNAME_S),Darwin)
|
ifeq ($(UNAME_S),Darwin)
|
||||||
BUILD_TYPES := debug release iosdebug iosrelease iossimdebug iossimrelease
|
BUILD_TYPES := debug release iosdebug iosrelease iossimdebug iossimrelease
|
||||||
HAVE_ANDROID = 0
|
|
||||||
HAVE_LINUX_IOS = 0
|
|
||||||
HAVE_LINUX_MACOS = 0
|
|
||||||
HAVE_WIN = 0
|
|
||||||
else ifeq ($(UNAME_S),Linux)
|
else ifeq ($(UNAME_S),Linux)
|
||||||
BUILD_TYPES := debug release
|
BUILD_TYPES := debug release
|
||||||
HAVE_ANDROID = $(if $(shell which $(ANDROID_SDK)/platform-tools/adb),1)
|
HAVE_ANDROID = $(if $(shell which $(ANDROID_SDK)/platform-tools/adb),1)
|
||||||
@@ -63,10 +61,7 @@ LDFLAGS += \
|
|||||||
-lbsd \
|
-lbsd \
|
||||||
-lnetwork \
|
-lnetwork \
|
||||||
-Wno-stringop-overflow
|
-Wno-stringop-overflow
|
||||||
HAVE_ANDROID = 0
|
USE_SYSTEM_SSL := 1
|
||||||
HAVE_LINUX_IOS = 0
|
|
||||||
HAVE_LINUX_MACOS = 0
|
|
||||||
HAVE_WIN = 0
|
|
||||||
else ifeq ($(UNAME_S),OpenBSD)
|
else ifeq ($(UNAME_S),OpenBSD)
|
||||||
BUILD_TYPES := debug release
|
BUILD_TYPES := debug release
|
||||||
CFLAGS += \
|
CFLAGS += \
|
||||||
@@ -77,12 +72,13 @@ LDFLAGS += \
|
|||||||
HAVE_ANDROID :=
|
HAVE_ANDROID :=
|
||||||
HAVE_LINUX_IOS :=
|
HAVE_LINUX_IOS :=
|
||||||
HAVE_LINUX_MACOS :=
|
HAVE_LINUX_MACOS :=
|
||||||
|
USE_SYSTEM_SSL := 1
|
||||||
else
|
else
|
||||||
$(error Unexpected host platform $(UNAME_S).)
|
$(error Unexpected host platform $(UNAME_S).)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Everything is set above.
|
# 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))
|
$(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))
|
||||||
|
|
||||||
CFLAGS += \
|
CFLAGS += \
|
||||||
-std=gnu11 \
|
-std=gnu11 \
|
||||||
@@ -102,10 +98,10 @@ LDFLAGS += \
|
|||||||
-Wno-aggressive-loop-optimizations
|
-Wno-aggressive-loop-optimizations
|
||||||
|
|
||||||
ANDROID_MIN_SDK_VERSION := 24
|
ANDROID_MIN_SDK_VERSION := 24
|
||||||
ANDROID_TARGET_SDK_VERSION := 35
|
ANDROID_TARGET_SDK_VERSION := 34
|
||||||
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/35.0.0
|
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0
|
||||||
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-$(ANDROID_TARGET_SDK_VERSION)
|
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-$(ANDROID_TARGET_SDK_VERSION)
|
||||||
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/27.2.12479018
|
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.3.11579264
|
||||||
|
|
||||||
ANDROID_ARMV7A_TARGETS := \
|
ANDROID_ARMV7A_TARGETS := \
|
||||||
out/androiddebug-armv7a/tildefriends \
|
out/androiddebug-armv7a/tildefriends \
|
||||||
@@ -249,10 +245,7 @@ $(ANDROID_TARGETS): CFLAGS += \
|
|||||||
-fno-asynchronous-unwind-tables \
|
-fno-asynchronous-unwind-tables \
|
||||||
-funwind-tables \
|
-funwind-tables \
|
||||||
-Wno-unknown-warning-option
|
-Wno-unknown-warning-option
|
||||||
$(ANDROID_TARGETS): LDFLAGS += \
|
$(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC
|
||||||
--sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
|
|
||||||
-Wl,-z,max-page-size=16384 \
|
|
||||||
-fPIC
|
|
||||||
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
|
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
|
||||||
$(DEBUG_TARGETS): LDFLAGS += -Og
|
$(DEBUG_TARGETS): LDFLAGS += -Og
|
||||||
$(RELEASE_TARGETS): CFLAGS += \
|
$(RELEASE_TARGETS): CFLAGS += \
|
||||||
@@ -266,12 +259,16 @@ $(WINDOWS_TARGETS): AS = $(CC)
|
|||||||
$(WINDOWS_TARGETS): CFLAGS += \
|
$(WINDOWS_TARGETS): CFLAGS += \
|
||||||
-D_WIN32_WINNT=0x0A00 \
|
-D_WIN32_WINNT=0x0A00 \
|
||||||
-DWINVER=0x0A00 \
|
-DWINVER=0x0A00 \
|
||||||
-DNTDDI_VERSION=NTDDI_WIN10
|
-DNTDDI_VERSION=NTDDI_WIN10 \
|
||||||
|
-Iout/openssl/$(UNAME_S)/mingw64/usr/local/include
|
||||||
$(WINDOWS_TARGETS): LDFLAGS += \
|
$(WINDOWS_TARGETS): LDFLAGS += \
|
||||||
-static \
|
-static \
|
||||||
-lm
|
-lm \
|
||||||
|
-Lout/openssl/$(UNAME_S)/mingw64/usr/local/lib
|
||||||
$(AARCH64_TARGETS): CC = aarch64-linux-gnu-gcc
|
$(AARCH64_TARGETS): CC = aarch64-linux-gnu-gcc
|
||||||
$(AARCH64_TARGETS): AS = $(CC)
|
$(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)
|
ifeq ($(UNAME_S),Darwin)
|
||||||
$(HOST_TARGETS): CC = xcrun clang
|
$(HOST_TARGETS): CC = xcrun clang
|
||||||
$(IOS_TARGETS): IOS_SYSROOT := $(shell xcrun --sdk iphoneos --show-sdk-path)
|
$(IOS_TARGETS): IOS_SYSROOT := $(shell xcrun --sdk iphoneos --show-sdk-path)
|
||||||
@@ -296,12 +293,39 @@ $(ANDROID_TARGETS): AS = $(CC)
|
|||||||
$(ANDROID_TARGETS): CFLAGS += \
|
$(ANDROID_TARGETS): CFLAGS += \
|
||||||
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \
|
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \
|
||||||
-Wno-unknown-warning-option
|
-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): 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
|
$(NONMACOS_TARGETS): CFLAGS += -Wno-cast-function-type
|
||||||
$(MACOS_TARGETS): LDFLAGS += -Wl,-dead_strip
|
$(MACOS_TARGETS): LDFLAGS += -Wl,-dead_strip
|
||||||
$(NONMACOS_TARGETS): LDFLAGS += -Wl,--gc-sections -Wl,--as-needed
|
$(NONMACOS_TARGETS): LDFLAGS += -Wl,--gc-sections -Wl,--as-needed
|
||||||
$(IOS_TARGETS): CFLAGS += -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)
|
$(IOS_TARGETS): CFLAGS += -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)
|
||||||
$(IOS_TARGETS): LDFLAGS += -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_M),x86_64)
|
||||||
ifeq ($(UNAME_S),Linux)
|
ifeq ($(UNAME_S),Linux)
|
||||||
@@ -582,16 +606,15 @@ $(UV_OBJS): CFLAGS += \
|
|||||||
-Ideps/libuv/include \
|
-Ideps/libuv/include \
|
||||||
-Ideps/libuv/src \
|
-Ideps/libuv/src \
|
||||||
-Wno-dangling-pointer \
|
-Wno-dangling-pointer \
|
||||||
-Wno-format-truncation \
|
|
||||||
-Wno-incompatible-pointer-types \
|
-Wno-incompatible-pointer-types \
|
||||||
-Wno-maybe-uninitialized \
|
-Wno-maybe-uninitialized \
|
||||||
-Wno-nonnull \
|
|
||||||
-Wno-sign-compare \
|
-Wno-sign-compare \
|
||||||
-Wno-unknown-attributes \
|
-Wno-unknown-attributes \
|
||||||
-Wno-unused-but-set-parameter \
|
-Wno-unused-but-set-parameter \
|
||||||
-Wno-unused-but-set-variable \
|
-Wno-unused-but-set-variable \
|
||||||
-Wno-unused-result \
|
-Wno-unused-result \
|
||||||
-Wno-unused-variable
|
-Wno-unused-variable \
|
||||||
|
-Wno-nonnull
|
||||||
$(filter out/win%,$(UV_OBJS)): \
|
$(filter out/win%,$(UV_OBJS)): \
|
||||||
CFLAGS += \
|
CFLAGS += \
|
||||||
-Wno-cast-function-type \
|
-Wno-cast-function-type \
|
||||||
@@ -618,7 +641,6 @@ SODIUM_SOURCES := \
|
|||||||
deps/libsodium/src/libsodium/crypto_core/hsalsa20/ref2/core_hsalsa20_ref2.c \
|
deps/libsodium/src/libsodium/crypto_core/hsalsa20/ref2/core_hsalsa20_ref2.c \
|
||||||
deps/libsodium/src/libsodium/crypto_core/salsa/ref/core_salsa_ref.c \
|
deps/libsodium/src/libsodium/crypto_core/salsa/ref/core_salsa_ref.c \
|
||||||
deps/libsodium/src/libsodium/crypto_core/softaes/softaes.c \
|
deps/libsodium/src/libsodium/crypto_core/softaes/softaes.c \
|
||||||
deps/libsodium/src/libsodium/crypto_generichash/crypto_generichash.c \
|
|
||||||
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-compress-ref.c \
|
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-compress-ref.c \
|
||||||
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c \
|
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c \
|
||||||
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/generichash_blake2b.c \
|
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/generichash_blake2b.c \
|
||||||
@@ -688,12 +710,12 @@ $(SQLITE_OBJS): CFLAGS += \
|
|||||||
-DSQLITE_MAX_COMPOUND_SELECT=300 \
|
-DSQLITE_MAX_COMPOUND_SELECT=300 \
|
||||||
-DSQLITE_MAX_EXPR_DEPTH=40 \
|
-DSQLITE_MAX_EXPR_DEPTH=40 \
|
||||||
-DSQLITE_MAX_FUNCTION_ARG=8 \
|
-DSQLITE_MAX_FUNCTION_ARG=8 \
|
||||||
-DSQLITE_MAX_LENGTH=10485760 \
|
-DSQLITE_MAX_LENGTH=5242880 \
|
||||||
-DSQLITE_MAX_LIKE_PATTERN_LENGTH=50 \
|
-DSQLITE_MAX_LIKE_PATTERN_LENGTH=50 \
|
||||||
-DSQLITE_MAX_SQL_LENGTH=100000 \
|
-DSQLITE_MAX_SQL_LENGTH=100000 \
|
||||||
-DSQLITE_MAX_TRIGGER_DEPTH=10 \
|
-DSQLITE_MAX_TRIGGER_DEPTH=10 \
|
||||||
-DSQLITE_MAX_VARIABLE_NUMBER=100 \
|
-DSQLITE_MAX_VARIABLE_NUMBER=100 \
|
||||||
-DSQLITE_MAX_VDBE_OP=50000 \
|
-DSQLITE_MAX_VDBE_OP=25000 \
|
||||||
-DSQLITE_OMIT_DEPRECATED \
|
-DSQLITE_OMIT_DEPRECATED \
|
||||||
-DSQLITE_OMIT_DESERIALIZE \
|
-DSQLITE_OMIT_DESERIALIZE \
|
||||||
-DSQLITE_OMIT_LOAD_EXTENSION \
|
-DSQLITE_OMIT_LOAD_EXTENSION \
|
||||||
@@ -712,7 +734,7 @@ $(SQLITE_OBJS): CFLAGS += \
|
|||||||
|
|
||||||
QUICKJS_SOURCES := \
|
QUICKJS_SOURCES := \
|
||||||
deps/quickjs/cutils.c \
|
deps/quickjs/cutils.c \
|
||||||
deps/quickjs/dtoa.c \
|
deps/quickjs/libbf.c \
|
||||||
deps/quickjs/libregexp.c \
|
deps/quickjs/libregexp.c \
|
||||||
deps/quickjs/libunicode.c \
|
deps/quickjs/libunicode.c \
|
||||||
deps/quickjs/quickjs.c
|
deps/quickjs/quickjs.c
|
||||||
@@ -789,6 +811,9 @@ $(MINIUNZIP_OBJS): CFLAGS += \
|
|||||||
LDFLAGS += \
|
LDFLAGS += \
|
||||||
-pthread \
|
-pthread \
|
||||||
-lm
|
-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),Haiku)
|
||||||
ifneq ($(UNAME_S),OpenBSD)
|
ifneq ($(UNAME_S),OpenBSD)
|
||||||
$(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
$(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
||||||
@@ -796,6 +821,8 @@ $(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
$(WINDOWS_TARGETS): LDFLAGS += \
|
$(WINDOWS_TARGETS): LDFLAGS += \
|
||||||
|
-lssl \
|
||||||
|
-lcrypto \
|
||||||
-lcrypt32 \
|
-lcrypt32 \
|
||||||
-ldbghelp \
|
-ldbghelp \
|
||||||
-liphlpapi \
|
-liphlpapi \
|
||||||
@@ -808,15 +835,15 @@ $(WINDOWS_TARGETS): LDFLAGS += \
|
|||||||
$(ANDROID_TARGETS): LDFLAGS += \
|
$(ANDROID_TARGETS): LDFLAGS += \
|
||||||
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \
|
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \
|
||||||
-ldl \
|
-ldl \
|
||||||
-llog
|
-llog \
|
||||||
|
-lssl \
|
||||||
|
-lcrypto
|
||||||
$(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): CFLAGS += \
|
$(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): CFLAGS += \
|
||||||
-Wno-unknown-warning-option
|
-Wno-unknown-warning-option
|
||||||
$(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
$(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
||||||
-framework Foundation \
|
-framework Foundation \
|
||||||
-framework CoreFoundation \
|
-framework CoreFoundation \
|
||||||
-framework CoreSpotlight \
|
|
||||||
-framework UIKit \
|
-framework UIKit \
|
||||||
-framework UniformTypeIdentifiers \
|
|
||||||
-framework WebKit
|
-framework WebKit
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -892,7 +919,7 @@ src/ios/Info.plist : $(firstword $(MAKEFILE_LIST))
|
|||||||
tr '\n' '^' | \
|
tr '\n' '^' | \
|
||||||
sed -r \
|
sed -r \
|
||||||
-e 's@(<key>CFBundleShortVersionString</key>\^[[:space:]]*<string>)[0-9.]*(</string>)@\1$(VERSION_NUMBER:%-wip=%)\2@' \
|
-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)\2@' \
|
-e 's@(<key>CFBundleVersion</key>\^[[:space:]]*<string>)[[:digit:]]+(</string>)@\1$(VERSION_CODE_IOS)\2@' \
|
||||||
-e 's@(<key>MinimumOSVersion</key>\^[[:space:]]*<string>)[0-9.]*(</string>)@\1$(IPHONEOS_VERSION_MIN)\2@' | \
|
-e 's@(<key>MinimumOSVersion</key>\^[[:space:]]*<string>)[0-9.]*(</string>)@\1$(IPHONEOS_VERSION_MIN)\2@' | \
|
||||||
tr '^' '\n' > \
|
tr '^' '\n' > \
|
||||||
$@.tmp && mv $@.tmp $@ || rm -f $@.tmp
|
$@.tmp && mv $@.tmp $@ || rm -f $@.tmp
|
||||||
@@ -979,7 +1006,7 @@ $(BUNDLETOOL):
|
|||||||
@curl -q -L --create-dirs -o $@ $(BUNDLETOOL_URL)
|
@curl -q -L --create-dirs -o $@ $(BUNDLETOOL_URL)
|
||||||
|
|
||||||
out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGETS)) $(RAW_FILES) out/apk/res.apk src/android/AndroidManifest.xml $(BUNDLETOOL)
|
out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGETS)) $(RAW_FILES) out/apk/res.apk src/android/AndroidManifest.xml $(BUNDLETOOL)
|
||||||
@rm -rf out/aab/staging/ out/aab/base.zip
|
@rm -rf out/aab/staging/
|
||||||
@mkdir -p out/aab/staging
|
@mkdir -p out/aab/staging
|
||||||
@$(ANDROID_BUILD_TOOLS)/aapt2 link --proto-format -o out/aab/temporary.apk \
|
@$(ANDROID_BUILD_TOOLS)/aapt2 link --proto-format -o out/aab/temporary.apk \
|
||||||
-I $(ANDROID_PLATFORM)/android.jar \
|
-I $(ANDROID_PLATFORM)/android.jar \
|
||||||
@@ -999,11 +1026,14 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
|
|||||||
@cp out/apk/classes.dex out/aab/staging/dex/
|
@cp out/apk/classes.dex out/aab/staging/dex/
|
||||||
@rm -fv out/base.zip
|
@rm -fv out/base.zip
|
||||||
@mkdir -p out/aab/staging/lib/arm64-v8a out/aab/staging/lib/armeabi-v7a out/aab/staging/lib/x86_64 out/aab/staging/lib/x86
|
@mkdir -p out/aab/staging/lib/arm64-v8a out/aab/staging/lib/armeabi-v7a out/aab/staging/lib/x86_64 out/aab/staging/lib/x86
|
||||||
@mkdir -p out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64 out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86
|
@cp out/androidrelease/tildefriends out/aab/staging/lib/arm64-v8a/libtildefriends.so
|
||||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease/tildefriends -o out/aab/staging/lib/arm64-v8a/libtildefriends.so
|
@cp out/androidrelease-armv7a/tildefriends out/aab/staging/lib/armeabi-v7a/libtildefriends.so
|
||||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-armv7a/tildefriends -o out/aab/staging/lib/armeabi-v7a/libtildefriends.so
|
@cp out/androidrelease-x86_64/tildefriends out/aab/staging/lib/x86_64/libtildefriends.so
|
||||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86_64/tildefriends -o out/aab/staging/lib/x86_64/libtildefriends.so
|
@cp out/androidrelease-x86/tildefriends out/aab/staging/lib/x86/libtildefriends.so
|
||||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86/tildefriends -o out/aab/staging/lib/x86/libtildefriends.so
|
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/arm64-v8a/libtildefriends.so
|
||||||
|
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/armeabi-v7a/libtildefriends.so
|
||||||
|
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86_64/libtildefriends.so
|
||||||
|
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86/libtildefriends.so
|
||||||
@cp -r apps/ out/aab/staging/root/
|
@cp -r apps/ out/aab/staging/root/
|
||||||
@rm -rf out/aab/staging/root/apps/welcome*
|
@rm -rf out/aab/staging/root/apps/welcome*
|
||||||
@cp -r core/ out/aab/staging/root/
|
@cp -r core/ out/aab/staging/root/
|
||||||
@@ -1012,12 +1042,7 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
|
|||||||
@cp -r deps/codemirror/ out/aab/staging/root/deps/
|
@cp -r deps/codemirror/ out/aab/staging/root/deps/
|
||||||
@cd out/aab/staging/; zip -r ../base.zip *; cd ../../../
|
@cd out/aab/staging/; zip -r ../base.zip *; cd ../../../
|
||||||
@java -jar $(BUNDLETOOL) build-bundle --overwrite --config=src/android/BundleConfig.json --modules=out/aab/base.zip --output=$@
|
@java -jar $(BUNDLETOOL) build-bundle --overwrite --config=src/android/BundleConfig.json --modules=out/aab/base.zip --output=$@
|
||||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym
|
@$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) $@
|
||||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-armv7a/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym
|
|
||||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86_64/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym
|
|
||||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym
|
|
||||||
@cd out/aab/staging; zip -u ../../../$@ BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym; cd ../../../
|
|
||||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) --alignment-preserved $@
|
|
||||||
|
|
||||||
aab: out/TildeFriends.aab ## Build an Android App Bundle.
|
aab: out/TildeFriends.aab ## Build an Android App Bundle.
|
||||||
.PHONY: aab
|
.PHONY: aab
|
||||||
@@ -1079,12 +1104,12 @@ out/apk/TildeFriends-%.fdroid.unsigned.apk:
|
|||||||
|
|
||||||
out/%.apk: out/apk/%.unsigned.apk
|
out/%.apk: out/apk/%.unsigned.apk
|
||||||
@echo "[apksigner] $(notdir $@)"
|
@echo "[apksigner] $(notdir $@)"
|
||||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ --alignment-preserved $<
|
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $<
|
||||||
|
|
||||||
out/%.zopfli.apk: out/%.apk
|
out/%.zopfli.apk: out/%.apk
|
||||||
@echo "[zopfli] $(notdir $@)"
|
@echo "[zopfli] $(notdir $@)"
|
||||||
$(ANDROID_BUILD_TOOLS)/zipalign -f -z 4 $< $@.zopfli
|
$(ANDROID_BUILD_TOOLS)/zipalign -f -z 4 $< $@.zopfli
|
||||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ --alignment-preserved $@.zopfli
|
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $@.zopfli
|
||||||
|
|
||||||
release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-release.zopfli.apk ## Build an Android release APK.
|
release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-release.zopfli.apk ## Build an Android release APK.
|
||||||
.PHONY: release-apk
|
.PHONY: release-apk
|
||||||
@@ -1102,11 +1127,6 @@ releaseapkgo: out/TildeFriends-arm-release.apk ## Build, install, and run a rele
|
|||||||
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
|
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
|
||||||
.PHONY: releaseapkgo
|
.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.
|
apklog: ## Display Android log output.
|
||||||
@adb logcat *:S tildefriends
|
@adb logcat *:S tildefriends
|
||||||
.PHONY: apklog
|
.PHONY: apklog
|
||||||
@@ -1133,13 +1153,7 @@ out/zsign_build/zsign: $(wildcard deps/zsign/*.cpp deps/zsign/*.h deps/zsign/*.t
|
|||||||
@cmake -B out/zsign_build deps/zsign
|
@cmake -B out/zsign_build deps/zsign
|
||||||
@cmake --build out/zsign_build -- COLOR=0 VERBOSE=0 MAKESILENT=-s
|
@cmake --build out/zsign_build -- COLOR=0 VERBOSE=0 MAKESILENT=-s
|
||||||
|
|
||||||
ifeq ($(HAVE_LINUX_IOS),1)
|
out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/data.zip $(if $(HAVE_LINUX_IOS),out/zsign_build/zsign)
|
||||||
ZSIGN_DEP = out/zsign_build/zsign
|
|
||||||
else
|
|
||||||
ZSIGN_DEP =
|
|
||||||
endif
|
|
||||||
|
|
||||||
out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/data.zip $(ZSIGN_DEP)
|
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
@cp -v $(filter-out out/zsign%,$<) $@
|
@cp -v $(filter-out out/zsign%,$<) $@
|
||||||
@cp -v out/data.zip $(@D)/
|
@cp -v out/data.zip $(@D)/
|
||||||
@@ -1152,7 +1166,6 @@ out/tildefriends-%.ipa: out/tildefriends-ios%.app/tildefriends
|
|||||||
@echo "[ipa] $@"
|
@echo "[ipa] $@"
|
||||||
@rm -rf $@.tmp $@
|
@rm -rf $@.tmp $@
|
||||||
@mkdir -p $@.tmp/Payload/tildefriends.app/
|
@mkdir -p $@.tmp/Payload/tildefriends.app/
|
||||||
@cp src/ios/tildefriends512.png $@.tmp/iTunesArtwork
|
|
||||||
@cp -R $(dir $<)/* $@.tmp/Payload/tildefriends.app/
|
@cp -R $(dir $<)/* $@.tmp/Payload/tildefriends.app/
|
||||||
@cd $@.tmp/ && zip -u ../../$@ -q -9 -r ./
|
@cd $@.tmp/ && zip -u ../../$@ -q -9 -r ./
|
||||||
@rm -rf $@.tmp/
|
@rm -rf $@.tmp/
|
||||||
@@ -1180,11 +1193,98 @@ ios%go: out/tildefriends-ios%.app/tildefriends
|
|||||||
ideviceinstaller -i $(realpath $(dir $<))
|
ideviceinstaller -i $(realpath $(dir $<))
|
||||||
|
|
||||||
iossimdebuggo: out/tildefriends-iossimdebug.app/tildefriends ## Build, install, and run an iOS debug build.
|
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 install booted out/tildefriends-iossimdebug.app/
|
||||||
xcrun simctl launch --console booted com.unprompted.tildefriends
|
xcrun simctl launch booted com.unprompted.tildefriends
|
||||||
.PHONY: iossimdebuggo
|
.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
|
out/macos%/tildefriends: out/macos%-arm/tildefriends out/macos%-x86_64/tildefriends
|
||||||
@echo [lipo] $@
|
@echo [lipo] $@
|
||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
@@ -1213,26 +1313,7 @@ out/tildefriends-x86_64.AppImage: out/release/tildefriends out/data.zip
|
|||||||
@cd out; ./appimagetool --appimage-extract; cd ..
|
@cd out; ./appimagetool --appimage-extract; cd ..
|
||||||
@cd out; unset SOURCE_DATE_EPOCH; PATH=$$PATH:squashfs-root/usr/bin ARCH=x86_64 squashfs-root/usr/bin/appimagetool -u 'zsync|https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage.zsync' tildefriends.AppDir tildefriends-x86_64.AppImage; cd ..
|
@cd out; unset SOURCE_DATE_EPOCH; PATH=$$PATH:squashfs-root/usr/bin ARCH=x86_64 squashfs-root/usr/bin/appimagetool -u 'zsync|https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage.zsync' tildefriends.AppDir tildefriends-x86_64.AppImage; cd ..
|
||||||
|
|
||||||
out/tildefriends-aarch64.AppImage: out/armrelease/tildefriends out/data.zip
|
appimage: out/tildefriends-x86_64.AppImage ## Build an AppImage.
|
||||||
@echo "[appimage] $$@"
|
|
||||||
@rm -rf out/tildefriends_aarch64.AppDir
|
|
||||||
@mkdir -p out/tildefriends_aarch64.AppDir/usr/bin
|
|
||||||
@mkdir -p out/tildefriends_aarch64.AppDir/usr/share/applications
|
|
||||||
@mkdir -p out/tildefriends_aarch64.AppDir/usr/share/icons/hicolor/scalable/apps
|
|
||||||
@mkdir -p out/tildefriends_aarch64.AppDir/usr/share/tildefriends
|
|
||||||
@echo $(APPIMAGETOOL_MD5) > out/appimagetool.md5
|
|
||||||
@test -x out/appimagetool || curl -q -L -o out/appimagetool $(APPIMAGETOOL_URL) && md5sum -c out/appimagetool.md5 && chmod +x out/appimagetool
|
|
||||||
@echo "[Desktop Entry]\nName=tildefriends\nExec=/usr/bin/tildefriends\nIcon=/usr/share/icons/hicolor/scalable/apps/tildefriends\nType=Application\nCategories=Network" > out/tildefriends_aarch64.AppDir/tildefriends.desktop
|
|
||||||
@cp src/ios/tildefriends.svg out/tildefriends_aarch64.AppDir/usr/share/icons/hicolor/scalable/apps/
|
|
||||||
@cp src/ios/tildefriends.svg out/tildefriends_aarch64.AppDir/
|
|
||||||
@cp out/armrelease/tildefriends out/tildefriends_aarch64.AppDir/usr/bin/
|
|
||||||
@cp out/data.zip out/tildefriends_aarch64.AppDir/usr/share/tildefriends/data.zip
|
|
||||||
@echo "#!/bin/sh\n\$${APPDIR}/usr/bin/tildefriends run -z \$$APPDIR/usr/share/tildefriends/data.zip" > out/tildefriends_aarch64.AppDir/AppRun
|
|
||||||
@chmod +x out/tildefriends_aarch64.AppDir/AppRun
|
|
||||||
@cd out; ./appimagetool --appimage-extract; cd ..
|
|
||||||
@cd out; unset SOURCE_DATE_EPOCH; PATH=$$PATH:squashfs-root/usr/bin ARCH=arm_aarch64 squashfs-root/usr/bin/appimagetool -u 'zsync|https://dev.tildefriends.net/releases/tildefriends-aarch64.AppImage.zsync' tildefriends_aarch64.AppDir tildefriends-aarch64.AppImage; cd ..
|
|
||||||
|
|
||||||
appimage: out/tildefriends-x86_64.AppImage out/tildefriends-aarch64.AppImage ## Build AppImages.
|
|
||||||
.PHONY: appimage
|
.PHONY: appimage
|
||||||
|
|
||||||
flatpak: out/ ## Build a flatpak.
|
flatpak: out/ ## Build a flatpak.
|
||||||
@@ -1277,6 +1358,7 @@ tarball: ## Build an all-inclusive source tarball (.tar.xz).
|
|||||||
--exclude=deps/libsodium/test \
|
--exclude=deps/libsodium/test \
|
||||||
--exclude=deps/libuv/docs \
|
--exclude=deps/libuv/docs \
|
||||||
--exclude=deps/libuv/test \
|
--exclude=deps/libuv/test \
|
||||||
|
--exclude=deps/speedscope/*.map \
|
||||||
--exclude=deps/sqlite/shell.c \
|
--exclude=deps/sqlite/shell.c \
|
||||||
--exclude=deps/zlib/contrib/vstudio \
|
--exclude=deps/zlib/contrib/vstudio \
|
||||||
--exclude=deps/zlib/doc \
|
--exclude=deps/zlib/doc \
|
||||||
@@ -1319,8 +1401,6 @@ dist:
|
|||||||
@cp out/TildeFriends-release.fdroid.apk dist/TildeFriends-$(VERSION_NUMBER).fdroid.apk
|
@cp out/TildeFriends-release.fdroid.apk dist/TildeFriends-$(VERSION_NUMBER).fdroid.apk
|
||||||
@echo "[cp] TildeFriends-x86_64-$(VERSION_NUMBER).AppImage"
|
@echo "[cp] TildeFriends-x86_64-$(VERSION_NUMBER).AppImage"
|
||||||
@cp out/tildefriends-x86_64.AppImage dist/TildeFriends-x86_64-$(VERSION_NUMBER).AppImage
|
@cp out/tildefriends-x86_64.AppImage dist/TildeFriends-x86_64-$(VERSION_NUMBER).AppImage
|
||||||
@echo "[cp] TildeFriends-aarch64-$(VERSION_NUMBER).AppImage"
|
|
||||||
@cp out/tildefriends-aarch64.AppImage dist/TildeFriends-aarch64-$(VERSION_NUMBER).AppImage
|
|
||||||
@echo "[cp] tildefriends-linux-$(UNAME_M)-$(VERSION_NUMBER)"
|
@echo "[cp] tildefriends-linux-$(UNAME_M)-$(VERSION_NUMBER)"
|
||||||
@cp out/release/tildefriends.standalone dist/tildefriends-linux-$(UNAME_M)-$(VERSION_NUMBER)
|
@cp out/release/tildefriends.standalone dist/tildefriends-linux-$(UNAME_M)-$(VERSION_NUMBER)
|
||||||
@test $(HAVE_CROSS_AARCH64) && echo "[cp] tildefriends-linux-aarch64-$(VERSION_NUMBER)"
|
@test $(HAVE_CROSS_AARCH64) && echo "[cp] tildefriends-linux-aarch64-$(VERSION_NUMBER)"
|
||||||
@@ -1339,7 +1419,7 @@ dist-ios: iosrelease-app
|
|||||||
mkdir -p out/Payload/tildefriends.app
|
mkdir -p out/Payload/tildefriends.app
|
||||||
cp -avR out/tildefriends-iosrelease.app/* out/Payload/tildefriends.app/
|
cp -avR out/tildefriends-iosrelease.app/* out/Payload/tildefriends.app/
|
||||||
cp src/ios/tildefriends.png out/Payload/tildefriends.app/
|
cp src/ios/tildefriends.png out/Payload/tildefriends.app/
|
||||||
xcrun -sdk iphoneos actool --compile out/Payload/tildefriends.app/ --platform iphoneos --minimum-deployment-target $(IPHONEOS_VERSION_MIN) --app-icon AppIcon src/ios/icons/Assets.xcassets src/ios/icons/*.png --output-partial-info-plist out/actool.plist
|
cp src/ios/icons/Assets.car out/Payload/tildefriends.app/
|
||||||
cp src/ios/distribution.mobileprovision out/Payload/tildefriends.app/embedded.mobileprovision
|
cp src/ios/distribution.mobileprovision out/Payload/tildefriends.app/embedded.mobileprovision
|
||||||
xcrun -sdk iphoneos codesign -f -s 'Apple Distribution' --entitlements src/ios/Entitlements.plist --generate-entitlement-der out/Payload/tildefriends.app
|
xcrun -sdk iphoneos codesign -f -s 'Apple Distribution' --entitlements src/ios/Entitlements.plist --generate-entitlement-der out/Payload/tildefriends.app
|
||||||
cd out; zip -r tildefriends.ipa Payload; cd ..
|
cd out; zip -r tildefriends.ipa Payload; cd ..
|
||||||
@@ -1385,18 +1465,6 @@ help: ## Display this help message.
|
|||||||
.PHONY: help
|
.PHONY: help
|
||||||
.DEFAULT_GOAL := help
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
docs: debug
|
|
||||||
docs: ## Build HTML docs.
|
docs: ## Build HTML docs.
|
||||||
@echo '# CLI Usage\n' > docs/usage.md
|
|
||||||
@echo "## tildefriends -h" >> docs/usage.md
|
|
||||||
@echo '\n```' >> docs/usage.md
|
|
||||||
@out/debug/tildefriends -h >> docs/usage.md
|
|
||||||
@echo '```' >> docs/usage.md
|
|
||||||
@for command in $$(out/debug/tildefriends -h | grep -Po '[A-Za-z_]*(?= - )'); do
|
|
||||||
@ echo "\n## tildefriends $$command -h" >> docs/usage.md
|
|
||||||
@ echo '\n```' >> docs/usage.md
|
|
||||||
@ out/debug/tildefriends $$command -h >> docs/usage.md
|
|
||||||
@ echo '```' >> docs/usage.md
|
|
||||||
@done
|
|
||||||
@doxygen
|
@doxygen
|
||||||
.PHONY: docs
|
.PHONY: docs
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ dependencies in the right places.
|
|||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
|
System OpenSSL libraries are assumed to be available on Haiku and OpenSSL.
|
||||||
|
|
||||||
On MacOS, Xcode's command-line tools are expected to be available.
|
On MacOS, Xcode's command-line tools are expected to be available.
|
||||||
|
|
||||||
### Build Commands
|
### Build Commands
|
||||||
@@ -53,8 +55,9 @@ standard.
|
|||||||
## Running
|
## Running
|
||||||
|
|
||||||
By default, running the built `out/debug/tildefriends` executable will start a
|
By default, running the built `out/debug/tildefriends` executable will start a
|
||||||
web server at <http://localhost:12345/>. `tildefriends -h` lists further
|
web server at <http://localhost:12345/>. It expects to be run with the
|
||||||
options.
|
repository root as the current working directory. `tildefriends -h` lists
|
||||||
|
further options.
|
||||||
|
|
||||||
The first user to create an account and log in will be granted administrative
|
The first user to create an account and log in will be granted administrative
|
||||||
privileges. Further administration can be done in the `admin` app at
|
privileges. Further administration can be done in the `admin` app at
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🎛",
|
"emoji": "🎛",
|
||||||
"previous": "&bRhS1LQIH8WQjbBfQqdhjLv7tqDdHT7IEPyCmj39b+4=.sha256"
|
"previous": "&kmKNyb/uaXNb24gCinJtfS8iWx4cLUWdtl0y2DwEUas=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,12 @@ tfrpc.register(function global_settings_set(key, value) {
|
|||||||
return core.globalSettingsSet(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() {
|
async function main() {
|
||||||
try {
|
try {
|
||||||
let data = {
|
let data = {
|
||||||
users: {},
|
users: {},
|
||||||
granted: await core.allPermissionsGranted(),
|
granted: await core.allPermissionsGranted(),
|
||||||
settings: await core.globalSettingsDescriptions(),
|
settings: await core.globalSettingsDescriptions(),
|
||||||
blocks: await ssb.getBlocks(),
|
|
||||||
};
|
};
|
||||||
for (let user of await core.users()) {
|
for (let user of await core.users()) {
|
||||||
data.users[user] = await core.permissionsForUser(user);
|
data.users[user] = await core.permissionsForUser(user);
|
||||||
|
|||||||
@@ -16,14 +16,6 @@ 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) {
|
function global_settings_set(key, value) {
|
||||||
tfrpc.rpc
|
tfrpc.rpc
|
||||||
.global_settings_set(key, value)
|
.global_settings_set(key, value)
|
||||||
@@ -102,32 +94,11 @@ ${description.value}</textarea
|
|||||||
${user}: ${permissions.map((x) => permission_template(x))}
|
${user}: ${permissions.map((x) => permission_template(x))}
|
||||||
</li>
|
</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) =>
|
const users_template = (users) =>
|
||||||
html` <header class="w3-container w3-theme-l2"><h2>Users</h2></header>
|
html` <header class="w3-container w3-theme-l2"><h2>Users</h2></header>
|
||||||
<ul class="w3-ul">
|
<ul class="w3-ul">
|
||||||
${Object.entries(users).map((u) => user_template(u[0], u[1]))}
|
${Object.entries(users).map((u) => user_template(u[0], u[1]))}
|
||||||
</ul>`;
|
</ul>`;
|
||||||
const 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) =>
|
const page_template = (data) =>
|
||||||
html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%">
|
html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%">
|
||||||
<header class="w3-container w3-theme-l2"><h2>Global Settings</h2></header>
|
<header class="w3-container w3-theme-l2"><h2>Global Settings</h2></header>
|
||||||
@@ -138,7 +109,7 @@ ${description.value}</textarea
|
|||||||
.map((x) => html`${input_template(x, data.settings[x])}`)}
|
.map((x) => html`${input_template(x, data.settings[x])}`)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
${users_template(data.users)} ${blocks_template(data.blocks)}
|
${users_template(data.users)}
|
||||||
</div> `;
|
</div> `;
|
||||||
render(page_template(g_data), document.body);
|
render(page_template(g_data), document.body);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||||
@@ -108,8 +108,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
|
||||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
|
||||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||||
@@ -150,7 +148,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||||
.w3-hover-none:hover{box-shadow:none!important}
|
.w3-hover-none:hover{box-shadow:none!important}
|
||||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
|
||||||
/* Colors */
|
/* Colors */
|
||||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||||
@@ -178,19 +175,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
|
||||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
|
||||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
|
||||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
|
||||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
|
||||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
|
||||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
|
||||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
|
||||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
|
||||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
|
||||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
|
||||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
|
||||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
|
||||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "📜",
|
"emoji": "📜",
|
||||||
"previous": "&sJqeyYjHys6Z8IqqtZ2ij2ZC1E2xieu/FU/u2hE+O1U=.sha256"
|
"previous": "&BEf0nraBdHk/+PWqx6tOSu5rheWVaxaL7orAOz3285M=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,9 +55,6 @@ app.setDocument(`<head>
|
|||||||
</head>
|
</head>
|
||||||
<body style="color:#fff">
|
<body style="color:#fff">
|
||||||
${markdown(docs.docs.global)}
|
${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')}
|
${[...treeify('', globalThis)].map(x => document(x)).join('\n')}
|
||||||
<a id="Database"></a>
|
<a id="Database"></a>
|
||||||
${markdown(docs.docs.database)}
|
${markdown(docs.docs.database)}
|
||||||
|
|||||||
@@ -195,6 +195,51 @@ Call a function after some delay.
|
|||||||
* *Number* **timeout** Number of milliseconds to wait before calling the callback function.
|
* *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()'] = `
|
docs['exit()'] = `
|
||||||
Exits the app. But why would you want to do that?
|
Exits the app. But why would you want to do that?
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||||
@@ -108,8 +108,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
|
||||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
|
||||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||||
@@ -150,7 +148,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||||
.w3-hover-none:hover{box-shadow:none!important}
|
.w3-hover-none:hover{box-shadow:none!important}
|
||||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
|
||||||
/* Colors */
|
/* Colors */
|
||||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||||
@@ -178,19 +175,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
|
||||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
|
||||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
|
||||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
|
||||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
|
||||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
|
||||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
|
||||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
|
||||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
|
||||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
|
||||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
|
||||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
|
||||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
|
||||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||||
|
|||||||
42
apps/blog/lit-all.min.js
vendored
42
apps/blog/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "tildefriends-app",
|
|
||||||
"emoji": "📖",
|
|
||||||
"previous": "&Vt7LWYGm9HpVM50+aBJv9Q1FnEf12Gd1+Uyft+IYWGo=.sha256"
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
async function query(sql, args) {
|
|
||||||
let result = [];
|
|
||||||
await ssb.sqlAsync(sql, args, function (row) {
|
|
||||||
result.push(row);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
let whoami = await ssb.getActiveIdentity();
|
|
||||||
let following = Object.keys(await ssb.following([whoami], 2));
|
|
||||||
let data = await query(
|
|
||||||
`
|
|
||||||
SELECT
|
|
||||||
messages.id,
|
|
||||||
content ->> 'title' AS title,
|
|
||||||
content ->> '$.image.link' AS image,
|
|
||||||
content ->> 'description' AS description,
|
|
||||||
content ->> 'authors' AS authors
|
|
||||||
FROM messages, json_each(?) AS following
|
|
||||||
ON messages.author = following.value
|
|
||||||
WHERE
|
|
||||||
content ->> 'type' = 'bookclub' AND
|
|
||||||
title IS NOT NULL AND
|
|
||||||
image IS NOT NULL AND
|
|
||||||
description IS NOT NULL
|
|
||||||
`,
|
|
||||||
[JSON.stringify(following)]
|
|
||||||
);
|
|
||||||
let books = Object.fromEntries(data.map((x) => [x.id, x]));
|
|
||||||
for (let book of Object.values(books)) {
|
|
||||||
try {
|
|
||||||
book.authors = JSON.parse(book.authors);
|
|
||||||
} catch {
|
|
||||||
book.authors = [book.authors];
|
|
||||||
}
|
|
||||||
book.reviews = [];
|
|
||||||
}
|
|
||||||
let reviews = await query(
|
|
||||||
`
|
|
||||||
SELECT author, about, json(json_group_object(key, value)) AS content
|
|
||||||
FROM (
|
|
||||||
SELECT
|
|
||||||
messages.author,
|
|
||||||
messages.content ->> '$.about' AS about,
|
|
||||||
fields.key,
|
|
||||||
RANK() OVER (PARTITION BY messages.author, messages.content ->> '$.about', fields.key ORDER BY messages.sequence DESC) AS rank,
|
|
||||||
fields.value
|
|
||||||
FROM messages, json_each(messages.content) AS fields, json_each(?1) AS book, json_each(?2) AS following
|
|
||||||
ON messages.author = following.value
|
|
||||||
WHERE
|
|
||||||
messages.content ->> 'type' = 'about'
|
|
||||||
AND messages.content ->> '$.about' = book.value
|
|
||||||
AND NOT fields.key IN ('about', 'type')
|
|
||||||
UNION
|
|
||||||
SELECT
|
|
||||||
messages.author,
|
|
||||||
messages.content ->> '$.updates' AS about,
|
|
||||||
fields.key,
|
|
||||||
RANK() OVER (PARTITION BY messages.author, messages.content ->> '$.updates', fields.key ORDER BY messages.sequence DESC) AS rank,
|
|
||||||
fields.value
|
|
||||||
FROM messages, json_each(messages.content) AS fields, json_each(?1) AS book, json_each(?2) AS following
|
|
||||||
ON messages.author = following.value
|
|
||||||
WHERE
|
|
||||||
messages.content ->> 'type' = 'bookclubUpdate'
|
|
||||||
AND messages.content ->> '$.updates' = book.value
|
|
||||||
AND NOT fields.key IN ('about', 'updates', 'type')
|
|
||||||
) WHERE rank = 1
|
|
||||||
GROUP BY author, about
|
|
||||||
`,
|
|
||||||
[JSON.stringify(Object.keys(books)), JSON.stringify(following)]
|
|
||||||
);
|
|
||||||
for (let review of reviews) {
|
|
||||||
review.content = JSON.parse(review.content);
|
|
||||||
books[review.about].reviews.push(review);
|
|
||||||
}
|
|
||||||
await app.setDocument(
|
|
||||||
utf8Decode(getFile('index.html')).replace('G_DATA', JSON.stringify(data))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch(function (e) {
|
|
||||||
throw new Error(e.message);
|
|
||||||
});
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import {LitElement, html, map, unsafeHTML} from './lit-all.min.js';
|
|
||||||
import {markdown} from './markdown.js';
|
|
||||||
|
|
||||||
class BookClubElement extends LitElement {
|
|
||||||
render() {
|
|
||||||
if (!g_data?.length) {
|
|
||||||
return html`<h1>No bookclub messages to display.</h1>`;
|
|
||||||
}
|
|
||||||
return html`
|
|
||||||
<link rel="stylesheet" href="w3.css"></link>
|
|
||||||
<div class="w3-grid" style="background-color: #fff; gap:8px; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr))">
|
|
||||||
${map(
|
|
||||||
g_data,
|
|
||||||
(x) => html`
|
|
||||||
<div class="w3-card-4">
|
|
||||||
<header class="w3-container w3-center">
|
|
||||||
<h1>${markdown(x.title)}</h1>
|
|
||||||
<p>${map(x.authors, (author) => markdown(author))}</p>
|
|
||||||
</header>
|
|
||||||
<div class="w3-container w3-center">
|
|
||||||
<img
|
|
||||||
src="/${x.image}/view"
|
|
||||||
style="max-height: 2in; max-width: 2in"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="w3-container">
|
|
||||||
<p>${markdown(x.description)}</p>
|
|
||||||
</div>
|
|
||||||
<ul class="w3-container w3-list">
|
|
||||||
${map(
|
|
||||||
x.reviews.filter(
|
|
||||||
(x) => x.content?.rating || x.content?.review
|
|
||||||
),
|
|
||||||
(review) => html`
|
|
||||||
<li>
|
|
||||||
${review.content.rating} /
|
|
||||||
${review.content.ratingMax ?? 5}
|
|
||||||
${review.content.ratingType ?? 'stars'}
|
|
||||||
<div>${review.content.review}</div>
|
|
||||||
</li>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define('bc-app', BookClubElement);
|
|
||||||
1
apps/bookclub/commonmark.min.js
vendored
1
apps/bookclub/commonmark.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,13 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" href="w3.css" />
|
|
||||||
<script>
|
|
||||||
const g_data = G_DATA;
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body style="background-color: #fff">
|
|
||||||
<bc-app />
|
|
||||||
</body>
|
|
||||||
<script type="module" src="bc-app.js"></script>
|
|
||||||
</html>
|
|
||||||
120
apps/bookclub/lit-all.min.js
vendored
120
apps/bookclub/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,119 +0,0 @@
|
|||||||
import * as commonmark from './commonmark.min.js';
|
|
||||||
import {unsafeHTML} from './lit-all.min.js';
|
|
||||||
|
|
||||||
function image(node, entering) {
|
|
||||||
if (
|
|
||||||
node.firstChild?.type === 'text' &&
|
|
||||||
node.firstChild.literal.startsWith('video:')
|
|
||||||
) {
|
|
||||||
if (entering) {
|
|
||||||
this.lit(
|
|
||||||
'<video style="max-width: 100%; max-height: 480px" title="' +
|
|
||||||
this.esc(node.firstChild?.literal) +
|
|
||||||
'" controls>'
|
|
||||||
);
|
|
||||||
this.lit('<source src="' + this.esc(node.destination) + '"></source>');
|
|
||||||
this.disableTags += 1;
|
|
||||||
} else {
|
|
||||||
this.disableTags -= 1;
|
|
||||||
this.lit('</video>');
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
node.firstChild?.type === 'text' &&
|
|
||||||
node.firstChild.literal.startsWith('audio:')
|
|
||||||
) {
|
|
||||||
if (entering) {
|
|
||||||
this.lit(
|
|
||||||
'<audio style="height: 32px; max-width: 100%" title="' +
|
|
||||||
this.esc(node.firstChild?.literal) +
|
|
||||||
'" controls>'
|
|
||||||
);
|
|
||||||
this.lit('<source src="' + this.esc(node.destination) + '"></source>');
|
|
||||||
this.disableTags += 1;
|
|
||||||
} else {
|
|
||||||
this.disableTags -= 1;
|
|
||||||
this.lit('</audio>');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (entering) {
|
|
||||||
if (this.disableTags === 0) {
|
|
||||||
this.lit(
|
|
||||||
'<div class="img_caption">' +
|
|
||||||
this.esc(node.firstChild?.literal || node.destination) +
|
|
||||||
'</div>'
|
|
||||||
);
|
|
||||||
if (this.options.safe && potentiallyUnsafe(node.destination)) {
|
|
||||||
this.lit('<img src="" title="');
|
|
||||||
} else {
|
|
||||||
this.lit('<img src="' + this.esc(node.destination) + '" title="');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.disableTags += 1;
|
|
||||||
} else {
|
|
||||||
this.disableTags -= 1;
|
|
||||||
if (this.disableTags === 0) {
|
|
||||||
if (node.title) {
|
|
||||||
this.lit('" title="' + this.esc(node.title));
|
|
||||||
}
|
|
||||||
this.lit('" />');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function code(node) {
|
|
||||||
let attrs = this.attrs(node);
|
|
||||||
attrs.push(['class', k_code_classes]);
|
|
||||||
this.tag('code', attrs);
|
|
||||||
this.out(node.literal);
|
|
||||||
this.tag('/code');
|
|
||||||
}
|
|
||||||
|
|
||||||
function attrs(node) {
|
|
||||||
let result = commonmark.HtmlRenderer.prototype.attrs.bind(this)(node);
|
|
||||||
if (node.type == 'block_quote') {
|
|
||||||
result.push(['class', 'w3-theme-d1']);
|
|
||||||
} else if (node.type == 'code_block') {
|
|
||||||
result.push(['class', k_code_classes]);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function markdown(md) {
|
|
||||||
let reader = new commonmark.Parser();
|
|
||||||
let writer = new commonmark.HtmlRenderer({safe: true});
|
|
||||||
writer.image = image;
|
|
||||||
writer.code = code;
|
|
||||||
writer.attrs = attrs;
|
|
||||||
let parsed = reader.parse(md || '');
|
|
||||||
let walker = parsed.walker();
|
|
||||||
let event, node;
|
|
||||||
while ((event = walker.next())) {
|
|
||||||
node = event.node;
|
|
||||||
if (event.entering) {
|
|
||||||
if (node.type == 'link') {
|
|
||||||
if (
|
|
||||||
node.destination.startsWith('@') &&
|
|
||||||
node.destination.endsWith('.ed25519')
|
|
||||||
) {
|
|
||||||
node.destination = '#' + encodeURIComponent(node.destination);
|
|
||||||
} else if (
|
|
||||||
node.destination.startsWith('%') &&
|
|
||||||
node.destination.endsWith('.sha256')
|
|
||||||
) {
|
|
||||||
node.destination = '#' + encodeURIComponent(node.destination);
|
|
||||||
} else if (
|
|
||||||
node.destination.startsWith('&') &&
|
|
||||||
node.destination.endsWith('.sha256')
|
|
||||||
) {
|
|
||||||
node.destination = '/' + node.destination + '/view';
|
|
||||||
}
|
|
||||||
} else if (node.type == 'image') {
|
|
||||||
if (node.destination.startsWith('&')) {
|
|
||||||
node.destination = '/' + node.destination + '/view';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return unsafeHTML(writer.render(parsed));
|
|
||||||
}
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
|
||||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
|
||||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
|
||||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
|
||||||
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
|
|
||||||
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
|
|
||||||
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
|
|
||||||
a{background-color:transparent}a:active,a:hover{outline-width:0}
|
|
||||||
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
|
|
||||||
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
|
|
||||||
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
|
|
||||||
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
|
|
||||||
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
|
|
||||||
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
|
|
||||||
button,input{overflow:visible}button,select{text-transform:none}
|
|
||||||
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
|
|
||||||
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
|
|
||||||
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
|
|
||||||
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
|
|
||||||
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
|
|
||||||
[type=checkbox],[type=radio]{padding:0}
|
|
||||||
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
|
|
||||||
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
|
|
||||||
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
|
|
||||||
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
|
|
||||||
/* End extract */
|
|
||||||
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
|
|
||||||
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
|
|
||||||
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
|
|
||||||
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
|
|
||||||
hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|
||||||
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
|
|
||||||
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
|
|
||||||
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
|
|
||||||
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
|
|
||||||
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
|
|
||||||
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
|
|
||||||
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
|
|
||||||
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
|
|
||||||
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
|
|
||||||
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
|
||||||
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
|
|
||||||
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
|
|
||||||
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
|
|
||||||
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
|
|
||||||
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
|
|
||||||
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
|
|
||||||
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
|
|
||||||
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
|
|
||||||
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
|
|
||||||
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
|
|
||||||
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
|
|
||||||
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
|
|
||||||
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
|
|
||||||
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
|
|
||||||
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
|
|
||||||
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
|
|
||||||
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
|
|
||||||
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
|
|
||||||
.w3-main,#main{transition:margin-left .4s}
|
|
||||||
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
|
|
||||||
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
|
|
||||||
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
|
|
||||||
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
|
|
||||||
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
|
|
||||||
.w3-bar .w3-button{white-space:normal}
|
|
||||||
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
|
|
||||||
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
|
|
||||||
.w3-responsive{display:block;overflow-x:auto}
|
|
||||||
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
|
|
||||||
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
|
|
||||||
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
|
|
||||||
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
|
|
||||||
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
|
|
||||||
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
|
|
||||||
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
|
|
||||||
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
|
|
||||||
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
|
|
||||||
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
|
|
||||||
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
|
|
||||||
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
|
|
||||||
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
|
|
||||||
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
|
|
||||||
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
|
|
||||||
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
|
|
||||||
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
|
|
||||||
@media (max-width:1205px){.w3-auto{max-width:95%}}
|
|
||||||
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
|
|
||||||
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
|
|
||||||
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
|
|
||||||
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
|
|
||||||
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
|
|
||||||
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
|
|
||||||
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
|
|
||||||
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
|
|
||||||
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
|
|
||||||
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
|
|
||||||
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
|
|
||||||
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
|
|
||||||
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
|
|
||||||
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
|
|
||||||
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
|
|
||||||
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
|
||||||
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
|
||||||
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
|
|
||||||
.w3-display-position{position:absolute}
|
|
||||||
.w3-circle{border-radius:50%}
|
|
||||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
|
||||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
|
||||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
|
||||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
|
||||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
|
||||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
|
||||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
|
||||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
|
||||||
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
|
|
||||||
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
|
|
||||||
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
|
|
||||||
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
|
|
||||||
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
|
|
||||||
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
|
|
||||||
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
|
|
||||||
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
|
|
||||||
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
|
|
||||||
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
|
|
||||||
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
|
|
||||||
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
|
|
||||||
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
|
|
||||||
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
|
|
||||||
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
|
|
||||||
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
|
|
||||||
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
|
|
||||||
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
|
|
||||||
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
|
|
||||||
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
|
|
||||||
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
|
|
||||||
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
|
|
||||||
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
|
|
||||||
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
|
|
||||||
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
|
|
||||||
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
|
|
||||||
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
|
|
||||||
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
|
|
||||||
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
|
|
||||||
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
|
|
||||||
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
|
|
||||||
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
|
|
||||||
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
|
|
||||||
.w3-left{float:left!important}.w3-right{float:right!important}
|
|
||||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
|
||||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
|
||||||
.w3-hover-none:hover{box-shadow:none!important}
|
|
||||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
|
||||||
/* Colors */
|
|
||||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
|
||||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
|
||||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
|
||||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
|
||||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
|
||||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
|
||||||
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
|
|
||||||
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
|
|
||||||
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
|
|
||||||
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
|
|
||||||
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
|
|
||||||
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
|
|
||||||
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
|
|
||||||
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
|
|
||||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
|
||||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
|
||||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
|
||||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
|
||||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
|
||||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
|
||||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
|
||||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
|
||||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
|
||||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
|
||||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
|
||||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
|
||||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
|
||||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
|
||||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
|
||||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
|
||||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
|
||||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
|
||||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
|
||||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
|
||||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
|
||||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
|
||||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
|
||||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
|
||||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
|
||||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
|
||||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
|
||||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
|
||||||
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
|
|
||||||
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
|
|
||||||
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
|
|
||||||
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
|
|
||||||
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
|
|
||||||
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
|
|
||||||
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
|
|
||||||
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
|
|
||||||
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
|
|
||||||
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
|
|
||||||
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
|
|
||||||
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
|
|
||||||
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
|
|
||||||
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
|
|
||||||
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
|
|
||||||
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
|
|
||||||
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
|
|
||||||
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
|
|
||||||
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
|
|
||||||
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
|
|
||||||
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
|
|
||||||
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
|
|
||||||
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
|
|
||||||
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
|
|
||||||
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
|
|
||||||
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
|
|
||||||
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
|
|
||||||
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
|
|
||||||
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
|
|
||||||
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
|
|
||||||
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
|
|
||||||
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
|
|
||||||
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
|
|
||||||
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
|
|
||||||
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
|
|
||||||
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
|
|
||||||
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
|
|
||||||
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
|
|
||||||
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
|
|
||||||
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
|
|
||||||
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
|
|
||||||
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
|
|
||||||
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
|
|
||||||
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
|
|
||||||
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
|
|
||||||
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
|
|
||||||
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
|
|
||||||
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
|
|
||||||
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
|
|
||||||
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
|
|
||||||
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
|
|
||||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
|
||||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
|
||||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
|
||||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "➡️",
|
"emoji": "➡️",
|
||||||
"previous": "&WiC0IosFJw/FNuypqKc4LFZUZjCFlmbY7KEAabrXU9o=.sha256"
|
"previous": "&YDDSzbRD8NFAykYlZnk4r4hAK5qXjT5LmKE6rhS1s+A=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ async function contacts_internal(id, last_row_id, following, max_row_id) {
|
|||||||
WHERE author = ? AND
|
WHERE author = ? AND
|
||||||
rowid > ? AND
|
rowid > ? AND
|
||||||
rowid <= ? AND
|
rowid <= ? AND
|
||||||
content ->> 'type' = 'contact'
|
json_extract(content, '$.type') = 'contact'
|
||||||
ORDER BY sequence
|
ORDER BY sequence
|
||||||
`,
|
`,
|
||||||
[id, last_row_id, max_row_id]
|
[id, last_row_id, max_row_id]
|
||||||
@@ -149,7 +149,7 @@ async function fetch_about(db, ids, users) {
|
|||||||
messages.author = following.value AND
|
messages.author = following.value AND
|
||||||
messages.rowid > ?3 AND
|
messages.rowid > ?3 AND
|
||||||
messages.rowid <= ?4 AND
|
messages.rowid <= ?4 AND
|
||||||
messages.content ->> 'type' = 'about'
|
json_extract(messages.content, '$.type') = 'about'
|
||||||
UNION
|
UNION
|
||||||
SELECT
|
SELECT
|
||||||
messages.*
|
messages.*
|
||||||
@@ -159,7 +159,7 @@ async function fetch_about(db, ids, users) {
|
|||||||
WHERE
|
WHERE
|
||||||
messages.author = following.value AND
|
messages.author = following.value AND
|
||||||
messages.rowid <= ?4 AND
|
messages.rowid <= ?4 AND
|
||||||
messages.content ->> 'type' = 'about'
|
json_extract(messages.content, '$.type') = 'about'
|
||||||
ORDER BY messages.author, messages.sequence
|
ORDER BY messages.author, messages.sequence
|
||||||
`,
|
`,
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||||
@@ -108,8 +108,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
|
||||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
|
||||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||||
@@ -150,7 +148,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||||
.w3-hover-none:hover{box-shadow:none!important}
|
.w3-hover-none:hover{box-shadow:none!important}
|
||||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
|
||||||
/* Colors */
|
/* Colors */
|
||||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||||
@@ -178,19 +175,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
|
||||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
|
||||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
|
||||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
|
||||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
|
||||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
|
||||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
|
||||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
|
||||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
|
||||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
|
||||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
|
||||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
|
||||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
|
||||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "tildefriends-app",
|
|
||||||
"emoji": "💡",
|
|
||||||
"previous": "&dez1mAjzd4X9Z6ss0cBJO8EJDP+g3GtyYDNiasrw2pM=.sha256"
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import * as tfrpc from '/tfrpc.js';
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
await app.setDocument(utf8Decode(getFile('index.html')));
|
|
||||||
}
|
|
||||||
|
|
||||||
tfrpc.register(async function complete() {
|
|
||||||
if (
|
|
||||||
core.user?.credentials?.permissions?.administration &&
|
|
||||||
(await core.globalSettingsGet('index')) == '/~core/intro/'
|
|
||||||
) {
|
|
||||||
return await core.globalSettingsSet('index', '/~core/ssb/');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
main();
|
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html style="height: 100%; margin: 0; padding: 0; box-sizing: border-box">
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="w3.css" />
|
|
||||||
<style>
|
|
||||||
.slide {
|
|
||||||
display: none;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
.dot {
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.w3-left,
|
|
||||||
.w3-right {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body
|
|
||||||
style="
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
"
|
|
||||||
class="w3-flex w3-dark-gray w3-center"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
id="scrollbox"
|
|
||||||
style="
|
|
||||||
flex: 1 1 auto;
|
|
||||||
overflow: auto;
|
|
||||||
contain: content;
|
|
||||||
padding-top: 16px;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="slide">
|
|
||||||
<div
|
|
||||||
class="w3-content w3-xlarge w3-card-4 w3-blue w3-panel w3-padding-32 w3-round-xlarge"
|
|
||||||
style="margin: 32px"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div>Welcome to</div>
|
|
||||||
<div>~😎 Tilde Friends.</div>
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
<button class="w3-button w3-yellow proceed" id="next0">Next</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="slide w3-card-4 w3-gray" style="width: 90%">
|
|
||||||
<header class="w3-container w3-blue w3-xlarge">
|
|
||||||
<h1>This brief tutorial will introduce:</h1>
|
|
||||||
</header>
|
|
||||||
<ul class="w3-large w3-left-align">
|
|
||||||
<li><b>Secure Scuttlebutt</b>, a decentralized social network.</li>
|
|
||||||
<li>
|
|
||||||
<b>Tilde Friends</b>, the application platform that you are using
|
|
||||||
right now.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<b>How to get started</b> if you want to get gossiping right away.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<footer class="w3-center w3-xlarge w3-padding">
|
|
||||||
<button class="w3-button w3-yellow proceed" id="next1">Next</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
<div class="slide w3-gray" style="width: 90%">
|
|
||||||
<div class="w3-card-4 w3-xlarge">
|
|
||||||
<header class="w3-container w3-blue">
|
|
||||||
<h1>💻Secure Scuttlebutt in a Nutshell🦀</h1>
|
|
||||||
</header>
|
|
||||||
<div class="w3-container w3-large w3-left-align">
|
|
||||||
<p>
|
|
||||||
Secure Scuttlebutt is a social network whose technical operation
|
|
||||||
attempts to mirror human social interaction.
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
You can create your own account and post to your own feed on
|
|
||||||
your own device. This is all <b>local</b> with no external
|
|
||||||
communication. This puts you fully in control of your own words
|
|
||||||
and actions.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Before you can interact with others, you need to
|
|
||||||
<b>connect over the network</b>, either directly to your friends
|
|
||||||
(i.e., peer-to-peer between your phones on coffee shop Wi-Fi) or
|
|
||||||
to 🚪<i>rooms</i> and 🍻<i>pubs</i> (hint: search the web for
|
|
||||||
<i>#ssbroom</i>).
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Who you choose to <b>follow</b> determines what you see, with
|
|
||||||
most people choosing to see messages from friends and friends of
|
|
||||||
those friends. If you encounter content you'd rather not see,
|
|
||||||
<b>block</b> the offending account to improve the experience for
|
|
||||||
you and your followers.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Your feed is an <b>immutable</b> log of your activity. Post with
|
|
||||||
care, because like your words in real life, posts can't be taken
|
|
||||||
back.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<footer class="w3-center w3-xlarge w3-padding">
|
|
||||||
<a
|
|
||||||
class="w3-button w3-light-gray"
|
|
||||||
href="https://scuttlebutt.nz/"
|
|
||||||
target="_blank"
|
|
||||||
>See scuttlebutt.nz</a
|
|
||||||
>
|
|
||||||
<button class="w3-button w3-yellow proceed" id="next2">Next</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="slide w3-gray" style="width: 90%">
|
|
||||||
<div class="w3-card-4 w3-xlarge">
|
|
||||||
<header class="w3-container w3-blue w3-center">
|
|
||||||
<h1>~😎 Let's Talk Tilde Friends ~😎</h1>
|
|
||||||
</header>
|
|
||||||
<div class="w3-container w3-large w3-left-align">
|
|
||||||
<p>
|
|
||||||
Tilde Friends is an application platform that is an application of
|
|
||||||
its own.
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
This intro is a Tilde Friends app. You can click <b>edit</b> at
|
|
||||||
the top to look under the hood and make changes.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
It is already possible to make and share new applications using
|
|
||||||
only Tilde Friends and Secure Scuttlebutt without having to set
|
|
||||||
up development environments, configure web servers, register
|
|
||||||
domain names, or pay for hosting services.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
But it's also set up so that you can't just break an app that
|
|
||||||
everybody is using or do malicious things with personal content.
|
|
||||||
There are <b>protections</b> in place like an operating system.
|
|
||||||
The intent is also for it to be <b>safe</b> to run strange apps
|
|
||||||
without worrying about adverse effects.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
But this is all a big 🚧work in progress🚧 and
|
|
||||||
<b>experiment</b>. Let's see where it takes us.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<footer class="w3-center w3-xlarge w3-padding">
|
|
||||||
<button class="w3-button w3-yellow proceed" id="next3">Next</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="slide w3-gray" style="width: 90%">
|
|
||||||
<div class="w3-card-4 w3-xlarge">
|
|
||||||
<header class="w3-container w3-blue w3-center">
|
|
||||||
<h1>🦀Let's Get this Tilde Friends Party Started🎉</h1>
|
|
||||||
</header>
|
|
||||||
<div class="w3-container w3-large w3-left-align">
|
|
||||||
<p>The button below will take you to the Secure Scuttlebutt app.</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Remember:
|
|
||||||
<ol>
|
|
||||||
<li>You are in charge. This is all on your device.</li>
|
|
||||||
<li>
|
|
||||||
Make network connections to exchange messages with others.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Follow more accounts to see more content, and block those
|
|
||||||
posting content you'd rather not see.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Be respectful, and consider the consequences of what you
|
|
||||||
post.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
This is all under active development. Exercise patience, and
|
|
||||||
report issues encountered.
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
To see this tutorial again later, select <b>apps</b> ->
|
|
||||||
<b>Core Apps</b> -> <b>intro</b>. When you continue, you will
|
|
||||||
be prompted to save a setting so that you don't see this every
|
|
||||||
time.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<footer class="w3-center w3-xlarge w3-padding">
|
|
||||||
<button class="w3-button w3-yellow" id="complete">Continue</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="w3-text-white w3-xlarge w3-center w3-flex"
|
|
||||||
style="
|
|
||||||
width: 100%;
|
|
||||||
flex: 0 1;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="w3-jumbo" id="left" style="flex: 1 0; cursor: pointer">
|
|
||||||
❮
|
|
||||||
</div>
|
|
||||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
|
||||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
|
||||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
|
||||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
|
||||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
|
||||||
<div class="w3-jumbo" style="flex: 1 0; cursor: pointer" id="right">
|
|
||||||
❯
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="module">
|
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
|
||||||
|
|
||||||
let index = 0;
|
|
||||||
function set(i) {
|
|
||||||
show(i - index);
|
|
||||||
}
|
|
||||||
function show(delta) {
|
|
||||||
let slides = [...document.getElementsByClassName('slide')];
|
|
||||||
let dots = [...document.getElementsByClassName('dot')];
|
|
||||||
index = (index + delta + slides.length) % slides.length;
|
|
||||||
for (let slide of slides) {
|
|
||||||
slide.style.display =
|
|
||||||
slides.indexOf(slide) == index ? 'block' : 'none';
|
|
||||||
}
|
|
||||||
for (let dot of dots) {
|
|
||||||
if (dots.indexOf(dot) == index) {
|
|
||||||
dot.classList.add('w3-white');
|
|
||||||
} else {
|
|
||||||
dot.classList.remove('w3-white');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.getElementById('left').style.visibility =
|
|
||||||
index == 0 ? 'hidden' : 'visible';
|
|
||||||
document.getElementById('right').style.visibility =
|
|
||||||
index == slides.length - 1 ? 'hidden' : 'visible';
|
|
||||||
document.getElementById('scrollbox').scrollTo(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let dots = [...document.getElementsByClassName('dot')];
|
|
||||||
for (let dot of dots) {
|
|
||||||
dot.onclick = () => set(dots.indexOf(dot));
|
|
||||||
}
|
|
||||||
for (let button of document.getElementsByClassName('proceed')) {
|
|
||||||
button.onclick = () => show(1);
|
|
||||||
}
|
|
||||||
document.getElementById('left').onclick = () => show(-1);
|
|
||||||
document.getElementById('right').onclick = () => show(1);
|
|
||||||
document.getElementById('complete').onclick = function () {
|
|
||||||
console.log('completing');
|
|
||||||
tfrpc.rpc.complete().finally(function () {
|
|
||||||
console.log('completed');
|
|
||||||
let a = document.createElement('a');
|
|
||||||
a.href = '/~core/ssb/';
|
|
||||||
a.target = '_top';
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
window.addEventListener('keyup', function (event) {
|
|
||||||
if (event.key == 'ArrowLeft') {
|
|
||||||
show(-1);
|
|
||||||
} else if (event.key == 'ArrowRight') {
|
|
||||||
show(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
show(0);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
|
||||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
|
||||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
|
||||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
|
||||||
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
|
|
||||||
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
|
|
||||||
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
|
|
||||||
a{background-color:transparent}a:active,a:hover{outline-width:0}
|
|
||||||
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
|
|
||||||
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
|
|
||||||
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
|
|
||||||
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
|
|
||||||
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
|
|
||||||
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
|
|
||||||
button,input{overflow:visible}button,select{text-transform:none}
|
|
||||||
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
|
|
||||||
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
|
|
||||||
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
|
|
||||||
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
|
|
||||||
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
|
|
||||||
[type=checkbox],[type=radio]{padding:0}
|
|
||||||
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
|
|
||||||
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
|
|
||||||
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
|
|
||||||
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
|
|
||||||
/* End extract */
|
|
||||||
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
|
|
||||||
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
|
|
||||||
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
|
|
||||||
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
|
|
||||||
hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|
||||||
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
|
|
||||||
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
|
|
||||||
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
|
|
||||||
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
|
|
||||||
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
|
|
||||||
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
|
|
||||||
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
|
|
||||||
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
|
|
||||||
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
|
|
||||||
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
|
||||||
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
|
|
||||||
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
|
|
||||||
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
|
|
||||||
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
|
|
||||||
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
|
|
||||||
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
|
|
||||||
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
|
|
||||||
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
|
|
||||||
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
|
|
||||||
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
|
|
||||||
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
|
|
||||||
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
|
|
||||||
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
|
|
||||||
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
|
|
||||||
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
|
|
||||||
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
|
|
||||||
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
|
|
||||||
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
|
|
||||||
.w3-main,#main{transition:margin-left .4s}
|
|
||||||
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
|
|
||||||
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
|
|
||||||
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
|
|
||||||
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
|
|
||||||
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
|
|
||||||
.w3-bar .w3-button{white-space:normal}
|
|
||||||
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
|
|
||||||
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
|
|
||||||
.w3-responsive{display:block;overflow-x:auto}
|
|
||||||
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
|
|
||||||
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
|
|
||||||
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
|
|
||||||
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
|
|
||||||
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
|
|
||||||
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
|
|
||||||
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
|
|
||||||
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
|
|
||||||
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
|
|
||||||
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
|
|
||||||
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
|
|
||||||
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
|
|
||||||
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
|
|
||||||
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
|
|
||||||
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
|
|
||||||
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
|
|
||||||
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
|
|
||||||
@media (max-width:1205px){.w3-auto{max-width:95%}}
|
|
||||||
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
|
|
||||||
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
|
|
||||||
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
|
|
||||||
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
|
|
||||||
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
|
|
||||||
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
|
|
||||||
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
|
|
||||||
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
|
|
||||||
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
|
|
||||||
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
|
|
||||||
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
|
|
||||||
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
|
|
||||||
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
|
|
||||||
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
|
|
||||||
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
|
|
||||||
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
|
||||||
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
|
||||||
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
|
|
||||||
.w3-display-position{position:absolute}
|
|
||||||
.w3-circle{border-radius:50%}
|
|
||||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
|
||||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
|
||||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
|
||||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
|
||||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
|
||||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
|
||||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
|
||||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
|
||||||
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
|
|
||||||
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
|
|
||||||
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
|
|
||||||
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
|
|
||||||
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
|
|
||||||
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
|
|
||||||
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
|
|
||||||
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
|
|
||||||
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
|
|
||||||
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
|
|
||||||
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
|
|
||||||
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
|
|
||||||
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
|
|
||||||
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
|
|
||||||
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
|
|
||||||
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
|
|
||||||
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
|
|
||||||
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
|
|
||||||
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
|
|
||||||
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
|
|
||||||
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
|
|
||||||
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
|
|
||||||
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
|
|
||||||
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
|
|
||||||
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
|
|
||||||
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
|
|
||||||
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
|
|
||||||
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
|
|
||||||
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
|
|
||||||
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
|
|
||||||
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
|
|
||||||
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
|
|
||||||
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
|
|
||||||
.w3-left{float:left!important}.w3-right{float:right!important}
|
|
||||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
|
||||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
|
||||||
.w3-hover-none:hover{box-shadow:none!important}
|
|
||||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
|
||||||
/* Colors */
|
|
||||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
|
||||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
|
||||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
|
||||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
|
||||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
|
||||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
|
||||||
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
|
|
||||||
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
|
|
||||||
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
|
|
||||||
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
|
|
||||||
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
|
|
||||||
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
|
|
||||||
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
|
|
||||||
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
|
|
||||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
|
||||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
|
||||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
|
||||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
|
||||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
|
||||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
|
||||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
|
||||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
|
||||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
|
||||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
|
||||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
|
||||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
|
||||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
|
||||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
|
||||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
|
||||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
|
||||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
|
||||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
|
||||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
|
||||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
|
||||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
|
||||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
|
||||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
|
||||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
|
||||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
|
||||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
|
||||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
|
||||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
|
||||||
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
|
|
||||||
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
|
|
||||||
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
|
|
||||||
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
|
|
||||||
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
|
|
||||||
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
|
|
||||||
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
|
|
||||||
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
|
|
||||||
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
|
|
||||||
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
|
|
||||||
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
|
|
||||||
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
|
|
||||||
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
|
|
||||||
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
|
|
||||||
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
|
|
||||||
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
|
|
||||||
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
|
|
||||||
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
|
|
||||||
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
|
|
||||||
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
|
|
||||||
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
|
|
||||||
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
|
|
||||||
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
|
|
||||||
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
|
|
||||||
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
|
|
||||||
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
|
|
||||||
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
|
|
||||||
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
|
|
||||||
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
|
|
||||||
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
|
|
||||||
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
|
|
||||||
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
|
|
||||||
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
|
|
||||||
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
|
|
||||||
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
|
|
||||||
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
|
|
||||||
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
|
|
||||||
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
|
|
||||||
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
|
|
||||||
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
|
|
||||||
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
|
|
||||||
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
|
|
||||||
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
|
|
||||||
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
|
|
||||||
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
|
|
||||||
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
|
|
||||||
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
|
|
||||||
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
|
|
||||||
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
|
|
||||||
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
|
|
||||||
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
|
|
||||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
|
||||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
|
||||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
|
||||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
|
||||||
42
apps/issues/lit-all.min.js
vendored
42
apps/issues/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
42
apps/journal/lit-all.min.js
vendored
42
apps/journal/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🚪",
|
"emoji": "🚪",
|
||||||
"previous": "&DJwkqNfYWtW9yBtJQMseEXm7l04Enpi+yAxZulLq9Vk=.sha256"
|
"previous": "&HXCdDG8gGYXElTyEFbg85jqa6lDXNL2ENPIA9UoJNbI=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
async function main() {
|
async function main() {
|
||||||
print(core.url);
|
let host = core.url.match(/.*\/\/(.*?)\//)[1];
|
||||||
let host = core.url.match(/.*?\/\/([^:/]*)/)[1];
|
let id = (await ssb.getServerIdentity()).substring(1);
|
||||||
let port = await ssb.port();
|
let room = `net:${host}:${ssb.port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
|
||||||
let id = (await ssb.getServerIdentity()).substring(1).split('.')[0];
|
|
||||||
let room = `net:${host}:${port}~shs:${id}:SSB+Room+PSK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
|
|
||||||
await app.setDocument(`
|
await app.setDocument(`
|
||||||
<body style="color: #fff">
|
<body style="color: #fff">
|
||||||
<h1>Server</h1>
|
<h1>Server</h1>
|
||||||
|
|||||||
42
apps/sneaker/lit-all.min.js
vendored
42
apps/sneaker/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🦀",
|
"emoji": "🦀",
|
||||||
"previous": "&PrgfYYKpL2tedApJXokIIk+h9/yRYo0aUliNF3QsnZ4=.sha256"
|
"previous": "&jAAzd36Nmpw0sRA1Dx9wLiIwGX+q//+S/Han+RLlEOw=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import * as tfrpc from '/tfrpc.js';
|
|||||||
|
|
||||||
let g_database;
|
let g_database;
|
||||||
let g_hash;
|
let g_hash;
|
||||||
let g_sql_cache = {};
|
|
||||||
|
|
||||||
tfrpc.register(async function localStorageGet(key) {
|
tfrpc.register(async function localStorageGet(key) {
|
||||||
return app.localStorageGet(key);
|
return app.localStorageGet(key);
|
||||||
@@ -52,40 +51,11 @@ tfrpc.register(async function connect(token) {
|
|||||||
tfrpc.register(async function closeConnection(id) {
|
tfrpc.register(async function closeConnection(id) {
|
||||||
await ssb.closeConnection(id);
|
await ssb.closeConnection(id);
|
||||||
});
|
});
|
||||||
tfrpc.register(async function query(sql, args, options) {
|
tfrpc.register(async function query(sql, args) {
|
||||||
let start = new Date();
|
|
||||||
let result = [];
|
let result = [];
|
||||||
let key = options?.cacheable ? JSON.stringify([sql, args]) : undefined;
|
await ssb.sqlAsync(sql, args, function callback(row) {
|
||||||
let entry = key ? g_sql_cache[key] : undefined;
|
result.push(row);
|
||||||
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;
|
return result;
|
||||||
});
|
});
|
||||||
tfrpc.register(async function appendMessage(id, message) {
|
tfrpc.register(async function appendMessage(id, message) {
|
||||||
@@ -106,9 +76,6 @@ tfrpc.register(function setHash(hash) {
|
|||||||
core.register('onMessage', async function (id) {
|
core.register('onMessage', async function (id) {
|
||||||
await tfrpc.rpc.notifyNewMessage(id);
|
await tfrpc.rpc.notifyNewMessage(id);
|
||||||
});
|
});
|
||||||
core.register('onBlob', async function (id) {
|
|
||||||
await tfrpc.rpc.notifyNewBlob(id);
|
|
||||||
});
|
|
||||||
tfrpc.register(async function store_blob(blob) {
|
tfrpc.register(async function store_blob(blob) {
|
||||||
if (Array.isArray(blob)) {
|
if (Array.isArray(blob)) {
|
||||||
blob = Uint8Array.from(blob);
|
blob = Uint8Array.from(blob);
|
||||||
@@ -139,15 +106,6 @@ tfrpc.register(async function sync() {
|
|||||||
tfrpc.register(async function url() {
|
tfrpc.register(async function url() {
|
||||||
return core.url;
|
return core.url;
|
||||||
});
|
});
|
||||||
tfrpc.register(async function globalSettingsGet(key) {
|
|
||||||
return core.globalSettingsGet(key);
|
|
||||||
});
|
|
||||||
tfrpc.register(async function globalSettingsSet(key, value) {
|
|
||||||
return core.globalSettingsSet(key, value);
|
|
||||||
});
|
|
||||||
tfrpc.register(function isAdministrator() {
|
|
||||||
return core.user?.credentials?.permissions?.administration;
|
|
||||||
});
|
|
||||||
|
|
||||||
core.register('onBroadcastsChanged', async function () {
|
core.register('onBroadcastsChanged', async function () {
|
||||||
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
|
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
import {html, render} from './lit-all.min.js';
|
import {html, render} from './lit-all.min.js';
|
||||||
import {styles, generate_theme} from './tf-styles.js';
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
let g_emojis;
|
let g_emojis;
|
||||||
|
|
||||||
@@ -14,8 +14,23 @@ function get_emojis() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function picker(callback, anchor, author, recent) {
|
async function get_recent(author) {
|
||||||
|
let recent = await tfrpc.rpc.query(
|
||||||
|
`
|
||||||
|
SELECT DISTINCT content ->> '$.vote.expression' AS value
|
||||||
|
FROM messages
|
||||||
|
WHERE author = ? AND
|
||||||
|
content ->> '$.type' = 'vote'
|
||||||
|
ORDER BY timestamp DESC LIMIT 10
|
||||||
|
`,
|
||||||
|
[author]
|
||||||
|
);
|
||||||
|
return recent.map((x) => x.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function picker(callback, anchor, author) {
|
||||||
let json = await get_emojis();
|
let json = await get_emojis();
|
||||||
|
let recent = await get_recent(author);
|
||||||
|
|
||||||
let div = document.createElement('div');
|
let div = document.createElement('div');
|
||||||
div.id = 'emoji_picker';
|
div.id = 'emoji_picker';
|
||||||
@@ -140,9 +155,6 @@ export async function picker(callback, anchor, author, recent) {
|
|||||||
<style>
|
<style>
|
||||||
${styles}
|
${styles}
|
||||||
</style>
|
</style>
|
||||||
<style>
|
|
||||||
${generate_theme()}
|
|
||||||
</style>
|
|
||||||
<div
|
<div
|
||||||
class="w3-modal"
|
class="w3-modal"
|
||||||
style="display: block; box-sizing: border-box; z-index: 10"
|
style="display: block; box-sizing: border-box; z-index: 10"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
42
apps/ssb/lit-all.min.js
vendored
42
apps/ssb/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -12,14 +12,12 @@ import * as tf_tab_news from './tf-tab-news.js';
|
|||||||
import * as tf_tab_news_feed from './tf-tab-news-feed.js';
|
import * as tf_tab_news_feed from './tf-tab-news-feed.js';
|
||||||
import * as tf_tab_search from './tf-tab-search.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_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_tag from './tf-tag.js';
|
||||||
import * as tf_styles from './tf-styles.js';
|
import * as tf_styles from './tf-styles.js';
|
||||||
|
|
||||||
window.addEventListener('load', function () {
|
window.addEventListener('load', function () {
|
||||||
let style = document.createElement('style');
|
let style = document.createElement('style');
|
||||||
style.innerText = tf_styles.styles;
|
style.innerText = tf_styles.styles;
|
||||||
Promise.resolve(tf_styles.generate_theme()).then(function (x) {
|
|
||||||
style.innerText += x;
|
|
||||||
});
|
|
||||||
document.body.appendChild(style);
|
document.body.appendChild(style);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {LitElement, html, css, guard, until} from './lit-all.min.js';
|
import {LitElement, html, css, guard, until} from './lit-all.min.js';
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
import {styles, generate_theme} from './tf-styles.js';
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
class TfElement extends LitElement {
|
class TfElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@@ -11,7 +11,6 @@ class TfElement extends LitElement {
|
|||||||
broadcasts: {type: Array},
|
broadcasts: {type: Array},
|
||||||
connections: {type: Array},
|
connections: {type: Array},
|
||||||
loading: {type: Boolean},
|
loading: {type: Boolean},
|
||||||
loading_about: {type: Number},
|
|
||||||
loaded: {type: Boolean},
|
loaded: {type: Boolean},
|
||||||
following: {type: Array},
|
following: {type: Array},
|
||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
@@ -21,13 +20,7 @@ class TfElement extends LitElement {
|
|||||||
channels_latest: {type: Object},
|
channels_latest: {type: Object},
|
||||||
guest: {type: Boolean},
|
guest: {type: Boolean},
|
||||||
url: {type: String},
|
url: {type: String},
|
||||||
private_closed: {type: Object},
|
|
||||||
private_messages: {type: Array},
|
private_messages: {type: Array},
|
||||||
grouped_private_messages: {type: Object},
|
|
||||||
recent_reactions: {type: Array},
|
|
||||||
is_administrator: {type: Boolean},
|
|
||||||
stay_connected: {type: Boolean},
|
|
||||||
progress: {type: Number},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,14 +36,11 @@ class TfElement extends LitElement {
|
|||||||
this.following = [];
|
this.following = [];
|
||||||
this.users = {};
|
this.users = {};
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.loading_about = 0;
|
|
||||||
this.channels = [];
|
this.channels = [];
|
||||||
this.channels_unread = {};
|
this.channels_unread = {};
|
||||||
this.channels_latest = {};
|
this.channels_latest = {};
|
||||||
this.loading_latest = 0;
|
this.loading_latest = 0;
|
||||||
this.loading_latest_scheduled = 0;
|
this.loading_latest_scheduled = 0;
|
||||||
this.recent_reactions = [];
|
|
||||||
this.private_closed = {};
|
|
||||||
tfrpc.rpc.getBroadcasts().then((b) => {
|
tfrpc.rpc.getBroadcasts().then((b) => {
|
||||||
self.broadcasts = b || [];
|
self.broadcasts = b || [];
|
||||||
});
|
});
|
||||||
@@ -60,22 +50,10 @@ class TfElement extends LitElement {
|
|||||||
tfrpc.rpc.getHash().then((hash) => self.set_hash(hash));
|
tfrpc.rpc.getHash().then((hash) => self.set_hash(hash));
|
||||||
tfrpc.register(function hashChanged(hash) {
|
tfrpc.register(function hashChanged(hash) {
|
||||||
self.set_hash(hash);
|
self.set_hash(hash);
|
||||||
self.reset_progress();
|
|
||||||
});
|
});
|
||||||
tfrpc.register(async function notifyNewMessage(id) {
|
tfrpc.register(async function notifyNewMessage(id) {
|
||||||
await self.fetch_new_message(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) {
|
tfrpc.register(function set(name, value) {
|
||||||
if (name === 'broadcasts') {
|
if (name === 'broadcasts') {
|
||||||
self.broadcasts = value;
|
self.broadcasts = value;
|
||||||
@@ -91,44 +69,13 @@ class TfElement extends LitElement {
|
|||||||
async initial_load() {
|
async initial_load() {
|
||||||
let whoami = await tfrpc.rpc.getActiveIdentity();
|
let whoami = await tfrpc.rpc.getActiveIdentity();
|
||||||
let ids = (await tfrpc.rpc.getIdentities()) || [];
|
let ids = (await tfrpc.rpc.getIdentities()) || [];
|
||||||
this.is_administrator = await tfrpc.rpc.isAdministrator();
|
|
||||||
this.stay_connected =
|
|
||||||
this.is_administrator &&
|
|
||||||
(await tfrpc.rpc.globalSettingsGet('stay_connected'));
|
|
||||||
this.url = await tfrpc.rpc.url();
|
this.url = await tfrpc.rpc.url();
|
||||||
this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
|
this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
|
||||||
this.guest = !this.whoami?.length;
|
this.guest = !this.whoami?.length;
|
||||||
this.ids = ids;
|
this.ids = ids;
|
||||||
let private_closed =
|
|
||||||
(await tfrpc.rpc.databaseGet('private_closed')) ?? '{}';
|
|
||||||
this.private_closed = JSON.parse(private_closed);
|
|
||||||
await this.load_channels();
|
await this.load_channels();
|
||||||
}
|
}
|
||||||
|
|
||||||
async open_private_chat(event) {
|
|
||||||
let update = {};
|
|
||||||
update[event.detail.key] = false;
|
|
||||||
this.private_closed = Object.assign(this.private_closed, update);
|
|
||||||
await tfrpc.rpc.databaseSet(
|
|
||||||
'private_closed',
|
|
||||||
JSON.stringify(this.private_closed)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async close_private_chat(event) {
|
|
||||||
let update = {};
|
|
||||||
update[
|
|
||||||
event.detail.key == '[]'
|
|
||||||
? JSON.stringify([this.whoami])
|
|
||||||
: event.detail.key
|
|
||||||
] = true;
|
|
||||||
this.private_closed = Object.assign(this.private_closed, update);
|
|
||||||
await tfrpc.rpc.databaseSet(
|
|
||||||
'private_closed',
|
|
||||||
JSON.stringify(this.private_closed)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async load_channels() {
|
async load_channels() {
|
||||||
let channels = await tfrpc.rpc.query(
|
let channels = await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
@@ -176,53 +123,16 @@ class TfElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
visible_private() {
|
|
||||||
if (!this.grouped_private_messages || !this.private_closed) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
let self = this;
|
|
||||||
let self_key = JSON.stringify([this.whoami]);
|
|
||||||
let opened = Object.entries(this.private_closed)
|
|
||||||
.filter(([key, value]) => !value)
|
|
||||||
.map(([key, value]) => [key, []]);
|
|
||||||
return Object.fromEntries(
|
|
||||||
[...Object.entries(this.grouped_private_messages), ...opened].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) {
|
next_channel(delta) {
|
||||||
let channel_names = [
|
let channel_names = ['', '@', '🔐', ...this.channels.map((x) => '#' + x)];
|
||||||
'',
|
let index = channel_names.indexOf(this.hash.substring(1));
|
||||||
'@',
|
|
||||||
'👍',
|
|
||||||
'🚩',
|
|
||||||
...Object.keys(this.visible_private())
|
|
||||||
.sort()
|
|
||||||
.map((x) => '🔐' + JSON.parse(x).join(',')),
|
|
||||||
...this.channels.map((x) => '#' + x),
|
|
||||||
];
|
|
||||||
let lookup = this.hash.substring(1);
|
|
||||||
if (lookup == '🔐') {
|
|
||||||
lookup = '🔐' + this.whoami;
|
|
||||||
}
|
|
||||||
let index = channel_names.indexOf(lookup);
|
|
||||||
index = index != -1 ? index + delta : 0;
|
index = index != -1 ? index + delta : 0;
|
||||||
let name =
|
tfrpc.rpc.setHash(
|
||||||
channel_names[(index + channel_names.length) % channel_names.length];
|
'#' +
|
||||||
if (name == '🔐' + this.whoami) {
|
encodeURIComponent(
|
||||||
name = '🔐';
|
channel_names[(index + channel_names.length) % channel_names.length]
|
||||||
}
|
)
|
||||||
tfrpc.rpc.setHash('#' + encodeURIComponent(name));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
set_hash(hash) {
|
set_hash(hash) {
|
||||||
@@ -231,15 +141,16 @@ class TfElement extends LitElement {
|
|||||||
this.tab = 'search';
|
this.tab = 'search';
|
||||||
} else if (this.hash === '#connections') {
|
} else if (this.hash === '#connections') {
|
||||||
this.tab = 'connections';
|
this.tab = 'connections';
|
||||||
|
} else if (this.hash.startsWith('#sql=')) {
|
||||||
|
this.tab = 'query';
|
||||||
} else {
|
} else {
|
||||||
this.tab = 'news';
|
this.tab = 'news';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetch_about(following, users, transient) {
|
async fetch_about(following, users) {
|
||||||
this.loading_about++;
|
|
||||||
let ids = Object.keys(following).sort();
|
let ids = Object.keys(following).sort();
|
||||||
const k_cache_version = 3;
|
const k_cache_version = 1;
|
||||||
let cache = await tfrpc.rpc.databaseGet('about');
|
let cache = await tfrpc.rpc.databaseGet('about');
|
||||||
let original_cache = cache;
|
let original_cache = cache;
|
||||||
cache = cache ? JSON.parse(cache) : {};
|
cache = cache ? JSON.parse(cache) : {};
|
||||||
@@ -247,86 +158,81 @@ class TfElement extends LitElement {
|
|||||||
cache = {
|
cache = {
|
||||||
version: k_cache_version,
|
version: k_cache_version,
|
||||||
about: {},
|
about: {},
|
||||||
|
last_row_id: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
let max_row_id = (
|
||||||
let ids_out_of_date = ids.filter(
|
await tfrpc.rpc.query(
|
||||||
(x) =>
|
`
|
||||||
(users[x]?.seq && !cache.about[x]?.seq) ||
|
SELECT MAX(rowid) AS max_row_id FROM messages
|
||||||
(users[x]?.seq && users[x]?.seq > cache.about[x].seq)
|
`,
|
||||||
);
|
[]
|
||||||
|
)
|
||||||
|
)[0].max_row_id;
|
||||||
for (let id of Object.keys(cache.about)) {
|
for (let id of Object.keys(cache.about)) {
|
||||||
if (ids.indexOf(id) == -1) {
|
if (ids.indexOf(id) == -1) {
|
||||||
delete cache.about[id];
|
delete cache.about[id];
|
||||||
} else {
|
|
||||||
users[id] = Object.assign(cache.about[id], users[id] || {});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
let abouts = await tfrpc.rpc.query(
|
||||||
'loading about for',
|
`
|
||||||
ids.length,
|
SELECT
|
||||||
'accounts',
|
messages.author, json(messages.content) AS content, messages.sequence
|
||||||
ids_out_of_date.length,
|
FROM
|
||||||
'out of date'
|
messages,
|
||||||
|
json_each(?1) AS following
|
||||||
|
WHERE
|
||||||
|
messages.author = following.value AND
|
||||||
|
messages.rowid > ?3 AND
|
||||||
|
messages.rowid <= ?4 AND
|
||||||
|
json_extract(messages.content, '$.type') = 'about'
|
||||||
|
UNION
|
||||||
|
SELECT
|
||||||
|
messages.author, json(messages.content) AS content, messages.sequence
|
||||||
|
FROM
|
||||||
|
messages,
|
||||||
|
json_each(?2) AS following
|
||||||
|
WHERE
|
||||||
|
messages.author = following.value AND
|
||||||
|
messages.rowid <= ?4 AND
|
||||||
|
json_extract(messages.content, '$.type') = 'about'
|
||||||
|
ORDER BY messages.author, messages.sequence
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
JSON.stringify(ids.filter((id) => cache.about[id])),
|
||||||
|
JSON.stringify(ids.filter((id) => !cache.about[id])),
|
||||||
|
cache.last_row_id,
|
||||||
|
max_row_id,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
if (ids_out_of_date.length) {
|
for (let about of abouts) {
|
||||||
try {
|
let content = JSON.parse(about.content);
|
||||||
let rows = await tfrpc.rpc.query(
|
if (content.about === about.author) {
|
||||||
`
|
delete content.type;
|
||||||
SELECT all_abouts.author, json(json_group_object(all_abouts.key, all_abouts.value)) AS about
|
delete content.about;
|
||||||
FROM (
|
cache.about[about.author] = Object.assign(
|
||||||
SELECT
|
cache.about[about.author] || {},
|
||||||
messages.author,
|
content
|
||||||
fields.key,
|
|
||||||
RANK() OVER (PARTITION BY messages.author, fields.key ORDER BY messages.sequence DESC) AS rank,
|
|
||||||
fields.value
|
|
||||||
FROM messages JOIN json_each(messages.content) AS fields
|
|
||||||
WHERE
|
|
||||||
messages.content ->> 'type' = 'about' AND
|
|
||||||
messages.content ->> '$.about' = messages.author AND
|
|
||||||
NOT fields.key IN ('about', 'type')) all_abouts
|
|
||||||
JOIN json_each(?) AS following ON all_abouts.author = following.value
|
|
||||||
WHERE rank = 1
|
|
||||||
GROUP BY all_abouts.author
|
|
||||||
`,
|
|
||||||
[JSON.stringify(ids_out_of_date)]
|
|
||||||
);
|
);
|
||||||
users = users || {};
|
|
||||||
for (let row of rows) {
|
|
||||||
users[row.author] = Object.assign(
|
|
||||||
users[row.author] || {},
|
|
||||||
JSON.parse(row.about)
|
|
||||||
);
|
|
||||||
cache.about[row.author] = Object.assign(
|
|
||||||
{seq: users[row.author].seq},
|
|
||||||
JSON.parse(row.about)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cache.last_row_id = max_row_id;
|
||||||
for (let id of ids_out_of_date) {
|
|
||||||
if (!cache.about[id]?.seq) {
|
|
||||||
cache.about[id] = Object.assign(cache.about[id] ?? {}, {
|
|
||||||
seq: users[id]?.seq ?? 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loading_about--;
|
|
||||||
|
|
||||||
let new_cache = JSON.stringify(cache);
|
let new_cache = JSON.stringify(cache);
|
||||||
if (!transient && new_cache != original_cache) {
|
if (new_cache !== original_cache) {
|
||||||
let start_time = new Date();
|
let start_time = new Date();
|
||||||
tfrpc.rpc.databaseSet('about', new_cache).then(function () {
|
tfrpc.rpc.databaseSet('about', new_cache).then(function () {
|
||||||
console.log('saving about took', (new Date() - start_time) / 1000);
|
console.log('saving about took', (new Date() - start_time) / 1000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
users = users || {};
|
||||||
|
for (let id of Object.keys(cache.about)) {
|
||||||
|
users[id] = Object.assign(
|
||||||
|
{follow_depth: following[id]?.d},
|
||||||
|
users[id] || {},
|
||||||
|
cache.about[id]
|
||||||
|
);
|
||||||
|
}
|
||||||
return Object.assign({}, users);
|
return Object.assign({}, users);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,7 +292,11 @@ class TfElement extends LitElement {
|
|||||||
ranges.push([i, Math.min(i + k_chunk_size, latest), true]);
|
ranges.push([i, Math.min(i + k_chunk_size, latest), true]);
|
||||||
}
|
}
|
||||||
for (let i = cache.range[0]; i >= 0; i -= k_chunk_size) {
|
for (let i = cache.range[0]; i >= 0; i -= k_chunk_size) {
|
||||||
ranges.push([Math.max(i - k_chunk_size, 0), i, false]);
|
ranges.push([
|
||||||
|
Math.max(i - k_chunk_size, 0),
|
||||||
|
Math.min(cache.range[0], i + k_chunk_size),
|
||||||
|
false,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < latest; i += k_chunk_size) {
|
for (let i = 0; i < latest; i += k_chunk_size) {
|
||||||
@@ -402,7 +312,7 @@ class TfElement extends LitElement {
|
|||||||
messages.rowid > ?1 AND
|
messages.rowid > ?1 AND
|
||||||
messages.rowid <= ?2 AND
|
messages.rowid <= ?2 AND
|
||||||
json(messages.content) LIKE '"%'
|
json(messages.content) LIKE '"%'
|
||||||
ORDER BY messages.rowid DESC
|
ORDER BY sequence DESC
|
||||||
`,
|
`,
|
||||||
[range[0], range[1]]
|
[range[0], range[1]]
|
||||||
);
|
);
|
||||||
@@ -428,119 +338,52 @@ class TfElement extends LitElement {
|
|||||||
return [cache.latest, cache.messages];
|
return [cache.latest, cache.messages];
|
||||||
}
|
}
|
||||||
|
|
||||||
async group_private_messages(messages) {
|
|
||||||
let groups = {};
|
|
||||||
let result = await this.decrypt(
|
|
||||||
await tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
SELECT messages.rowid, messages.id, author, timestamp, json(content) AS content
|
|
||||||
FROM messages
|
|
||||||
JOIN json_each(?) AS ids
|
|
||||||
WHERE messages.id = ids.value
|
|
||||||
ORDER BY timestamp DESC
|
|
||||||
`,
|
|
||||||
[JSON.stringify(messages)]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
for (let message of result) {
|
|
||||||
let key = JSON.stringify(
|
|
||||||
[
|
|
||||||
...new Set(
|
|
||||||
message?.decrypted?.recps?.filter((x) => x != this.whoami)
|
|
||||||
),
|
|
||||||
].sort() ?? []
|
|
||||||
);
|
|
||||||
if (!groups[key]) {
|
|
||||||
groups[key] = [];
|
|
||||||
}
|
|
||||||
groups[key].push(message);
|
|
||||||
}
|
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
async load_channels_latest(following) {
|
async load_channels_latest(following) {
|
||||||
|
let start_time = new Date();
|
||||||
let latest_private = this.get_latest_private(following);
|
let latest_private = this.get_latest_private(following);
|
||||||
const k_args = [
|
let channels = await tfrpc.rpc.query(
|
||||||
JSON.stringify(this.channels),
|
`
|
||||||
JSON.stringify(following),
|
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
|
||||||
'"' + this.whoami.replace('"', '""') + '"',
|
JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value
|
||||||
this.whoami,
|
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||||
];
|
WHERE
|
||||||
let channels = (
|
messages.content ->> 'type' = 'post' AND
|
||||||
await Promise.all([
|
messages.content ->> 'root' IS NULL AND
|
||||||
tfrpc.rpc.query(
|
messages.author != ?4
|
||||||
`
|
GROUP by channel
|
||||||
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
|
UNION
|
||||||
JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value
|
SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages
|
||||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||||
WHERE
|
WHERE
|
||||||
messages.content ->> 'type' = 'post' AND
|
messages.content ->> 'type' = 'post' AND
|
||||||
messages.content ->> 'root' IS NULL AND
|
messages.content ->> 'root' IS NULL AND
|
||||||
messages.author != ?4
|
messages.author != ?4
|
||||||
GROUP by channel
|
UNION
|
||||||
`,
|
SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3)
|
||||||
k_args
|
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||||
),
|
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||||
tfrpc.rpc.query(
|
WHERE messages.author != ?4
|
||||||
`
|
`,
|
||||||
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
|
[
|
||||||
JOIN messages_refs ON messages.id = messages_refs.message
|
JSON.stringify(this.channels),
|
||||||
JOIN json_each(?1) AS channels ON messages_refs.ref = '#' || channels.value
|
JSON.stringify(following),
|
||||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
'"' + this.whoami.replace('"', '""') + '"',
|
||||||
WHERE
|
this.whoami,
|
||||||
messages.content ->> 'type' = 'post' AND
|
]
|
||||||
messages.content ->> 'root' IS NULL AND
|
);
|
||||||
messages.author != ?4
|
this.channels_latest = Object.fromEntries(
|
||||||
GROUP by channel
|
channels.map((x) => [x.channel, x.rowid])
|
||||||
`,
|
);
|
||||||
k_args
|
console.log('channels took', (new Date() - start_time) / 1000.0);
|
||||||
),
|
|
||||||
tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages
|
|
||||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
|
||||||
WHERE
|
|
||||||
messages.content ->> 'type' = 'post' AND
|
|
||||||
messages.content ->> 'root' IS NULL AND
|
|
||||||
messages.author != ?4
|
|
||||||
`,
|
|
||||||
k_args
|
|
||||||
),
|
|
||||||
tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3)
|
|
||||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
|
||||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
|
||||||
WHERE messages.author != ?4
|
|
||||||
`,
|
|
||||||
k_args
|
|
||||||
),
|
|
||||||
tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
SELECT '🚩' AS channel, MAX(messages.rowid) AS rowid FROM messages
|
|
||||||
WHERE messages.content ->> 'type' = 'flag'
|
|
||||||
`,
|
|
||||||
k_args
|
|
||||||
),
|
|
||||||
])
|
|
||||||
).flat();
|
|
||||||
let latest = {'🔐': undefined};
|
|
||||||
for (let row of channels) {
|
|
||||||
if (!latest[row.channel]) {
|
|
||||||
latest[row.channel] = row.rowid;
|
|
||||||
} else {
|
|
||||||
latest[row.channel] = Math.max(row.rowid, latest[row.channel]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.channels_latest = latest;
|
|
||||||
let self = this;
|
let self = this;
|
||||||
latest_private.then(async function (latest) {
|
start_time = new Date();
|
||||||
let grouped = await self.group_private_messages(latest[1]);
|
latest_private.then(function (latest) {
|
||||||
self.channels_latest = Object.assign({}, self.channels_latest, {
|
self.channels_latest = Object.assign({}, self.channels_latest, {
|
||||||
'🔐': latest[0],
|
'🔐': latest[0],
|
||||||
});
|
});
|
||||||
|
console.log('private took', (new Date() - start_time) / 1000.0);
|
||||||
|
console.log(latest);
|
||||||
self.private_messages = latest[1];
|
self.private_messages = latest[1];
|
||||||
self.grouped_private_messages = grouped;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,28 +392,7 @@ class TfElement extends LitElement {
|
|||||||
this.schedule_load_latest();
|
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() {
|
schedule_load_latest() {
|
||||||
this.reset_progress();
|
|
||||||
if (!this.loading_latest) {
|
if (!this.loading_latest) {
|
||||||
this.shadowRoot.getElementById('tf-tab-news')?.load_latest();
|
this.shadowRoot.getElementById('tf-tab-news')?.load_latest();
|
||||||
this.load();
|
this.load();
|
||||||
@@ -590,66 +412,54 @@ class TfElement extends LitElement {
|
|||||||
[JSON.stringify(Object.keys(users))]
|
[JSON.stringify(Object.keys(users))]
|
||||||
);
|
);
|
||||||
for (let row of info) {
|
for (let row of info) {
|
||||||
users[row.author] = Object.assign(users[row.author], {
|
users[row.author].seq = row.max_seq;
|
||||||
seq: row.max_sequence,
|
users[row.author].ts = row.max_ts;
|
||||||
ts: row.max_ts,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
async load_recent_reactions() {
|
|
||||||
this.recent_reactions = (
|
|
||||||
await tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
SELECT DISTINCT content ->> '$.vote.expression' AS value
|
|
||||||
FROM messages
|
|
||||||
WHERE author = ? AND
|
|
||||||
content ->> 'type' = 'vote'
|
|
||||||
ORDER BY timestamp DESC LIMIT 10
|
|
||||||
`,
|
|
||||||
[this.whoami]
|
|
||||||
)
|
|
||||||
).map((x) => x.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
this.loading_latest = true;
|
this.loading_latest = true;
|
||||||
this.reset_progress();
|
|
||||||
try {
|
try {
|
||||||
|
let start_time = new Date();
|
||||||
let whoami = this.whoami;
|
let whoami = this.whoami;
|
||||||
let following = await tfrpc.rpc.following([whoami], 2);
|
let following = await tfrpc.rpc.following([whoami], 2);
|
||||||
let old_users = this.users ?? {};
|
|
||||||
let users = {};
|
let users = {};
|
||||||
let by_count = [];
|
let by_count = [];
|
||||||
for (let [id, v] of Object.entries(following)) {
|
for (let [id, v] of Object.entries(following)) {
|
||||||
users[id] = Object.assign(
|
users[id] = {
|
||||||
{
|
following: v.of,
|
||||||
following: v.of,
|
blocking: v.ob,
|
||||||
blocking: v.ob,
|
followed: v.if,
|
||||||
followed: v.if,
|
blocked: v.ib,
|
||||||
blocked: v.ib,
|
};
|
||||||
follow_depth: following[id]?.d,
|
|
||||||
},
|
|
||||||
old_users[id]
|
|
||||||
);
|
|
||||||
by_count.push({count: v.of, id: id});
|
by_count.push({count: v.of, id: id});
|
||||||
}
|
}
|
||||||
let reactions = this.load_recent_reactions();
|
this.load_channels_latest(Object.keys(following));
|
||||||
let channels = this.load_channels_latest(Object.keys(following));
|
|
||||||
this.channels_unread = JSON.parse(
|
this.channels_unread = JSON.parse(
|
||||||
(await tfrpc.rpc.databaseGet('unread')) ?? '{}'
|
(await tfrpc.rpc.databaseGet('unread')) ?? '{}'
|
||||||
);
|
);
|
||||||
this.following = Object.keys(following);
|
this.following = Object.keys(following);
|
||||||
|
let about_start_time = new Date();
|
||||||
|
users = await this.fetch_about(following, users);
|
||||||
|
console.log(
|
||||||
|
'about took',
|
||||||
|
(new Date() - about_start_time) / 1000.0,
|
||||||
|
'seconds for',
|
||||||
|
Object.keys(users).length,
|
||||||
|
'users'
|
||||||
|
);
|
||||||
|
start_time = new Date();
|
||||||
users = await this.fetch_user_info(users);
|
users = await this.fetch_user_info(users);
|
||||||
|
console.log(
|
||||||
|
'user info took',
|
||||||
|
(new Date() - start_time) / 1000.0,
|
||||||
|
'seconds'
|
||||||
|
);
|
||||||
this.users = users;
|
this.users = users;
|
||||||
|
console.log(
|
||||||
let self = this;
|
`load finished ${whoami} => ${this.whoami} in ${(new Date() - start_time) / 1000}`
|
||||||
this.fetch_about(following, users).then(function (result) {
|
);
|
||||||
self.users = result;
|
|
||||||
});
|
|
||||||
await reactions;
|
|
||||||
await channels;
|
|
||||||
this.whoami = whoami;
|
this.whoami = whoami;
|
||||||
this.loaded = whoami;
|
this.loaded = whoami;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -700,23 +510,13 @@ class TfElement extends LitElement {
|
|||||||
whoami=${this.whoami}
|
whoami=${this.whoami}
|
||||||
.users=${this.users}
|
.users=${this.users}
|
||||||
hash=${this.hash}
|
hash=${this.hash}
|
||||||
?loading=${this.loading || this.loading_about != 0}
|
?loading=${this.loading}
|
||||||
.channels=${this.channels}
|
.channels=${this.channels}
|
||||||
.channels_latest=${this.channels_latest}
|
.channels_latest=${this.channels_latest}
|
||||||
.channels_unread=${this.channels_unread}
|
.channels_unread=${this.channels_unread}
|
||||||
@channelsetunread=${this.channel_set_unread}
|
@channelsetunread=${this.channel_set_unread}
|
||||||
@refresh=${this.refresh}
|
|
||||||
@toggle_stay_connected=${this.toggle_stay_connected}
|
|
||||||
@loadmessages=${this.reset_progress}
|
|
||||||
@openprivatechat=${this.open_private_chat}
|
|
||||||
@closeprivatechat=${this.close_private_chat}
|
|
||||||
.connections=${this.connections}
|
.connections=${this.connections}
|
||||||
.private_messages=${this.private_messages}
|
.private_messages=${this.private_messages}
|
||||||
.visible_private_messages=${this.visible_private()}
|
|
||||||
.grouped_private_messages=${this.grouped_private_messages}
|
|
||||||
.recent_reactions=${this.recent_reactions}
|
|
||||||
?is_administrator=${this.is_administrator}
|
|
||||||
?stay_connected=${this.stay_connected}
|
|
||||||
></tf-tab-news>
|
></tf-tab-news>
|
||||||
`;
|
`;
|
||||||
} else if (this.tab === 'connections') {
|
} else if (this.tab === 'connections') {
|
||||||
@@ -733,19 +533,33 @@ class TfElement extends LitElement {
|
|||||||
.following=${this.following}
|
.following=${this.following}
|
||||||
whoami=${this.whoami}
|
whoami=${this.whoami}
|
||||||
.users=${this.users}
|
.users=${this.users}
|
||||||
query=${this.search_text()}
|
query=${this.hash?.startsWith('#q=')
|
||||||
|
? decodeURIComponent(this.hash.substring(3))
|
||||||
|
: null}
|
||||||
></tf-tab-search>
|
></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) {
|
async set_tab(tab) {
|
||||||
this.tab = tab;
|
this.tab = tab;
|
||||||
if (tab === 'news') {
|
if (tab === 'news') {
|
||||||
this.schedule_load_latest();
|
|
||||||
await tfrpc.rpc.setHash('#');
|
await tfrpc.rpc.setHash('#');
|
||||||
} else if (tab === 'connections') {
|
} else if (tab === 'connections') {
|
||||||
await tfrpc.rpc.setHash('#connections');
|
await tfrpc.rpc.setHash('#connections');
|
||||||
|
} else if (tab === 'query') {
|
||||||
|
await tfrpc.rpc.setHash('#sql=');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -753,67 +567,6 @@ class TfElement extends LitElement {
|
|||||||
tfrpc.rpc.sync();
|
tfrpc.rpc.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
async toggle_stay_connected() {
|
|
||||||
let stay_connected = await tfrpc.rpc.globalSettingsGet('stay_connected');
|
|
||||||
let new_stay_connected = !this.stay_connected;
|
|
||||||
try {
|
|
||||||
if (new_stay_connected != stay_connected) {
|
|
||||||
await tfrpc.rpc.globalSettingsSet('stay_connected', new_stay_connected);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this.stay_connected = await tfrpc.rpc.globalSettingsGet('stay_connected');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async pick_color() {
|
|
||||||
let input = document.createElement('input');
|
|
||||||
input.type = 'color';
|
|
||||||
input.value = (await tfrpc.rpc.localStorageGet('color')) ?? '#ff0000';
|
|
||||||
input.addEventListener('change', async function () {
|
|
||||||
await tfrpc.rpc.localStorageSet('color', input.value);
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
input.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async request_user(event) {
|
|
||||||
let users = {};
|
|
||||||
users[event.detail.id] = {};
|
|
||||||
users = await this.fetch_user_info(users);
|
|
||||||
if (this.users[event.detail.id]?.seq !== users[event.detail.id]?.seq) {
|
|
||||||
let self = this;
|
|
||||||
this.fetch_about(users, users, true).then(function (result) {
|
|
||||||
self.users = Object.assign({}, self.users, users);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let self = this;
|
let self = this;
|
||||||
|
|
||||||
@@ -827,19 +580,23 @@ class TfElement extends LitElement {
|
|||||||
const k_tabs = {
|
const k_tabs = {
|
||||||
'📰': 'news',
|
'📰': 'news',
|
||||||
'📡': 'connections',
|
'📡': 'connections',
|
||||||
|
'🔍': 'search',
|
||||||
|
'👩💻': 'query',
|
||||||
};
|
};
|
||||||
|
|
||||||
let tabs = html`
|
let tabs = html`
|
||||||
<style>
|
|
||||||
#search_text:focus {
|
|
||||||
float: none !important;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div
|
<div
|
||||||
class="w3-bar w3-theme-l1"
|
class="w3-bar w3-theme-l1"
|
||||||
style="position: static; top: 0; z-index: 10"
|
style="position: static; top: 0; z-index: 10"
|
||||||
>
|
>
|
||||||
|
<button
|
||||||
|
class=${'w3-bar-item w3-button w3-circle w3-ripple' +
|
||||||
|
(this.connections?.some((x) => x.flags.one_shot) ? ' w3-spin' : '')}
|
||||||
|
style="width: 1.5em; height: 1.5em; padding: 8px"
|
||||||
|
@click=${this.refresh}
|
||||||
|
>
|
||||||
|
↻
|
||||||
|
</button>
|
||||||
${Object.entries(k_tabs).map(
|
${Object.entries(k_tabs).map(
|
||||||
([k, v]) => html`
|
([k, v]) => html`
|
||||||
<button
|
<button
|
||||||
@@ -856,39 +613,6 @@ class TfElement extends LitElement {
|
|||||||
</button>
|
</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-right"
|
|
||||||
@click=${this.refresh}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style="display: inline-block"
|
|
||||||
class=${this.connections?.some((x) => x.flags.one_shot)
|
|
||||||
? ' w3-spin'
|
|
||||||
: ''}
|
|
||||||
>
|
|
||||||
↻
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="w3-bar-item w3-button 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>
|
</div>
|
||||||
`;
|
`;
|
||||||
let contents = this.guest
|
let contents = this.guest
|
||||||
@@ -912,27 +636,11 @@ class TfElement extends LitElement {
|
|||||||
Loading...
|
Loading...
|
||||||
</div>`
|
</div>`
|
||||||
: this.render_tab();
|
: 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`
|
return html`
|
||||||
<style>
|
|
||||||
${generate_theme()}
|
|
||||||
</style>
|
|
||||||
<div
|
<div
|
||||||
style="width: 100vw; min-height: 100vh; height: 100vh; display: flex; flex-direction: column"
|
style="width: 100vw; min-height: 100vh; height: 100vh; display: flex; flex-direction: column"
|
||||||
class="w3-theme-dark"
|
class="w3-theme-dark"
|
||||||
@tf-request-user=${this.request_user}
|
|
||||||
>
|
>
|
||||||
${progress}
|
|
||||||
<div style="flex: 0 0">${tabs}</div>
|
<div style="flex: 0 0">${tabs}</div>
|
||||||
<div style="flex: 1 1; overflow: auto; contain: layout">
|
<div style="flex: 1 1; overflow: auto; contain: layout">
|
||||||
${contents}
|
${contents}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {LitElement, html, unsafeHTML, live} from './lit-all.min.js';
|
import {LitElement, html, unsafeHTML, live} from './lit-all.min.js';
|
||||||
import * as tfutils from './tf-utils.js';
|
import * as tfutils from './tf-utils.js';
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
import {styles, generate_theme} from './tf-styles.js';
|
import {styles} from './tf-styles.js';
|
||||||
import Tribute from './tribute.esm.js';
|
import Tribute from './tribute.esm.js';
|
||||||
|
|
||||||
class TfComposeElement extends LitElement {
|
class TfComposeElement extends LitElement {
|
||||||
@@ -16,7 +16,6 @@ class TfComposeElement extends LitElement {
|
|||||||
author: {type: String},
|
author: {type: String},
|
||||||
channel: {type: String},
|
channel: {type: String},
|
||||||
new_thread: {type: Boolean},
|
new_thread: {type: Boolean},
|
||||||
recipients: {type: Array},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,9 +91,7 @@ class TfComposeElement extends LitElement {
|
|||||||
bubbles: true,
|
bubbles: true,
|
||||||
composed: true,
|
composed: true,
|
||||||
detail: {
|
detail: {
|
||||||
id:
|
id: this.branch,
|
||||||
this.branch ??
|
|
||||||
(this.recipients ? this.recipients.join(',') : undefined),
|
|
||||||
draft: draft,
|
draft: draft,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -258,12 +255,10 @@ class TfComposeElement extends LitElement {
|
|||||||
let self = this;
|
let self = this;
|
||||||
let input = document.createElement('input');
|
let input = document.createElement('input');
|
||||||
input.type = 'file';
|
input.type = 'file';
|
||||||
input.addEventListener('change', function (event) {
|
input.onchange = function (event) {
|
||||||
input.parentNode.removeChild(input);
|
|
||||||
let file = event.target.files[0];
|
let file = event.target.files[0];
|
||||||
self.add_file(file);
|
self.add_file(file);
|
||||||
});
|
};
|
||||||
document.body.appendChild(input);
|
|
||||||
input.click();
|
input.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +289,7 @@ class TfComposeElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get_values() {
|
firstUpdated() {
|
||||||
let values = Object.entries(this.users).map((x) => ({
|
let values = Object.entries(this.users).map((x) => ({
|
||||||
key: x[1].name ?? x[0],
|
key: x[1].name ?? x[0],
|
||||||
value: x[0],
|
value: x[0],
|
||||||
@@ -310,15 +305,11 @@ class TfComposeElement extends LitElement {
|
|||||||
values
|
values
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
firstUpdated() {
|
|
||||||
let tribute = new Tribute({
|
let tribute = new Tribute({
|
||||||
iframe: this.shadowRoot,
|
iframe: this.shadowRoot,
|
||||||
collection: [
|
collection: [
|
||||||
{
|
{
|
||||||
values: this.get_values(),
|
values: values,
|
||||||
selectTemplate: function (item) {
|
selectTemplate: function (item) {
|
||||||
return item
|
return item
|
||||||
? `[@${item.original.key}](${item.original.value})`
|
? `[@${item.original.key}](${item.original.value})`
|
||||||
@@ -337,7 +328,6 @@ class TfComposeElement extends LitElement {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
tribute.attach(this.renderRoot.getElementById('edit'));
|
tribute.attach(this.renderRoot.getElementById('edit'));
|
||||||
this._tribute = tribute;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updated() {
|
updated() {
|
||||||
@@ -348,7 +338,6 @@ class TfComposeElement extends LitElement {
|
|||||||
preview.innerHTML = this.process_text(edit.innerText);
|
preview.innerHTML = this.process_text(edit.innerText);
|
||||||
this.last_updated_text = edit.innerText;
|
this.last_updated_text = edit.innerText;
|
||||||
}
|
}
|
||||||
this._tribute.collection[0].values = this.get_values();
|
|
||||||
let encrypt = this.renderRoot.getElementById('encrypt_to');
|
let encrypt = this.renderRoot.getElementById('encrypt_to');
|
||||||
if (encrypt) {
|
if (encrypt) {
|
||||||
let tribute = new Tribute({
|
let tribute = new Tribute({
|
||||||
@@ -368,7 +357,7 @@ class TfComposeElement extends LitElement {
|
|||||||
remove_mention(id) {
|
remove_mention(id) {
|
||||||
let draft = this.get_draft();
|
let draft = this.get_draft();
|
||||||
delete draft.mentions[id];
|
delete draft.mentions[id];
|
||||||
setTimeout(() => this.notify(draft), 0);
|
setTimeout(() => this.notify(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
render_mention(mention) {
|
render_mention(mention) {
|
||||||
@@ -455,15 +444,12 @@ class TfComposeElement extends LitElement {
|
|||||||
self.apps = await tfrpc.rpc.apps();
|
self.apps = await tfrpc.rpc.apps();
|
||||||
}
|
}
|
||||||
if (!this.apps) {
|
if (!this.apps) {
|
||||||
return html`<button
|
return html`<button class="w3-button w3-theme-d1" @click=${attach_app}>
|
||||||
class="w3-button w3-bar-item w3-theme-d1"
|
|
||||||
@click=${attach_app}
|
|
||||||
>
|
|
||||||
Attach App
|
Attach App
|
||||||
</button>`;
|
</button>`;
|
||||||
} else {
|
} else {
|
||||||
return html`<button
|
return html`<button
|
||||||
class="w3-button w3-bar-item w3-theme-d1"
|
class="w3-button w3-theme-d1"
|
||||||
@click=${() => (this.apps = null)}
|
@click=${() => (this.apps = null)}
|
||||||
>
|
>
|
||||||
Discard App
|
Discard App
|
||||||
@@ -484,9 +470,18 @@ class TfComposeElement extends LitElement {
|
|||||||
if (draft.content_warning !== undefined) {
|
if (draft.content_warning !== undefined) {
|
||||||
return html`
|
return html`
|
||||||
<div class="w3-container w3-padding">
|
<div class="w3-container w3-padding">
|
||||||
|
<p>
|
||||||
|
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning(undefined)} checked="checked"></input>
|
||||||
|
<label for="cw">CW</label>
|
||||||
|
</p>
|
||||||
<input type="text" class="w3-input w3-border w3-theme-d1" id="content_warning" placeholder="Enter a content warning here." @input=${self.input} value=${draft.content_warning}></input>
|
<input type="text" class="w3-input w3-border w3-theme-d1" id="content_warning" placeholder="Enter a content warning here." @input=${self.input} value=${draft.content_warning}></input>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
} else {
|
||||||
|
return html`
|
||||||
|
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning('')}></input>
|
||||||
|
<label for="cw">CW</label>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,17 +500,7 @@ class TfComposeElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get_draft() {
|
get_draft() {
|
||||||
let key =
|
return this.drafts[this.branch || ''] || {};
|
||||||
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) {
|
update_encrypt(event) {
|
||||||
@@ -537,7 +522,7 @@ class TfComposeElement extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<div style="display: flex; flex-direction: row; width: 100%">
|
<div style="display: flex; flex-direction: row; width: 100%">
|
||||||
<label for="encrypt_to">🔐 To:</label>
|
<label for="encrypt_to">🔐 To:</label>
|
||||||
<input type="text" id="encrypt_to" class="w3-input w3-theme-d1 w3-border" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input>
|
<input type="text" id="encrypt_to" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input>
|
||||||
<button class="w3-button w3-theme-d1" @click=${() => this.set_encrypt(undefined)}>🚮</button>
|
<button class="w3-button w3-theme-d1" @click=${() => this.set_encrypt(undefined)}>🚮</button>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -559,31 +544,6 @@ class TfComposeElement extends LitElement {
|
|||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle_menu(event) {
|
|
||||||
event.srcElement.parentNode
|
|
||||||
.querySelector('.w3-dropdown-content')
|
|
||||||
.classList.toggle('w3-show');
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
this._click_callback = this.document_click.bind(this);
|
|
||||||
document.body.addEventListener('mouseup', this._click_callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnectedCallback() {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
document.body.removeEventListener('mouseup', this._click_callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
document_click(event) {
|
|
||||||
let content = this.renderRoot.querySelector('.w3-dropdown-content');
|
|
||||||
let target = event.target;
|
|
||||||
if (content && !content.contains(target)) {
|
|
||||||
content.classList.remove('w3-show');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let self = this;
|
let self = this;
|
||||||
let draft = self.get_draft();
|
let draft = self.get_draft();
|
||||||
@@ -597,15 +557,12 @@ class TfComposeElement extends LitElement {
|
|||||||
draft.encrypt_to !== undefined
|
draft.encrypt_to !== undefined
|
||||||
? undefined
|
? undefined
|
||||||
: html`<button
|
: html`<button
|
||||||
class="w3-button w3-bar-item w3-theme-d1"
|
class="w3-button w3-theme-d1"
|
||||||
@click=${() => this.set_encrypt([])}
|
@click=${() => this.set_encrypt([])}
|
||||||
>
|
>
|
||||||
🔐 Encrypt
|
🔐
|
||||||
</button>`;
|
</button>`;
|
||||||
let result = html`
|
let result = html`
|
||||||
<style>
|
|
||||||
${generate_theme()}
|
|
||||||
</style>
|
|
||||||
<style>
|
<style>
|
||||||
.w3-input:empty::before {
|
.w3-input:empty::before {
|
||||||
content: attr(placeholder);
|
content: attr(placeholder);
|
||||||
@@ -624,11 +581,11 @@ class TfComposeElement extends LitElement {
|
|||||||
: undefined}
|
: undefined}
|
||||||
${this.render_encrypt()}
|
${this.render_encrypt()}
|
||||||
</header>
|
</header>
|
||||||
<div class="w3-container" style="padding: 0 0 16px 0">
|
<div class="w3-container w3-padding-small">
|
||||||
<div class="w3-half">
|
<div class="w3-half">
|
||||||
<span
|
<span
|
||||||
class="w3-input w3-theme-d1 w3-border"
|
class="w3-input w3-theme-d1 w3-border"
|
||||||
style="resize: vertical; width: 100%; white-space: pre-wrap"
|
style="resize: vertical; width: 100%; overflow: hidden; white-space: pre-wrap"
|
||||||
placeholder="Write a post here."
|
placeholder="Write a post here."
|
||||||
id="edit"
|
id="edit"
|
||||||
@input=${this.input}
|
@input=${this.input}
|
||||||
@@ -645,7 +602,7 @@ class TfComposeElement extends LitElement {
|
|||||||
${Object.values(draft.mentions || {}).map((x) =>
|
${Object.values(draft.mentions || {}).map((x) =>
|
||||||
self.render_mention(x)
|
self.render_mention(x)
|
||||||
)}
|
)}
|
||||||
<footer>
|
<footer class="w3-container">
|
||||||
${this.render_attach_app()} ${this.render_content_warning()}
|
${this.render_attach_app()} ${this.render_content_warning()}
|
||||||
${this.render_new_thread()}
|
${this.render_new_thread()}
|
||||||
<button
|
<button
|
||||||
@@ -655,43 +612,13 @@ class TfComposeElement extends LitElement {
|
|||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
<div class="w3-dropdown-click">
|
<button class="w3-button w3-theme-d1" @click=${this.attach}>
|
||||||
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
|
Attach
|
||||||
⚙️
|
</button>
|
||||||
</button>
|
${this.render_attach_app_button()} ${encrypt}
|
||||||
<div class="w3-dropdown-content w3-bar-block">
|
<button class="w3-button w3-theme-d1" @click=${this.discard}>
|
||||||
${this.get_draft().content_warning === undefined
|
Discard
|
||||||
? html`
|
</button>
|
||||||
<button
|
|
||||||
class="w3-button w3-bar-item w3-theme-d1"
|
|
||||||
@click=${() => self.set_content_warning('')}
|
|
||||||
>
|
|
||||||
Add Content Warning
|
|
||||||
</button>
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<button
|
|
||||||
class="w3-button w3-bar-item w3-theme-d1"
|
|
||||||
@click=${() => self.set_content_warning(undefined)}
|
|
||||||
>
|
|
||||||
Remove Content Warning
|
|
||||||
</button>
|
|
||||||
`}
|
|
||||||
<button
|
|
||||||
class="w3-button w3-bar-item w3-theme-d1"
|
|
||||||
@click=${this.attach}
|
|
||||||
>
|
|
||||||
Attach
|
|
||||||
</button>
|
|
||||||
${this.render_attach_app_button()} ${encrypt}
|
|
||||||
<button
|
|
||||||
class="w3-button w3-bar-item w3-theme-d1"
|
|
||||||
@click=${this.discard}
|
|
||||||
>
|
|
||||||
Discard
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
import {LitElement, html, unsafeHTML, repeat, until} from './lit-all.min.js';
|
import {LitElement, html, unsafeHTML, repeat, until} from './lit-all.min.js';
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
import {styles, generate_theme} from './tf-styles.js';
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
class TfNewsElement extends LitElement {
|
class TfNewsElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@@ -13,8 +13,6 @@ class TfNewsElement extends LitElement {
|
|||||||
expanded: {type: Object},
|
expanded: {type: Object},
|
||||||
channel: {type: String},
|
channel: {type: String},
|
||||||
channel_unread: {type: Number},
|
channel_unread: {type: Number},
|
||||||
recent_reactions: {type: Array},
|
|
||||||
hash: {type: String},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,13 +28,14 @@ class TfNewsElement extends LitElement {
|
|||||||
this.drafts = {};
|
this.drafts = {};
|
||||||
this.expanded = {};
|
this.expanded = {};
|
||||||
this.channel_unread = -1;
|
this.channel_unread = -1;
|
||||||
this.recent_reactions = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process_messages(messages) {
|
process_messages(messages) {
|
||||||
let self = this;
|
let self = this;
|
||||||
let messages_by_id = {};
|
let messages_by_id = {};
|
||||||
|
|
||||||
|
console.log('processing', messages.length, 'messages');
|
||||||
|
|
||||||
function ensure_message(id, rowid) {
|
function ensure_message(id, rowid) {
|
||||||
let found = messages_by_id[id];
|
let found = messages_by_id[id];
|
||||||
if (found) {
|
if (found) {
|
||||||
@@ -57,9 +56,6 @@ class TfNewsElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function link_message(message) {
|
function link_message(message) {
|
||||||
if (!message.content) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (message.content.type === 'vote') {
|
if (message.content.type === 'vote') {
|
||||||
let parent = ensure_message(message.content.vote.link, message.rowid);
|
let parent = ensure_message(message.content.vote.link, message.rowid);
|
||||||
if (!parent.votes) {
|
if (!parent.votes) {
|
||||||
@@ -67,16 +63,6 @@ class TfNewsElement extends LitElement {
|
|||||||
}
|
}
|
||||||
parent.votes.push(message);
|
parent.votes.push(message);
|
||||||
message.parent_message = message.content.vote.link;
|
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') {
|
} else if (message.content.type == 'post') {
|
||||||
if (message.content.root) {
|
if (message.content.root) {
|
||||||
if (typeof message.content.root === 'string') {
|
if (typeof message.content.root === 'string') {
|
||||||
@@ -95,22 +81,6 @@ class TfNewsElement extends LitElement {
|
|||||||
message.parent_message = message.content.root[0];
|
message.parent_message = message.content.root[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
let parent_id = message.content.about?.startswith?.('%')
|
|
||||||
? message.content.about
|
|
||||||
: message.content.updates?.startsWith?.('%')
|
|
||||||
? message.content.updates
|
|
||||||
: message.content.parent?.startsWith?.('%')
|
|
||||||
? message.content.parent
|
|
||||||
: undefined;
|
|
||||||
if (parent_id) {
|
|
||||||
let parent = ensure_message(parent_id, message.rowid);
|
|
||||||
if (!parent?.refs) {
|
|
||||||
parent.refs = [];
|
|
||||||
}
|
|
||||||
parent.refs.push(message);
|
|
||||||
message.parent_message = parent_id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +103,6 @@ class TfNewsElement extends LitElement {
|
|||||||
message.parent_message = placeholder.parent_message;
|
message.parent_message = placeholder.parent_message;
|
||||||
message.child_messages = placeholder.child_messages;
|
message.child_messages = placeholder.child_messages;
|
||||||
message.votes = placeholder.votes;
|
message.votes = placeholder.votes;
|
||||||
message.flags = placeholder.flags;
|
|
||||||
if (
|
if (
|
||||||
placeholder.parent_message &&
|
placeholder.parent_message &&
|
||||||
messages_by_id[placeholder.parent_message]
|
messages_by_id[placeholder.parent_message]
|
||||||
@@ -188,80 +157,46 @@ class TfNewsElement extends LitElement {
|
|||||||
return recursive_sort(roots, true);
|
return recursive_sort(roots, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
group_messages(messages) {
|
group_following(messages) {
|
||||||
let result = [];
|
let result = [];
|
||||||
let group = [];
|
let group = [];
|
||||||
let type = undefined;
|
|
||||||
for (let message of messages) {
|
for (let message of messages) {
|
||||||
if (
|
if (message?.content?.type === 'contact') {
|
||||||
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);
|
group.push(message);
|
||||||
} else {
|
} else {
|
||||||
if (group.length == 1) {
|
if (group.length > 0) {
|
||||||
result.push(group[0]);
|
|
||||||
group = [];
|
|
||||||
} else if (group.length > 1) {
|
|
||||||
result.push({
|
result.push({
|
||||||
rowid: Math.max(...group.map((x) => x.rowid)),
|
rowid: Math.max(...group.map((x) => x.rowid)),
|
||||||
type: `${type}_group`,
|
type: 'contact_group',
|
||||||
messages: group,
|
messages: group,
|
||||||
});
|
});
|
||||||
group = [];
|
group = [];
|
||||||
}
|
}
|
||||||
result.push(message);
|
result.push(message);
|
||||||
type = undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (group.length == 1) {
|
if (group.length > 0) {
|
||||||
result.push(group[0]);
|
|
||||||
group = [];
|
|
||||||
} else if (group.length > 1) {
|
|
||||||
result.push({
|
result.push({
|
||||||
rowid: Math.max(...group.map((x) => x.rowid)),
|
rowid: Math.max(...group.map((x) => x.rowid)),
|
||||||
type: `${type}_group`,
|
type: 'contact_group',
|
||||||
messages: group,
|
messages: group,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
unread_allowed() {
|
|
||||||
return !this.hash?.startsWith('#%') && !this.hash?.startsWith('#@');
|
|
||||||
}
|
|
||||||
|
|
||||||
load_and_render(messages) {
|
load_and_render(messages) {
|
||||||
let messages_by_id = this.process_messages(messages);
|
let messages_by_id = this.process_messages(messages);
|
||||||
let final_messages = this.group_messages(
|
let final_messages = this.group_following(
|
||||||
this.finalize_messages(messages_by_id)
|
this.finalize_messages(messages_by_id)
|
||||||
);
|
);
|
||||||
let unread_rowid = -1;
|
let unread_rowid = -1;
|
||||||
if (this.unread_allowed()) {
|
for (let message of final_messages) {
|
||||||
for (let message of final_messages) {
|
if (message.rowid >= this.channel_unread) {
|
||||||
if (message.rowid >= this.channel_unread) {
|
unread_rowid = message.rowid;
|
||||||
unread_rowid = message.rowid;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<style>
|
|
||||||
${generate_theme()}
|
|
||||||
</style>
|
|
||||||
<div>
|
<div>
|
||||||
${repeat(
|
${repeat(
|
||||||
final_messages,
|
final_messages,
|
||||||
@@ -276,26 +211,13 @@ class TfNewsElement extends LitElement {
|
|||||||
collapsed="true"
|
collapsed="true"
|
||||||
channel=${this.channel}
|
channel=${this.channel}
|
||||||
channel_unread=${this.channel_unread}
|
channel_unread=${this.channel_unread}
|
||||||
.recent_reactions=${this.recent_reactions}
|
|
||||||
></tf-message>
|
></tf-message>
|
||||||
${x.rowid == unread_rowid
|
${x.rowid == unread_rowid
|
||||||
? html`<div style="display: flex; flex-direction: row">
|
? html`<div style="display: flex; flex-direction: row">
|
||||||
<div
|
<div
|
||||||
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
|
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
|
||||||
></div>
|
></div>
|
||||||
<button
|
<div style="color: #f00; padding: 8px">unread</div>
|
||||||
style="color: #f00; padding: 8px"
|
|
||||||
class="w3-button"
|
|
||||||
@click=${() =>
|
|
||||||
this.dispatchEvent(
|
|
||||||
new Event('mark_all_read', {
|
|
||||||
bubbles: true,
|
|
||||||
composed: true,
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
unread
|
|
||||||
</button>
|
|
||||||
<div
|
<div
|
||||||
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
|
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
|
||||||
></div>
|
></div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {LitElement, html, until, unsafeHTML} from './lit-all.min.js';
|
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
import * as tfutils from './tf-utils.js';
|
import * as tfutils from './tf-utils.js';
|
||||||
import {styles, generate_theme} from './tf-styles.js';
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
class TfProfileElement extends LitElement {
|
class TfProfileElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@@ -11,10 +11,8 @@ class TfProfileElement extends LitElement {
|
|||||||
id: {type: String},
|
id: {type: String},
|
||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
size: {type: Number},
|
size: {type: Number},
|
||||||
sequence: {type: Number},
|
|
||||||
following: {type: Boolean},
|
following: {type: Boolean},
|
||||||
blocking: {type: Boolean},
|
blocking: {type: Boolean},
|
||||||
show_followed: {type: Boolean},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +26,6 @@ class TfProfileElement extends LitElement {
|
|||||||
this.id = null;
|
this.id = null;
|
||||||
this.users = {};
|
this.users = {};
|
||||||
this.size = 0;
|
this.size = 0;
|
||||||
this.sequence = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
@@ -37,22 +34,16 @@ class TfProfileElement extends LitElement {
|
|||||||
this.following = undefined;
|
this.following = undefined;
|
||||||
this.blocking = 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(
|
let result = await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
SELECT json_extract(content, '$.following') AS following
|
SELECT json_extract(content, '$.following') AS following
|
||||||
FROM messages WHERE author = ? AND
|
FROM messages WHERE author = ? AND
|
||||||
json_extract(content, '$.type') = 'contact' AND
|
json_extract(content, '$.type') = 'contact' AND
|
||||||
json_extract(content, '$.contact') = ? AND
|
json_extract(content, '$.contact') = ? AND
|
||||||
following IS NOT NULL AND
|
following IS NOT NULL
|
||||||
messages.rowid <= ?
|
|
||||||
ORDER BY sequence DESC LIMIT 1
|
ORDER BY sequence DESC LIMIT 1
|
||||||
`,
|
`,
|
||||||
[this.whoami, this.id, latest],
|
[this.whoami, this.id]
|
||||||
{cacheable: true}
|
|
||||||
);
|
);
|
||||||
this.following = result?.[0]?.following ?? false;
|
this.following = result?.[0]?.following ?? false;
|
||||||
result = await tfrpc.rpc.query(
|
result = await tfrpc.rpc.query(
|
||||||
@@ -61,12 +52,10 @@ class TfProfileElement extends LitElement {
|
|||||||
FROM messages WHERE author = ? AND
|
FROM messages WHERE author = ? AND
|
||||||
json_extract(content, '$.type') = 'contact' AND
|
json_extract(content, '$.type') = 'contact' AND
|
||||||
json_extract(content, '$.contact') = ? AND
|
json_extract(content, '$.contact') = ? AND
|
||||||
blocking IS NOT NULL AND
|
blocking IS NOT NULL
|
||||||
messages.rowid <= ?
|
|
||||||
ORDER BY sequence DESC LIMIT 1
|
ORDER BY sequence DESC LIMIT 1
|
||||||
`,
|
`,
|
||||||
[this.whoami, this.id, latest],
|
[this.whoami, this.id]
|
||||||
{cacheable: true}
|
|
||||||
);
|
);
|
||||||
this.blocking = result?.[0]?.blocking ?? false;
|
this.blocking = result?.[0]?.blocking ?? false;
|
||||||
}
|
}
|
||||||
@@ -150,8 +139,7 @@ class TfProfileElement extends LitElement {
|
|||||||
let self = this;
|
let self = this;
|
||||||
let input = document.createElement('input');
|
let input = document.createElement('input');
|
||||||
input.type = 'file';
|
input.type = 'file';
|
||||||
input.addEventListener('change', function (event) {
|
input.onchange = function (event) {
|
||||||
input.parentNode.removeChild(input);
|
|
||||||
let file = event.target.files[0];
|
let file = event.target.files[0];
|
||||||
file
|
file
|
||||||
.arrayBuffer()
|
.arrayBuffer()
|
||||||
@@ -166,8 +154,7 @@ class TfProfileElement extends LitElement {
|
|||||||
.catch(function (e) {
|
.catch(function (e) {
|
||||||
alert(e.message);
|
alert(e.message);
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
document.body.appendChild(input);
|
|
||||||
input.click();
|
input.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,98 +162,17 @@ class TfProfileElement extends LitElement {
|
|||||||
navigator.clipboard.writeText(this.id);
|
navigator.clipboard.writeText(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
show_image(link) {
|
|
||||||
let div = document.createElement('div');
|
|
||||||
div.style.left = 0;
|
|
||||||
div.style.top = 0;
|
|
||||||
div.style.width = '100%';
|
|
||||||
div.style.height = '100%';
|
|
||||||
div.style.position = 'fixed';
|
|
||||||
div.style.background = '#000';
|
|
||||||
div.style.zIndex = 100;
|
|
||||||
div.style.display = 'grid';
|
|
||||||
let img = document.createElement('img');
|
|
||||||
img.src = link;
|
|
||||||
img.style.maxWidth = '100vw';
|
|
||||||
img.style.maxHeight = '100vh';
|
|
||||||
img.style.display = 'block';
|
|
||||||
img.style.margin = 'auto';
|
|
||||||
img.style.objectFit = 'contain';
|
|
||||||
img.style.width = '100vw';
|
|
||||||
div.appendChild(img);
|
|
||||||
function image_close(event) {
|
|
||||||
document.body.removeChild(div);
|
|
||||||
window.removeEventListener('keydown', image_close);
|
|
||||||
}
|
|
||||||
div.onclick = image_close;
|
|
||||||
window.addEventListener('keydown', image_close);
|
|
||||||
document.body.appendChild(div);
|
|
||||||
}
|
|
||||||
|
|
||||||
body_click(event) {
|
|
||||||
if (event.srcElement.tagName == 'IMG') {
|
|
||||||
this.show_image(event.srcElement.src);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggle_account_list(event) {
|
|
||||||
let content = event.srcElement.nextElementSibling;
|
|
||||||
this.show_followed = !this.show_followed;
|
|
||||||
}
|
|
||||||
|
|
||||||
async load_follows() {
|
|
||||||
let accounts = await tfrpc.rpc.following([this.id], 1);
|
|
||||||
delete accounts[this.id];
|
|
||||||
return html`
|
|
||||||
<div class="w3-container">
|
|
||||||
<button
|
|
||||||
class="w3-button w3-block w3-theme-d1 followed_accounts"
|
|
||||||
@click=${this.toggle_account_list}
|
|
||||||
>
|
|
||||||
${this.show_followed ? 'Hide' : 'Show'} Followed Accounts
|
|
||||||
(${Object.keys(accounts).length})
|
|
||||||
</button>
|
|
||||||
<div class=${'w3-card' + (this.show_followed ? '' : ' w3-hide')}>
|
|
||||||
<ul class="w3-ul w3-theme-d4 w3-border-theme">
|
|
||||||
${Object.keys(accounts).map(
|
|
||||||
(x) => html`
|
|
||||||
<li class="w3-border-theme">
|
|
||||||
<tf-user id=${x} .users=${this.users}></tf-user>
|
|
||||||
</li>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
open_private_chat() {
|
|
||||||
let hash = '#🔐' + (this.id != this.whoami ? this.id : '');
|
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent('openprivatechat', {
|
|
||||||
bubbles: true,
|
|
||||||
composed: true,
|
|
||||||
detail: {
|
|
||||||
key: JSON.stringify([this.id]),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
tfrpc.rpc.setHash(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
this.load();
|
this.load();
|
||||||
let self = this;
|
let self = this;
|
||||||
let profile = this.users[this.id] || {};
|
let profile = this.users[this.id] || {};
|
||||||
tfrpc.rpc
|
tfrpc.rpc
|
||||||
.query(
|
.query(
|
||||||
`SELECT size AS size, max_sequence AS sequence FROM messages_stats WHERE author = ?`,
|
`SELECT SUM(LENGTH(content)) AS size FROM messages WHERE author = ?`,
|
||||||
[this.id]
|
[this.id]
|
||||||
)
|
)
|
||||||
.then(function (result) {
|
.then(function (result) {
|
||||||
self.size = result[0].size;
|
self.size = result[0].size;
|
||||||
self.sequence = result[0].sequence;
|
|
||||||
});
|
});
|
||||||
let edit;
|
let edit;
|
||||||
let follow;
|
let follow;
|
||||||
@@ -274,18 +180,16 @@ class TfProfileElement extends LitElement {
|
|||||||
if (this.id === this.whoami) {
|
if (this.id === this.whoami) {
|
||||||
if (this.editing) {
|
if (this.editing) {
|
||||||
edit = html`
|
edit = html`
|
||||||
<div style="margin-top: 8px">
|
<button
|
||||||
<button
|
id="save_profile"
|
||||||
id="save_profile"
|
class="w3-button w3-theme-d1"
|
||||||
class="w3-button w3-theme-l1"
|
@click=${this.save_edits}
|
||||||
@click=${this.save_edits}
|
>
|
||||||
>
|
Save Profile
|
||||||
Save Profile
|
</button>
|
||||||
</button>
|
<button class="w3-button w3-theme-d1" @click=${this.discard_edits}>
|
||||||
<button class="w3-button w3-theme-d1" @click=${this.discard_edits}>
|
Discard
|
||||||
Discard
|
</button>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
edit = html`<button
|
edit = html`<button
|
||||||
@@ -331,44 +235,34 @@ class TfProfileElement extends LitElement {
|
|||||||
<div>
|
<div>
|
||||||
<button class="w3-button w3-theme-d1" @click=${this.attach_image}>Attach Image</button>
|
<button class="w3-button w3-theme-d1" @click=${this.attach_image}>Attach Image</button>
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
: null;
|
: null;
|
||||||
let image = profile.image;
|
let image =
|
||||||
if (typeof image == 'string' && !image.startsWith('&')) {
|
typeof profile.image == 'string' ? profile.image : profile.image?.link;
|
||||||
try {
|
|
||||||
image = JSON.parse(image)?.link;
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
image = this.editing?.image ?? image;
|
image = this.editing?.image ?? image;
|
||||||
let description = this.editing?.description ?? profile.description;
|
let description = this.editing?.description ?? profile.description;
|
||||||
return html`
|
return html`<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box">
|
||||||
<style>${generate_theme()}</style>
|
|
||||||
<div class="w3-card-4 w3-theme-d3" style="box-sizing: border-box">
|
|
||||||
<header class="w3-container">
|
<header class="w3-container">
|
||||||
<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)} in ${this.sequence} messages)</p>
|
<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)})</p>
|
||||||
</header>
|
</header>
|
||||||
<div class="w3-container" @click=${this.body_click}>
|
<div class="w3-container">
|
||||||
<div class="w3-margin-bottom" style="display: flex; flex-direction: row">
|
<div class="w3-margin-bottom" style="display: flex; flex-direction: row">
|
||||||
<input type="text" class="w3-input w3-border w3-theme-d1" style="display: flex 1 1" readonly value=${this.id}></input>
|
<input type="text" class="w3-input w3-border w3-theme-d1" style="display: flex 1 1" readonly value=${this.id}></input>
|
||||||
<button class="w3-button w3-theme-d1 w3-ripple" style="flex: 0 0 auto" @click=${this.copy_id}>Copy</button>
|
<button class="w3-button w3-theme-d1 w3-ripple" style="flex: 0 0 auto" @click=${this.copy_id}>Copy</button>
|
||||||
</div>
|
</div>
|
||||||
<div class=${this.editing ? 'w3-card' : ''}>
|
<div style="display: flex; flex-direction: row; gap: 1em">
|
||||||
${this.editing ? html`<header class="w3-container w3-theme-l2"><h2>Editing Your Profile</h2></header>` : undefined}
|
${edit_profile}
|
||||||
<div style="display: flex; flex-direction: row; gap: 1em" class="w3-margin">
|
<div style="flex: 1 0 50%">
|
||||||
${edit_profile}
|
${
|
||||||
<div style="flex: 1 0 50%; contain: layout; overflow: auto; word-wrap: normal; word-break: normal">
|
image
|
||||||
${
|
? html`<div><img src=${'/' + image + '/view'} style="width: 256px; height: auto"></img></div>`
|
||||||
image
|
: html`<div>
|
||||||
? html`<div><img src=${'/' + image + '/view'} style="width: min(256px, 100%); height: auto"></img></div>`
|
<div class="w3-jumbo">😎</div>
|
||||||
: html`<div>
|
<div><i>Profile image not set.</i></div>
|
||||||
<div class="w3-jumbo">😎</div>
|
</div>`
|
||||||
<div><i>Profile image not set.</i></div>
|
}
|
||||||
</div>`
|
<div>${unsafeHTML(tfutils.markdown(description))}</div>
|
||||||
}
|
|
||||||
<div>${unsafeHTML(tfutils.markdown(description))}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
${this.editing ? html`<footer class="w3-container w3-theme-l2"><p>${edit}</p></footer>` : undefined}
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Following ${profile.following} identities.
|
Following ${profile.following} identities.
|
||||||
@@ -377,13 +271,9 @@ class TfProfileElement extends LitElement {
|
|||||||
Blocked by ${profile.blocked} identities.
|
Blocked by ${profile.blocked} identities.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${until(this.load_follows(), html`<p>Loading accounts followed...</p>`)}
|
|
||||||
<footer class="w3-container">
|
<footer class="w3-container">
|
||||||
<p>
|
<p>
|
||||||
<button class="w3-button w3-theme-d1" @click=${this.open_private_chat} id="open_private_chat">
|
${edit}
|
||||||
Open Private Chat
|
|
||||||
</button>
|
|
||||||
${this.editing ? undefined : edit}
|
|
||||||
${follow}
|
${follow}
|
||||||
${block}
|
${block}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||||
import {styles, generate_theme} from './tf-styles.js';
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
class TfReactionsModalElement extends LitElement {
|
class TfReactionsModalElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@@ -24,57 +24,47 @@ class TfReactionsModalElement extends LitElement {
|
|||||||
render() {
|
render() {
|
||||||
let self = this;
|
let self = this;
|
||||||
return this.votes?.length
|
return this.votes?.length
|
||||||
? html` <style>
|
? html` <div
|
||||||
${generate_theme()}
|
class="w3-modal w3-animate-opacity"
|
||||||
</style>
|
style="display: block; box-sizing: border-box; z-index: 10"
|
||||||
|
@click=${this.clear}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="w3-modal w3-animate-opacity"
|
class="w3-modal-content w3-card-4 w3-theme-d1"
|
||||||
style="display: block; box-sizing: border-box; z-index: 10"
|
onclick="event.stopPropagation()"
|
||||||
@click=${this.clear}
|
|
||||||
>
|
>
|
||||||
<div
|
<div class="w3-container w3-padding">
|
||||||
class="w3-modal-content w3-card-4 w3-theme-d1"
|
<header class="w3-container">
|
||||||
onclick="event.stopPropagation()"
|
<h2>Reactions</h2>
|
||||||
>
|
<span class="w3-button w3-display-topright" @click=${this.clear}
|
||||||
<div class="w3-container w3-padding">
|
>×</span
|
||||||
<header class="w3-container">
|
>
|
||||||
<h2>Reactions</h2>
|
</header>
|
||||||
<span
|
<ul class="w3-theme-dark w3-container w3-ul">
|
||||||
class="w3-button w3-display-topright"
|
${this.votes.map(
|
||||||
@click=${this.clear}
|
(x) => html`
|
||||||
>×</span
|
<li class="w3-bar">
|
||||||
>
|
<span class="w3-bar-item"
|
||||||
</header>
|
>${x?.content?.vote?.expression}</span
|
||||||
<ul class="w3-theme-dark w3-container w3-ul">
|
>
|
||||||
${this.votes
|
<tf-user
|
||||||
.sort((x, y) => y.timestamp - x.timestamp)
|
class="w3-bar-item"
|
||||||
.map(
|
id=${x.author}
|
||||||
(x) => html`
|
.users=${this.users}
|
||||||
<li
|
></tf-user>
|
||||||
style="display: flex; flex-direction: row; gap: 4px"
|
<span class="w3-bar-item w3-right"
|
||||||
>
|
>${new Date(x?.timestamp).toLocaleString()}</span
|
||||||
<span style="flex-basis: 3em"
|
>
|
||||||
>${x?.content?.vote?.expression}</span
|
</li>
|
||||||
>
|
`
|
||||||
<tf-user
|
)}
|
||||||
style="flex: 1 1; overflow: hidden"
|
</ul>
|
||||||
id=${x.author}
|
<footer class="w3-container w3-padding">
|
||||||
.users=${this.users}
|
<button class="w3-button" @click=${this.clear}>Close</button>
|
||||||
></tf-user>
|
</footer>
|
||||||
<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>
|
||||||
|
</div>`
|
||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {css, unsafeCSS, until} from './lit-all.min.js';
|
import {css, unsafeCSS} from './lit-all.min.js';
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
|
||||||
|
|
||||||
const tf = css`
|
const tf = css`
|
||||||
img {
|
img {
|
||||||
@@ -44,14 +43,12 @@ const tf = css`
|
|||||||
border-left: 4px solid #fff;
|
border-left: 4px solid #fff;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const w3 = css`
|
const w3 = css`
|
||||||
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */
|
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||||
@@ -161,10 +158,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||||
|
|
||||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
|
||||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
|
||||||
|
|
||||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||||
@@ -206,9 +199,9 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||||
.w3-hover-none:hover{box-shadow:none!important}
|
.w3-hover-none:hover{box-shadow:none!important}
|
||||||
/* Colors */
|
/* Colors */
|
||||||
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important}
|
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||||
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important}
|
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||||
@@ -223,24 +216,15 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||||
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important}
|
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||||
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important}
|
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||||
|
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
|
|
||||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||||
|
|
||||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
|
||||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
|
||||||
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
|
|
||||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
|
||||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
|
||||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
|
||||||
|
|
||||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||||
@@ -409,8 +393,16 @@ function is_dark(hex, value) {
|
|||||||
return (r * 299 + g * 587 + b * 114) / 1000 < value;
|
return (r * 299 + g * 587 + b * 114) / 1000 < value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generate(color) {
|
function generated() {
|
||||||
let [r, g, b] = hex_to_rgb(color);
|
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);
|
||||||
let [h, s, l] = rgb_to_hsl(r, g, b);
|
let [h, s, l] = rgb_to_hsl(r, g, b);
|
||||||
|
|
||||||
let theme1 = {
|
let theme1 = {
|
||||||
@@ -454,28 +446,4 @@ export function generate(color) {
|
|||||||
return unsafeCSS(result);
|
return unsafeCSS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
let g_theme;
|
export let styles = [tf, w3, generated()];
|
||||||
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 {LitElement, html} from './lit-all.min.js';
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
import {styles, generate_theme} from './tf-styles.js';
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
class TfTabConnectionsElement extends LitElement {
|
class TfTabConnectionsElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@@ -15,7 +15,6 @@ class TfTabConnectionsElement extends LitElement {
|
|||||||
connect_attempt: {type: Object},
|
connect_attempt: {type: Object},
|
||||||
connect_message: {type: String},
|
connect_message: {type: String},
|
||||||
connect_success: {type: Boolean},
|
connect_success: {type: Boolean},
|
||||||
peer_exchange: {type: Boolean},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,20 +47,6 @@ class TfTabConnectionsElement extends LitElement {
|
|||||||
tfrpc.rpc.getServerIdentity().then(function (identity) {
|
tfrpc.rpc.getServerIdentity().then(function (identity) {
|
||||||
self.server_identity = 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) {
|
render_connection_summary(connection) {
|
||||||
@@ -118,23 +103,6 @@ class TfTabConnectionsElement extends LitElement {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
render_progress(name, value, max) {
|
|
||||||
if (max && value != max) {
|
|
||||||
return html`
|
|
||||||
<div class="w3-theme-d1 w3-small">
|
|
||||||
<div
|
|
||||||
class="w3-container w3-theme-l1"
|
|
||||||
style="width: ${Math.floor(
|
|
||||||
(100.0 * value) / max
|
|
||||||
)}%; text-wrap: nowrap"
|
|
||||||
>
|
|
||||||
${name} ${value} / ${max} (${Math.round((100.0 * value) / max)}%)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render_broadcast(connection) {
|
render_broadcast(connection) {
|
||||||
let self = this;
|
let self = this;
|
||||||
return html`
|
return html`
|
||||||
@@ -186,16 +154,6 @@ class TfTabConnectionsElement extends LitElement {
|
|||||||
: undefined}
|
: undefined}
|
||||||
${connection.flags.one_shot ? '🔃' : undefined}
|
${connection.flags.one_shot ? '🔃' : undefined}
|
||||||
<tf-user id=${connection.id} .users=${this.users}></tf-user>
|
<tf-user id=${connection.id} .users=${this.users}></tf-user>
|
||||||
${this.render_progress(
|
|
||||||
'recv',
|
|
||||||
connection.progress.in.total - connection.progress.in.current,
|
|
||||||
connection.progress.in.total
|
|
||||||
)}
|
|
||||||
${this.render_progress(
|
|
||||||
'send',
|
|
||||||
connection.progress.out.total - connection.progress.out.current,
|
|
||||||
connection.progress.out.total
|
|
||||||
)}
|
|
||||||
${connection.tunnel !== undefined
|
${connection.tunnel !== undefined
|
||||||
? '🚇'
|
? '🚇'
|
||||||
: html`(${connection.host}:${connection.port})`}
|
: html`(${connection.host}:${connection.port})`}
|
||||||
@@ -248,44 +206,10 @@ class TfTabConnectionsElement extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle_accordian(id) {
|
|
||||||
let element = this.renderRoot.getElementById(id);
|
|
||||||
element.classList.toggle('w3-hide');
|
|
||||||
}
|
|
||||||
|
|
||||||
valid_connections() {
|
|
||||||
return this.connections.filter((x) => x.tunnel === undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
valid_broadcasts() {
|
|
||||||
return this.broadcasts
|
|
||||||
.filter((x) => x.address)
|
|
||||||
.filter((x) => this.connections.map((c) => c.id).indexOf(x.pubkey) == -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let self = this;
|
let self = this;
|
||||||
return html`
|
return html`
|
||||||
<style>
|
|
||||||
${generate_theme()}
|
|
||||||
</style>
|
|
||||||
<div class="w3-container" style="box-sizing: border-box">
|
<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>
|
<h2>New Connection</h2>
|
||||||
<textarea class="w3-input w3-theme-d1" id="code"></textarea>
|
<textarea class="w3-input w3-theme-d1" id="code"></textarea>
|
||||||
${this.render_message(this.renderRoot.getElementById('code')?.value)}
|
${this.render_message(this.renderRoot.getElementById('code')?.value)}
|
||||||
@@ -296,33 +220,27 @@ class TfTabConnectionsElement extends LitElement {
|
|||||||
>
|
>
|
||||||
Connect
|
Connect
|
||||||
</button>
|
</button>
|
||||||
<h2
|
<h2>Broadcasts</h2>
|
||||||
class="w3-button w3-block w3-theme-d1"
|
<ul class="w3-ul w3-border">
|
||||||
@click=${() => self.toggle_accordian('connections')}
|
${this.broadcasts
|
||||||
>
|
.filter((x) => x.address)
|
||||||
Connections (${this.valid_connections().length})
|
.filter(
|
||||||
</h2>
|
(x) => self.connections.map((c) => c.id).indexOf(x.pubkey) == -1
|
||||||
<ul class="w3-ul w3-border" id="connections">
|
)
|
||||||
${this.valid_connections().map(
|
.map((x) => self.render_broadcast(x))}
|
||||||
(x) => html` <li class="w3-bar">${this.render_connection(x)}</li> `
|
|
||||||
)}
|
|
||||||
</ul>
|
</ul>
|
||||||
<h2
|
<h2>Connections</h2>
|
||||||
class="w3-button w3-block w3-theme-d1"
|
<ul class="w3-ul w3-border">
|
||||||
@click=${() => self.toggle_accordian('broadcasts')}
|
${this.connections
|
||||||
>
|
.filter((x) => x.tunnel === undefined)
|
||||||
Discovery (${this.valid_broadcasts().length})
|
.map(
|
||||||
</h2>
|
(x) => html`
|
||||||
<ul class="w3-ul w3-border w3-hide" id="broadcasts">
|
<li class="w3-bar">${this.render_connection(x)}</li>
|
||||||
${this.valid_broadcasts().map((x) => self.render_broadcast(x))}
|
`
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
<h2
|
<h2>Stored Connections</h2>
|
||||||
class="w3-button w3-block w3-theme-d1"
|
<ul class="w3-ul w3-border">
|
||||||
@click=${() => self.toggle_accordian('stored_connections')}
|
|
||||||
>
|
|
||||||
Stored Connections (${this.stored_connections.length})
|
|
||||||
</h2>
|
|
||||||
<ul class="w3-ul w3-border w3-hide" id="stored_connections">
|
|
||||||
${this.stored_connections.map(
|
${this.stored_connections.map(
|
||||||
(x) => html`
|
(x) => html`
|
||||||
<li>
|
<li>
|
||||||
@@ -342,12 +260,6 @@ class TfTabConnectionsElement extends LitElement {
|
|||||||
<div class="w3-bar-item">
|
<div class="w3-bar-item">
|
||||||
<tf-user id=${x.pubkey} .users=${self.users}></tf-user>
|
<tf-user id=${x.pubkey} .users=${self.users}></tf-user>
|
||||||
<div><small>${x.address}:${x.port}</small></div>
|
<div><small>${x.address}:${x.port}</small></div>
|
||||||
<div>
|
|
||||||
<small
|
|
||||||
>Last connection:
|
|
||||||
${new Date(x.last_success * 1000)}</small
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${this.render_message(x)}
|
${this.render_message(x)}
|
||||||
@@ -355,13 +267,8 @@ class TfTabConnectionsElement extends LitElement {
|
|||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
<h2
|
<h2>Local Accounts</h2>
|
||||||
class="w3-button w3-block w3-theme-d1"
|
<div class="w3-container">
|
||||||
@click=${() => self.toggle_accordian('local_accounts')}
|
|
||||||
>
|
|
||||||
Local Accounts (${this.identities.length})
|
|
||||||
</h2>
|
|
||||||
<div class="w3-container w3-hide" id="local_accounts">
|
|
||||||
${this.identities.map(
|
${this.identities.map(
|
||||||
(x) =>
|
(x) =>
|
||||||
html`<div
|
html`<div
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {LitElement, cache, html, unsafeHTML, until} from './lit-all.min.js';
|
import {LitElement, cache, html, unsafeHTML, until} from './lit-all.min.js';
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
import {styles, generate_theme} from './tf-styles.js';
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
class TfTabNewsFeedElement extends LitElement {
|
class TfTabNewsFeedElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@@ -18,8 +18,6 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
time_range: {type: Array},
|
time_range: {type: Array},
|
||||||
time_loading: {type: Array},
|
time_loading: {type: Array},
|
||||||
private_messages: {type: Array},
|
private_messages: {type: Array},
|
||||||
grouped_private_messages: {type: Object},
|
|
||||||
recent_reactions: {type: Array},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +37,6 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
this.start_time = new Date().valueOf();
|
this.start_time = new Date().valueOf();
|
||||||
this.time_range = [0, 0];
|
this.time_range = [0, 0];
|
||||||
this.time_loading = undefined;
|
this.time_loading = undefined;
|
||||||
this.recent_reactions = [];
|
|
||||||
this.loading = 0;
|
this.loading = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,82 +46,20 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
: this.hash.substring(1);
|
: this.hash.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetch_related_messages(messages) {
|
|
||||||
let refs = await tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
WITH
|
|
||||||
news AS (
|
|
||||||
SELECT value AS id FROM json_each(?)
|
|
||||||
)
|
|
||||||
SELECT refs_out.ref AS ref FROM messages_refs refs_out JOIN news ON refs_out.message = news.id
|
|
||||||
UNION
|
|
||||||
SELECT refs_in.message AS ref FROM messages_refs refs_in JOIN news ON refs_in.ref = news.id
|
|
||||||
`,
|
|
||||||
[JSON.stringify(messages.map((x) => x.id))]
|
|
||||||
);
|
|
||||||
let related_messages = await tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
|
||||||
FROM messages
|
|
||||||
JOIN json_each(?2) refs ON messages.id = refs.value
|
|
||||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
|
||||||
`,
|
|
||||||
[JSON.stringify(this.following), JSON.stringify(refs.map((x) => x.ref))]
|
|
||||||
);
|
|
||||||
let combined = [].concat(messages, related_messages);
|
|
||||||
let refs2 = await tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
WITH
|
|
||||||
news AS (
|
|
||||||
SELECT value AS id FROM json_each(?)
|
|
||||||
)
|
|
||||||
SELECT refs_out.ref AS ref FROM messages_refs refs_out JOIN news ON refs_out.message = news.id
|
|
||||||
UNION
|
|
||||||
SELECT refs_in.message AS ref FROM messages_refs refs_in JOIN news ON refs_in.ref = news.id
|
|
||||||
`,
|
|
||||||
[JSON.stringify(combined.map((x) => x.id))]
|
|
||||||
);
|
|
||||||
let result = [].concat(
|
|
||||||
combined,
|
|
||||||
await tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
|
||||||
FROM json_each(?2) refs
|
|
||||||
JOIN messages ON messages.id = refs.value
|
|
||||||
JOIN json_each(?1) following ON messages.author = following.value
|
|
||||||
WHERE messages.content ->> 'type' != 'post'
|
|
||||||
`,
|
|
||||||
[
|
|
||||||
JSON.stringify(this.following),
|
|
||||||
JSON.stringify(refs2.map((x) => x.ref)),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetch_messages(start_time, end_time) {
|
async fetch_messages(start_time, end_time) {
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent('loadmessages', {
|
|
||||||
bubbles: true,
|
|
||||||
composed: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
this.time_loading = [start_time, end_time];
|
this.time_loading = [start_time, end_time];
|
||||||
let result;
|
let result;
|
||||||
const k_max_results = 64;
|
|
||||||
if (this.hash == '#@') {
|
if (this.hash == '#@') {
|
||||||
result = await tfrpc.rpc.query(
|
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
|
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_refs
|
FROM messages_fts(?1)
|
||||||
JOIN messages ON messages.id = messages_refs.message
|
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||||
WHERE
|
WHERE
|
||||||
messages_refs.ref = ?1 AND
|
|
||||||
messages.author != ?1 AND
|
messages.author != ?1 AND
|
||||||
(?3 IS NULL OR messages.timestamp >= ?3) AND messages.timestamp < ?4
|
(?3 IS NULL OR messages.timestamp >= ?3) AND messages.timestamp < ?4
|
||||||
ORDER BY timestamp DESC limit ?5)
|
ORDER BY timestamp DESC limit 20)
|
||||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
FROM mentions
|
FROM mentions
|
||||||
JOIN messages_refs ON mentions.id = messages_refs.ref
|
JOIN messages_refs ON mentions.id = messages_refs.ref
|
||||||
@@ -133,11 +68,10 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
SELECT TRUE AS is_primary, * FROM mentions
|
SELECT TRUE AS is_primary, * FROM mentions
|
||||||
`,
|
`,
|
||||||
[
|
[
|
||||||
this.whoami,
|
'"' + this.whoami.replace('"', '""') + '"',
|
||||||
JSON.stringify(this.following),
|
JSON.stringify(this.following),
|
||||||
start_time,
|
start_time,
|
||||||
end_time,
|
end_time,
|
||||||
k_max_results,
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else if (this.hash.startsWith('#@')) {
|
} else if (this.hash.startsWith('#@')) {
|
||||||
@@ -147,7 +81,7 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
selected AS (SELECT rowid, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
selected AS (SELECT rowid, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE messages.author = ?1 AND (?2 IS NULL OR messages.timestamp >= 2) AND messages.timestamp < ?3
|
WHERE messages.author = ?1 AND (?2 IS NULL OR messages.timestamp >= 2) AND messages.timestamp < ?3
|
||||||
ORDER BY sequence DESC LIMIT ?4
|
ORDER BY sequence DESC LIMIT 20
|
||||||
)
|
)
|
||||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
FROM selected
|
FROM selected
|
||||||
@@ -156,7 +90,7 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
UNION
|
UNION
|
||||||
SELECT TRUE AS is_primary, * FROM selected
|
SELECT TRUE AS is_primary, * FROM selected
|
||||||
`,
|
`,
|
||||||
[this.hash.substring(1), start_time, end_time, k_max_results]
|
[this.hash.substring(1), start_time, end_time]
|
||||||
);
|
);
|
||||||
} else if (this.hash.startsWith('#%')) {
|
} else if (this.hash.startsWith('#%')) {
|
||||||
result = await tfrpc.rpc.query(
|
result = await tfrpc.rpc.query(
|
||||||
@@ -173,37 +107,44 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
[this.hash.substring(1)]
|
[this.hash.substring(1)]
|
||||||
);
|
);
|
||||||
} else if (this.hash.startsWith('##')) {
|
} else if (this.hash.startsWith('##')) {
|
||||||
let initial_messages = await tfrpc.rpc.query(
|
result = await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
WITH
|
WITH
|
||||||
all_news AS (
|
all_news AS (
|
||||||
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
FROM messages
|
FROM messages
|
||||||
JOIN json_each(?) AS following ON messages.author = following.value
|
JOIN json_each(?) AS following ON messages.author = following.value
|
||||||
WHERE messages.content ->> 'channel' = ?4 AND messages.content ->> 'type' != 'vote'
|
WHERE messages.content ->> 'channel' = ?4
|
||||||
UNION
|
UNION
|
||||||
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
FROM messages_refs
|
FROM messages_fts(?5)
|
||||||
JOIN messages ON messages.id = messages_refs.message
|
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
JOIN json_each(?1) AS following ON messages.author = following.value
|
||||||
WHERE messages_refs.ref = '#' || ?4 AND messages.content ->> 'type' != 'vote'
|
JOIN json_tree(messages.content, '$.mentions') AS mention ON mention.value = '#' || ?4),
|
||||||
)
|
news AS (SELECT * FROM all_news
|
||||||
SELECT TRUE AS is_primary, all_news.* FROM all_news
|
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
|
||||||
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
|
ORDER BY all_news.timestamp DESC LIMIT 20)
|
||||||
ORDER BY all_news.timestamp DESC LIMIT ?5
|
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
|
FROM news
|
||||||
|
JOIN messages_refs ON news.id = messages_refs.ref
|
||||||
|
JOIN messages ON messages_refs.message = messages.id
|
||||||
|
UNION
|
||||||
|
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
|
FROM news
|
||||||
|
JOIN messages_refs ON news.id = messages_refs.message
|
||||||
|
JOIN messages ON messages_refs.ref = messages.id
|
||||||
|
UNION
|
||||||
|
SELECT TRUE AS is_primary, news.* FROM news
|
||||||
`,
|
`,
|
||||||
[
|
[
|
||||||
JSON.stringify(this.following),
|
JSON.stringify(this.following),
|
||||||
start_time,
|
start_time,
|
||||||
end_time,
|
end_time,
|
||||||
this.hash.substring(2),
|
this.hash.substring(2),
|
||||||
k_max_results,
|
'"#' + this.hash.substring(2).replace('"', '""') + '"',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
result = await this._fetch_related_messages(initial_messages);
|
} else if (this.hash == '#🔐') {
|
||||||
} else if (this.hash.startsWith('#🔐')) {
|
|
||||||
let ids =
|
|
||||||
this.hash == '#🔐' ? [] : this.hash.substring('#🔐'.length).split(',');
|
|
||||||
result = await tfrpc.rpc.query(
|
result = await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||||
@@ -212,83 +153,39 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
WHERE
|
WHERE
|
||||||
(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND
|
(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND
|
||||||
json(messages.content) LIKE '"%'
|
json(messages.content) LIKE '"%'
|
||||||
ORDER BY messages.rowid DESC LIMIT ?4
|
ORDER BY messages.sequence DESC LIMIT 20
|
||||||
`,
|
`,
|
||||||
[
|
[JSON.stringify(this.private_messages), start_time, end_time]
|
||||||
JSON.stringify(
|
|
||||||
this.grouped_private_messages?.[JSON.stringify(ids)]?.map(
|
|
||||||
(x) => x.id
|
|
||||||
) ?? []
|
|
||||||
),
|
|
||||||
start_time,
|
|
||||||
end_time,
|
|
||||||
k_max_results,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
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(
|
|
||||||
`
|
|
||||||
WITH votes AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
|
||||||
FROM messages
|
|
||||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
|
||||||
WHERE
|
|
||||||
messages.content ->> 'type' = 'vote' AND
|
|
||||||
(?2 IS NULL OR messages.timestamp >= ?2) AND messages.timestamp < ?3
|
|
||||||
ORDER BY timestamp DESC limit ?4)
|
|
||||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
|
||||||
FROM votes
|
|
||||||
JOIN messages ON messages.id = votes.content ->> '$.vote.link'
|
|
||||||
UNION
|
|
||||||
SELECT TRUE AS is_primary, * FROM votes
|
|
||||||
`,
|
|
||||||
[JSON.stringify(this.following), start_time, end_time, k_max_results]
|
|
||||||
);
|
|
||||||
} else 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]
|
|
||||||
);
|
);
|
||||||
|
result = (await this.decrypt(result)).filter((x) => x.decrypted);
|
||||||
} else {
|
} else {
|
||||||
let initial_messages = await tfrpc.rpc.query(
|
result = await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
WITH
|
WITH
|
||||||
channels AS (SELECT '#' || value AS value FROM json_each(?5))
|
all_news AS (
|
||||||
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
|
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
FROM messages
|
FROM messages
|
||||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
JOIN json_each(?) AS following ON messages.author = following.value
|
||||||
WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
|
WHERE timestamp >= 0 AND timestamp < ?3),
|
||||||
messages.content ->> 'type' != 'vote' AND
|
news AS (
|
||||||
(messages.content ->> 'root' IS NULL OR (
|
SELECT * FROM all_news
|
||||||
NOT EXISTS (SELECT * FROM messages root JOIN channels ON ('#' || (root.content ->> 'channel')) = channels.value WHERE root.id = messages.content ->> 'root') AND
|
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
|
||||||
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)
|
ORDER BY timestamp DESC LIMIT 20
|
||||||
)) AND
|
)
|
||||||
(messages.content ->> 'channel' IS NULL OR ('#' || (messages.content ->> 'channel')) NOT IN (SELECT * FROM channels)) AND
|
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
|
||||||
NOT EXISTS (SELECT * FROM messages_refs JOIN channels ON messages_refs.message = messages.id AND messages_refs.ref = channels.value)
|
FROM news
|
||||||
ORDER BY timestamp DESC LIMIT ?4
|
JOIN messages_refs ON news.id = messages_refs.ref
|
||||||
|
JOIN messages ON messages_refs.message = messages.id
|
||||||
|
UNION
|
||||||
|
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
|
FROM news
|
||||||
|
JOIN messages_refs ON news.id = messages_refs.message
|
||||||
|
JOIN messages ON messages_refs.ref = messages.id
|
||||||
|
UNION
|
||||||
|
SELECT TRUE AS is_primary, news.* FROM news
|
||||||
`,
|
`,
|
||||||
[
|
[JSON.stringify(this.following), start_time, end_time]
|
||||||
JSON.stringify(this.following),
|
|
||||||
start_time,
|
|
||||||
end_time,
|
|
||||||
k_max_results,
|
|
||||||
JSON.stringify(Object.keys(this.channels_latest)),
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
result = await this._fetch_related_messages(initial_messages);
|
|
||||||
}
|
}
|
||||||
this.time_loading = undefined;
|
this.time_loading = undefined;
|
||||||
return result;
|
return result;
|
||||||
@@ -308,24 +205,13 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
unread_allowed() {
|
|
||||||
return (
|
|
||||||
this.hash == '#@' ||
|
|
||||||
(!this.hash.startsWith('#%') && !this.hash.startsWith('#@'))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async load_more() {
|
async load_more() {
|
||||||
this.loading++;
|
this.loading++;
|
||||||
this.loading_canceled = false;
|
this.loading_canceled = false;
|
||||||
try {
|
try {
|
||||||
let more = [];
|
let more = [];
|
||||||
let last_start_time = this.time_range[0];
|
let last_start_time = this.time_range[0];
|
||||||
try {
|
more = await this.fetch_messages(null, last_start_time);
|
||||||
more = await this.fetch_messages(null, last_start_time);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
this.update_time_range_from_messages(
|
this.update_time_range_from_messages(
|
||||||
more.filter((x) => x.timestamp < last_start_time)
|
more.filter((x) => x.timestamp < last_start_time)
|
||||||
);
|
);
|
||||||
@@ -396,13 +282,7 @@ 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() {
|
async load_messages() {
|
||||||
@@ -410,20 +290,12 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
let self = this;
|
let self = this;
|
||||||
this.loading++;
|
this.loading++;
|
||||||
let messages = [];
|
let messages = [];
|
||||||
let original_key = this.make_messages_key();
|
|
||||||
try {
|
try {
|
||||||
if (this._messages_key !== original_key) {
|
if (this._messages_hash !== this.hash) {
|
||||||
this.messages = [];
|
this.messages = [];
|
||||||
this._messages_key = original_key;
|
this._messages_hash = this.hash;
|
||||||
}
|
}
|
||||||
this._messages_following = JSON.stringify(this.following);
|
this._messages_following = 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 now = new Date().valueOf();
|
||||||
let start_time = now - 24 * 60 * 60 * 1000;
|
let start_time = now - 24 * 60 * 60 * 1000;
|
||||||
this.start_time = start_time;
|
this.start_time = start_time;
|
||||||
@@ -436,11 +308,11 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
} finally {
|
} finally {
|
||||||
this.loading--;
|
this.loading--;
|
||||||
}
|
}
|
||||||
let current_key = this.make_messages_key();
|
this.messages = this.merge_messages(this.messages, messages);
|
||||||
if (current_key === original_key) {
|
|
||||||
this.messages = this.merge_messages(this.messages, messages);
|
|
||||||
}
|
|
||||||
this.time_loading = undefined;
|
this.time_loading = undefined;
|
||||||
|
console.log(
|
||||||
|
`loading messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
mark_all_read() {
|
mark_all_read() {
|
||||||
@@ -462,48 +334,15 @@ 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() {
|
render() {
|
||||||
if (
|
if (
|
||||||
!this.messages ||
|
!this.messages ||
|
||||||
this._messages_key !== this.make_messages_key() ||
|
this._messages_hash !== this.hash ||
|
||||||
this._messages_following !== JSON.stringify(this.following) ||
|
JSON.stringify(this._messages_following) !==
|
||||||
(this.hash.startsWith('#🔐') &&
|
JSON.stringify(this.following)
|
||||||
this._private_messages !==
|
|
||||||
JSON.stringify([
|
|
||||||
this.private_messages,
|
|
||||||
this.grouped_private_messages,
|
|
||||||
]))
|
|
||||||
) {
|
) {
|
||||||
console.log(
|
console.log(
|
||||||
`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})`
|
`loading messages for ${this.whoami} (following ${this.following.length})`
|
||||||
);
|
);
|
||||||
this.load_messages();
|
this.load_messages();
|
||||||
}
|
}
|
||||||
@@ -511,16 +350,9 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
if (!this.hash.startsWith('#%')) {
|
if (!this.hash.startsWith('#%')) {
|
||||||
more = html`
|
more = html`
|
||||||
<p>
|
<p>
|
||||||
${this.unread_allowed()
|
<button class="w3-button w3-theme-d1" @click=${this.mark_all_read}>
|
||||||
? html`
|
Mark All Read
|
||||||
<button
|
</button>
|
||||||
class="w3-button w3-theme-d1"
|
|
||||||
@click=${this.mark_all_read}
|
|
||||||
>
|
|
||||||
Mark All Read
|
|
||||||
</button>
|
|
||||||
`
|
|
||||||
: undefined}
|
|
||||||
<button
|
<button
|
||||||
?disabled=${this.loading}
|
?disabled=${this.loading}
|
||||||
class="w3-button w3-theme-d1"
|
class="w3-button w3-theme-d1"
|
||||||
@@ -552,18 +384,9 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
return cache(html`
|
return cache(html`
|
||||||
<style>
|
<button class="w3-button w3-theme-d1" @click=${this.mark_all_read}>
|
||||||
${generate_theme()}
|
Mark All Read
|
||||||
</style>
|
</button>
|
||||||
${this.unread_allowed()
|
|
||||||
? html`<button
|
|
||||||
class="w3-button w3-theme-d1"
|
|
||||||
@click=${this.mark_all_read}
|
|
||||||
>
|
|
||||||
Mark All Read
|
|
||||||
</button>`
|
|
||||||
: undefined}
|
|
||||||
${this.render_close_chat_button()}
|
|
||||||
<tf-news
|
<tf-news
|
||||||
id="news"
|
id="news"
|
||||||
whoami=${this.whoami}
|
whoami=${this.whoami}
|
||||||
@@ -572,11 +395,8 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
.following=${this.following}
|
.following=${this.following}
|
||||||
.drafts=${this.drafts}
|
.drafts=${this.drafts}
|
||||||
.expanded=${this.expanded}
|
.expanded=${this.expanded}
|
||||||
hash=${this.hash}
|
|
||||||
channel=${this.channel()}
|
channel=${this.channel()}
|
||||||
channel_unread=${this.channels_unread?.[this.channel()]}
|
channel_unread=${this.channels_unread?.[this.channel()]}
|
||||||
.recent_reactions=${this.recent_reactions}
|
|
||||||
@mark_all_read=${this.mark_all_read}
|
|
||||||
></tf-news>
|
></tf-news>
|
||||||
${more}
|
${more}
|
||||||
`);
|
`);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
until,
|
until,
|
||||||
} from './lit-all.min.js';
|
} from './lit-all.min.js';
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
import {styles, generate_theme} from './tf-styles.js';
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
class TfTabNewsElement extends LitElement {
|
class TfTabNewsElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@@ -24,12 +24,6 @@ class TfTabNewsElement extends LitElement {
|
|||||||
channels_latest: {type: Object},
|
channels_latest: {type: Object},
|
||||||
connections: {type: Array},
|
connections: {type: Array},
|
||||||
private_messages: {type: Array},
|
private_messages: {type: Array},
|
||||||
grouped_private_messages: {type: Object},
|
|
||||||
visible_private_messages: {type: Object},
|
|
||||||
recent_reactions: {type: Array},
|
|
||||||
peer_exchange: {type: Boolean},
|
|
||||||
is_administrator: {type: Boolean},
|
|
||||||
stay_connected: {type: Boolean},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,11 +43,9 @@ class TfTabNewsElement extends LitElement {
|
|||||||
this.channels_latest = {};
|
this.channels_latest = {};
|
||||||
this.channels = [];
|
this.channels = [];
|
||||||
this.connections = [];
|
this.connections = [];
|
||||||
this.recent_reactions = [];
|
|
||||||
tfrpc.rpc.localStorageGet('drafts').then(function (d) {
|
tfrpc.rpc.localStorageGet('drafts').then(function (d) {
|
||||||
self.drafts = JSON.parse(d || '{}');
|
self.drafts = JSON.parse(d || '{}');
|
||||||
});
|
});
|
||||||
this.check_peer_exchange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
@@ -66,14 +58,6 @@ class TfTabNewsElement extends LitElement {
|
|||||||
document.body.removeEventListener('keypress', this.on_keypress.bind(this));
|
document.body.removeEventListener('keypress', this.on_keypress.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async check_peer_exchange() {
|
|
||||||
if (await tfrpc.rpc.isAdministrator()) {
|
|
||||||
this.peer_exchange = await tfrpc.rpc.globalSettingsGet('peer_exchange');
|
|
||||||
} else {
|
|
||||||
this.peer_exchange = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
load_latest() {
|
load_latest() {
|
||||||
let news = this.shadowRoot?.getElementById('news');
|
let news = this.shadowRoot?.getElementById('news');
|
||||||
if (news) {
|
if (news) {
|
||||||
@@ -111,26 +95,7 @@ class TfTabNewsElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unread_status(channel) {
|
unread_status(channel) {
|
||||||
if (channel === undefined) {
|
if (
|
||||||
if (
|
|
||||||
Object.keys(this.channels_unread).some((x) => this.unread_status(x))
|
|
||||||
) {
|
|
||||||
return '✉️ ';
|
|
||||||
}
|
|
||||||
} else if (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] &&
|
||||||
this.channels_latest[channel] > 0 &&
|
this.channels_latest[channel] > 0 &&
|
||||||
(this.channels_unread[channel] === undefined ||
|
(this.channels_unread[channel] === undefined ||
|
||||||
@@ -171,8 +136,11 @@ class TfTabNewsElement extends LitElement {
|
|||||||
return this.hash.startsWith('##') ? this.hash.substring(2) : undefined;
|
return this.hash.startsWith('##') ? this.hash.substring(2) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
compare_follows(a, b) {
|
compare_follows() {
|
||||||
return b[1].ts > a[1].ts ? 1 : b[1].ts < a[1].ts ? -1 : 0;
|
const now = new Date().valueOf();
|
||||||
|
return function (a, b) {
|
||||||
|
return (b[1].ts > now ? -1 : b[1].ts) - (a[1].ts > now ? -1 : a[1].ts);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
suggested_follows() {
|
suggested_follows() {
|
||||||
@@ -181,26 +149,14 @@ class TfTabNewsElement extends LitElement {
|
|||||||
** pinned at the top.
|
** pinned at the top.
|
||||||
*/
|
*/
|
||||||
let self = this;
|
let self = this;
|
||||||
let now = new Date().valueOf();
|
|
||||||
return Object.entries(this.users)
|
return Object.entries(this.users)
|
||||||
.filter((x) => x[1].ts < now)
|
|
||||||
.filter((x) => x[1].follow_depth > 1)
|
.filter((x) => x[1].follow_depth > 1)
|
||||||
.sort(self.compare_follows)
|
.sort(self.compare_follows())
|
||||||
.slice(0, 8)
|
.slice(0, 8)
|
||||||
.map((x) => x[0]);
|
.map((x) => x[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async enable_peer_exchange() {
|
|
||||||
await tfrpc.rpc.globalSettingsSet('peer_exchange', true);
|
|
||||||
await this.check_peer_exchange();
|
|
||||||
}
|
|
||||||
|
|
||||||
is_loading() {
|
|
||||||
return this.shadowRoot?.getElementById('news')?.loading;
|
|
||||||
}
|
|
||||||
|
|
||||||
render_sidebar() {
|
render_sidebar() {
|
||||||
let self_key = JSON.stringify([this.whoami]);
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="w3-sidebar w3-bar-block w3-theme-d1 w3-collapse w3-animate-left"
|
class="w3-sidebar w3-bar-block w3-theme-d1 w3-collapse w3-animate-left"
|
||||||
@@ -221,7 +177,7 @@ class TfTabNewsElement extends LitElement {
|
|||||||
href="#"
|
href="#"
|
||||||
class="w3-bar-item w3-button"
|
class="w3-bar-item w3-button"
|
||||||
style="font-weight: bold"
|
style="font-weight: bold"
|
||||||
>${this.hash.substring(1)}</a
|
>${this.hash.substring(2)}</a
|
||||||
>
|
>
|
||||||
`
|
`
|
||||||
: undefined}
|
: undefined}
|
||||||
@@ -239,44 +195,11 @@ class TfTabNewsElement extends LitElement {
|
|||||||
>${this.unread_status('@')}@mentions</a
|
>${this.unread_status('@')}@mentions</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="#👍"
|
href="#🔐"
|
||||||
class="w3-bar-item w3-button"
|
class="w3-bar-item w3-button"
|
||||||
style=${this.hash == '#👍' ? 'font-weight: bold' : undefined}
|
style=${this.hash == '#🔐' ? 'font-weight: bold' : undefined}
|
||||||
>${this.unread_status('👍')}👍votes</a
|
>${this.unread_status('🔐')}🔐private</a
|
||||||
>
|
>
|
||||||
<a
|
|
||||||
href="#🚩"
|
|
||||||
class="w3-bar-item w3-button"
|
|
||||||
style=${this.hash == '#🚩' ? 'font-weight: bold' : undefined}
|
|
||||||
>${this.unread_status('🚩')}🚩flagged</a
|
|
||||||
>
|
|
||||||
${Object.keys(this?.visible_private_messages ?? [])
|
|
||||||
?.sort()
|
|
||||||
?.map(
|
|
||||||
(key) => html`
|
|
||||||
<a
|
|
||||||
href=${'#🔐' +
|
|
||||||
(key == self_key ? '' : JSON.parse(key).join(','))}
|
|
||||||
class="w3-bar-item w3-button"
|
|
||||||
style=${this.hash ==
|
|
||||||
'#🔐' + (key == self_key ? '' : JSON.parse(key).join(','))
|
|
||||||
? 'font-weight: bold'
|
|
||||||
: undefined}
|
|
||||||
>${this.unread_status(
|
|
||||||
'🔐' + (key == self_key ? '' : 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)
|
${Object.keys(this.drafts)
|
||||||
.sort()
|
.sort()
|
||||||
.map(
|
.map(
|
||||||
@@ -300,62 +223,15 @@ class TfTabNewsElement extends LitElement {
|
|||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<a class="w3-bar-item w3-theme-d2 w3-button" href="#connections">
|
<h4 class="w3-bar-item w3-theme-d2">Connections</h4>
|
||||||
<h4 style="margin: 0">Connections</h4>
|
|
||||||
</a>
|
|
||||||
${this.connections?.filter((x) => x.id)?.length == 0
|
|
||||||
? html`
|
|
||||||
<button
|
|
||||||
class=${'w3-bar-item w3-button' +
|
|
||||||
(this.connections?.some((x) => x.flags.one_shot)
|
|
||||||
? ' w3-spin'
|
|
||||||
: '')}
|
|
||||||
@click=${() =>
|
|
||||||
this.dispatchEvent(
|
|
||||||
new Event('refresh', {bubbles: true, composed: true})
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
↻ Sync now
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="w3-bar-item w3-button w3-ripple"
|
|
||||||
@click=${() =>
|
|
||||||
this.dispatchEvent(
|
|
||||||
new Event('toggle_stay_connected', {
|
|
||||||
bubbles: true,
|
|
||||||
composed: true,
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span style="display: inline-block; width: 1.8em"
|
|
||||||
>${this.stay_connected ? '🔗' : '⛓️💥'}</span
|
|
||||||
>
|
|
||||||
${this.stay_connected ? 'Online mode' : 'Passive mode'}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class=${'w3-bar-item w3-button w3-border w3-leftbar w3-rightbar' +
|
|
||||||
(this.peer_exchange !== false ? ' w3-hide' : '')}
|
|
||||||
@click=${this.enable_peer_exchange}
|
|
||||||
>
|
|
||||||
🔍🌐 Use publicly advertised peers
|
|
||||||
</button>
|
|
||||||
`
|
|
||||||
: undefined}
|
|
||||||
${this.connections
|
${this.connections
|
||||||
.filter((x) => x.id)
|
.filter((x) => x.id && !x.destroy_reason)
|
||||||
.map(
|
.map(
|
||||||
(x) => html`
|
(x) => html`
|
||||||
<tf-user
|
<tf-user
|
||||||
class="w3-bar-item"
|
class="w3-bar-item"
|
||||||
style=${x.destroy_reason
|
style="max-width: 100%"
|
||||||
? 'border-left: 4px solid red; border-right: 4px solid red'
|
|
||||||
: x.connected
|
|
||||||
? x.flags?.one_shot
|
|
||||||
? 'border-left: 4px solid blue; border-right: 4px solid blue'
|
|
||||||
: 'border-left: 4px solid green; border-right: 4px solid green'
|
|
||||||
: ''}
|
|
||||||
id=${x.id}
|
id=${x.id}
|
||||||
fallback_name=${x.host}
|
|
||||||
.users=${this.users}
|
.users=${this.users}
|
||||||
></tf-user>
|
></tf-user>
|
||||||
`
|
`
|
||||||
@@ -380,14 +256,6 @@ class TfTabNewsElement extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
recipients() {
|
|
||||||
if (this.hash == '#🔐') {
|
|
||||||
return [this.whoami];
|
|
||||||
} else if (this.hash.startsWith('#🔐')) {
|
|
||||||
return this.hash.substring('#🔐'.length).split(',');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let profile =
|
let profile =
|
||||||
this.hash.startsWith('#@') && this.hash != '#@'
|
this.hash.startsWith('#@') && this.hash != '#@'
|
||||||
@@ -415,12 +283,9 @@ class TfTabNewsElement extends LitElement {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
return cache(html`
|
return cache(html`
|
||||||
<style>
|
|
||||||
${generate_theme()}
|
|
||||||
</style>
|
|
||||||
${this.render_sidebar()}
|
${this.render_sidebar()}
|
||||||
<div
|
<div
|
||||||
style="margin-left: 2in; padding: 0px; top: 0; height: 100vh; max-height: 100%; overflow: auto; contain: layout"
|
style="margin-left: 2in; padding: 0px; top: 0; max-height: 100%; overflow: auto"
|
||||||
id="main"
|
id="main"
|
||||||
class="w3-main"
|
class="w3-main"
|
||||||
>
|
>
|
||||||
@@ -441,18 +306,13 @@ class TfTabNewsElement extends LitElement {
|
|||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
style="width: 100%; max-width: 100%; white-space: nowrap; overflow: hidden"
|
id="show_sidebar"
|
||||||
|
class="w3-button w3-hide-large"
|
||||||
|
@click=${this.show_sidebar}
|
||||||
>
|
>
|
||||||
<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>
|
</div>
|
||||||
|
Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
|
||||||
${edit_profile}
|
${edit_profile}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -463,7 +323,6 @@ class TfTabNewsElement extends LitElement {
|
|||||||
.drafts=${this.drafts}
|
.drafts=${this.drafts}
|
||||||
@tf-draft=${this.draft}
|
@tf-draft=${this.draft}
|
||||||
.channel=${this.channel()}
|
.channel=${this.channel()}
|
||||||
.recipients=${this.recipients()}
|
|
||||||
></tf-compose>
|
></tf-compose>
|
||||||
</div>
|
</div>
|
||||||
${profile}
|
${profile}
|
||||||
@@ -480,8 +339,6 @@ class TfTabNewsElement extends LitElement {
|
|||||||
.channels_unread=${this.channels_unread}
|
.channels_unread=${this.channels_unread}
|
||||||
.channels_latest=${this.channels_latest}
|
.channels_latest=${this.channels_latest}
|
||||||
.private_messages=${this.private_messages}
|
.private_messages=${this.private_messages}
|
||||||
.grouped_private_messages=${this.grouped_private_messages}
|
|
||||||
.recent_reactions=${this.recent_reactions}
|
|
||||||
></tf-tab-news-feed>
|
></tf-tab-news-feed>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
136
apps/ssb/tf-tab-query.js
Normal file
136
apps/ssb/tf-tab-query.js
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
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, until} from './lit-all.min.js';
|
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
import {styles, generate_theme} from './tf-styles.js';
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
class TfTabSearchElement extends LitElement {
|
class TfTabSearchElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@@ -11,9 +11,6 @@ class TfTabSearchElement extends LitElement {
|
|||||||
following: {type: Array},
|
following: {type: Array},
|
||||||
query: {type: String},
|
query: {type: String},
|
||||||
expanded: {type: Object},
|
expanded: {type: Object},
|
||||||
messages: {type: Array},
|
|
||||||
results: {type: Array},
|
|
||||||
error: {type: Object},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,40 +38,29 @@ class TfTabSearchElement extends LitElement {
|
|||||||
search.select();
|
search.select();
|
||||||
}
|
}
|
||||||
await tfrpc.rpc.setHash('#q=' + encodeURIComponent(query));
|
await tfrpc.rpc.setHash('#q=' + encodeURIComponent(query));
|
||||||
this.error = undefined;
|
let results = await tfrpc.rpc.query(
|
||||||
this.results = [];
|
`
|
||||||
this.messages = [];
|
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
try {
|
FROM messages_fts(?)
|
||||||
if (query.startsWith('sql:')) {
|
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||||
this.messages = [];
|
JOIN json_each(?) AS following ON messages.author = following.value
|
||||||
this.results = await tfrpc.rpc.query(
|
ORDER BY timestamp DESC limit 100
|
||||||
query.substring('sql:'.length),
|
`,
|
||||||
[]
|
['"' + query.replace('"', '""') + '"', JSON.stringify(this.following)]
|
||||||
);
|
);
|
||||||
} else {
|
console.log('Done.');
|
||||||
let results = await tfrpc.rpc.query(
|
search = this.renderRoot.getElementById('search');
|
||||||
`
|
if (search) {
|
||||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
search.value = query;
|
||||||
FROM messages_fts(?)
|
search.focus();
|
||||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
search.select();
|
||||||
JOIN json_each(?) AS following ON messages.author = following.value
|
}
|
||||||
ORDER BY timestamp DESC limit 100
|
this.renderRoot.getElementById('news').messages = results;
|
||||||
`,
|
}
|
||||||
['"' + query.replace('"', '""') + '"', JSON.stringify(this.following)]
|
|
||||||
);
|
search_keydown(event) {
|
||||||
search = this.renderRoot.getElementById('search');
|
if (event.keyCode == 13) {
|
||||||
if (search) {
|
this.query = this.renderRoot.getElementById('search').value;
|
||||||
search.value = query;
|
|
||||||
search.focus();
|
|
||||||
search.select();
|
|
||||||
}
|
|
||||||
this.messages = results;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.messages = [];
|
|
||||||
this.results = [];
|
|
||||||
this.error = e;
|
|
||||||
console.log(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,58 +87,18 @@ class TfTabSearchElement extends LitElement {
|
|||||||
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
|
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
|
||||||
}
|
}
|
||||||
|
|
||||||
render_results() {
|
render() {
|
||||||
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) {
|
if (this.query !== this.last_query) {
|
||||||
this.last_query = this.query;
|
this.last_query = this.query;
|
||||||
this._query = this.search(this.query);
|
this.search(this.query);
|
||||||
}
|
}
|
||||||
await this._query;
|
let self = this;
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<div style="display: flex; flex-direction: row; gap: 4px">
|
||||||
${generate_theme()}
|
<input type="text" class="w3-input w3-theme-d1" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
|
||||||
</style>
|
<button class="w3-button w3-theme-d1" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button>
|
||||||
<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>
|
</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 {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||||
import {styles, generate_theme} from './tf-styles.js';
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
class TfTagElement extends LitElement {
|
class TfTagElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@@ -17,15 +17,11 @@ class TfTagElement extends LitElement {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
let number = this.count ? html` (${this.count})` : undefined;
|
let number = this.count ? html` (${this.count})` : undefined;
|
||||||
return html`
|
return html`<a
|
||||||
<style>
|
href=${'#' + encodeURIComponent(this.tag)}
|
||||||
${generate_theme()}</style
|
class="w3-tag w3-theme-d1 w3-round-4 w3-button"
|
||||||
><a
|
>${this.tag}${number}</a
|
||||||
href=${'#' + encodeURIComponent(this.tag)}
|
> `;
|
||||||
class="w3-tag w3-theme-d1 w3-round-4 w3-button"
|
|
||||||
>${this.tag}${number}</a
|
|
||||||
>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import {LitElement, html} from './lit-all.min.js';
|
import {LitElement, html} from './lit-all.min.js';
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
import {styles, generate_theme} from './tf-styles.js';
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
class TfUserElement extends LitElement {
|
class TfUserElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
id: {type: String},
|
id: {type: String},
|
||||||
fallback_name: {type: String},
|
|
||||||
icon_only: {type: Boolean},
|
|
||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
nolink: {type: Boolean},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,22 +15,11 @@ class TfUserElement extends LitElement {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.id = null;
|
this.id = null;
|
||||||
this.fallback_name = null;
|
|
||||||
this.icon_only = false;
|
|
||||||
this.users = {};
|
this.users = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let user = this.users[this.id];
|
let user = this.users[this.id];
|
||||||
if (!this.users[this.id]) {
|
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent('tf-request-user', {
|
|
||||||
bubbles: true,
|
|
||||||
composed: true,
|
|
||||||
detail: {id: this.id},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let shape =
|
let shape =
|
||||||
user?.follow_depth === undefined || user.follow_depth >= 2
|
user?.follow_depth === undefined || user.follow_depth >= 2
|
||||||
? 'w3-circle'
|
? 'w3-circle'
|
||||||
@@ -44,40 +30,27 @@ class TfUserElement extends LitElement {
|
|||||||
>😎</span
|
>😎</span
|
||||||
>`;
|
>`;
|
||||||
let name = this.users?.[this.id]?.name;
|
let name = this.users?.[this.id]?.name;
|
||||||
let name_string = name ?? this.fallback_name ?? this.id;
|
name = html`<a target="_top" href=${'#' + this.id}
|
||||||
name = this.icon_only
|
>${name !== undefined ? name : this.id}</a
|
||||||
? undefined
|
>`;
|
||||||
: !this.nolink
|
|
||||||
? html`<a target="_top" href=${'#' + encodeURIComponent(this.id)}
|
|
||||||
>${name_string}</a
|
|
||||||
>`
|
|
||||||
: html`<span>${name_string}</span>`;
|
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
let image_link = user.image;
|
let image_link = user.image;
|
||||||
if (typeof image_link == 'string' && !image_link.startsWith('&')) {
|
image_link =
|
||||||
try {
|
typeof image_link == 'string' ? image_link : image_link?.link;
|
||||||
image_link = JSON.parse(image_link)?.link;
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
if (image_link !== undefined) {
|
if (image_link !== undefined) {
|
||||||
image = html`<img
|
image = html`<img
|
||||||
class=${'w3-theme-l4 ' + shape}
|
class=${'w3-theme-l4 ' + shape}
|
||||||
style="width: 2em; height: 2em; vertical-align: middle; object-fit: cover"
|
style="width: 2em; height: 2em; vertical-align: middle; object-fit: cover"
|
||||||
src="/${image_link}/view"
|
src="/${image_link}/view"
|
||||||
title=${name_string + ' (' + this.id + ')'}
|
|
||||||
/>`;
|
/>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return html` <style>
|
return html` <div
|
||||||
${generate_theme()}
|
style="display: inline-block; vertical-align: middle; font-weight: bold; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis"
|
||||||
</style>
|
>
|
||||||
<div
|
${image} ${name}
|
||||||
style=${'display: inline-block; vertical-align: middle; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis' +
|
</div>`;
|
||||||
(this.nolink ? '' : '; font-weight: bold')}
|
|
||||||
>
|
|
||||||
${image} ${name}
|
|
||||||
</div>`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ function image(node, entering) {
|
|||||||
'</div>'
|
'</div>'
|
||||||
);
|
);
|
||||||
if (this.options.safe && potentiallyUnsafe(node.destination)) {
|
if (this.options.safe && potentiallyUnsafe(node.destination)) {
|
||||||
this.lit('<img src="" title="');
|
this.lit('<img src="" alt="');
|
||||||
} else {
|
} else {
|
||||||
this.lit('<img src="' + this.esc(node.destination) + '" title="');
|
this.lit('<img src="' + this.esc(node.destination) + '" alt="');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.disableTags += 1;
|
this.disableTags += 1;
|
||||||
@@ -104,12 +104,12 @@ export function markdown(md) {
|
|||||||
node.destination.startsWith('@') &&
|
node.destination.startsWith('@') &&
|
||||||
node.destination.endsWith('.ed25519')
|
node.destination.endsWith('.ed25519')
|
||||||
) {
|
) {
|
||||||
node.destination = '#' + encodeURIComponent(node.destination);
|
node.destination = '#' + node.destination;
|
||||||
} else if (
|
} else if (
|
||||||
node.destination.startsWith('%') &&
|
node.destination.startsWith('%') &&
|
||||||
node.destination.endsWith('.sha256')
|
node.destination.endsWith('.sha256')
|
||||||
) {
|
) {
|
||||||
node.destination = '#' + encodeURIComponent(node.destination);
|
node.destination = '#' + node.destination;
|
||||||
} else if (
|
} else if (
|
||||||
node.destination.startsWith('&') &&
|
node.destination.startsWith('&') &&
|
||||||
node.destination.endsWith('.sha256')
|
node.destination.endsWith('.sha256')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "💾",
|
"emoji": "💾",
|
||||||
"previous": "&tzZFIe7Y54O4sx1QtAPdemkXh+p5qHXSG/dlS7NP6OQ=.sha256"
|
"previous": "&mvGTlWKFR5QM/3nb4fJ2WQq0n/gNKvBmhGDkAvb8ki8=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ async function query(sql, args) {
|
|||||||
|
|
||||||
async function get_biggest() {
|
async function get_biggest() {
|
||||||
return query(`
|
return query(`
|
||||||
select author, size from messages_stats group by author order by size desc limit 10;
|
select author, sum(length(content)) as size from messages group by author order by size desc limit 10;
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,14 +62,15 @@ function nice_size(bytes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
await app.setDocument('<p style="color: #fff">Analyzing feeds...</p>');
|
await app.setDocument(
|
||||||
let most_follows = get_most_follows();
|
'<p style="color: #fff">Finding the top 10 largest feeds...</p>'
|
||||||
|
);
|
||||||
|
let most_follows = await get_most_follows();
|
||||||
let total = await get_total();
|
let total = await get_total();
|
||||||
let identities = await ssb.getAllIdentities();
|
let identities = await ssb.getAllIdentities();
|
||||||
let following1 = await ssb.following(identities, 1);
|
let following1 = await ssb.following(identities, 1);
|
||||||
let following2 = await ssb.following(identities, 2);
|
let following2 = await ssb.following(identities, 2);
|
||||||
let biggest = await get_biggest();
|
let biggest = await get_biggest();
|
||||||
most_follows = await most_follows;
|
|
||||||
let names = await get_names(
|
let names = await get_names(
|
||||||
[].concat(
|
[].concat(
|
||||||
biggest.map((x) => x.author),
|
biggest.map((x) => x.author),
|
||||||
@@ -93,7 +94,7 @@ async function main() {
|
|||||||
}
|
}
|
||||||
let html = `<body style="color: #000; background-color: #ddd">\n
|
let html = `<body style="color: #000; background-color: #ddd">\n
|
||||||
<h1>Storage Summary</h1>
|
<h1>Storage Summary</h1>
|
||||||
<h2>Top Accounts by Size</h2>
|
<h2>Top 10 Accounts by Size</h2>
|
||||||
<ol>`;
|
<ol>`;
|
||||||
for (let item of biggest) {
|
for (let item of biggest) {
|
||||||
html += `<li>
|
html += `<li>
|
||||||
@@ -104,7 +105,7 @@ async function main() {
|
|||||||
}
|
}
|
||||||
html += `
|
html += `
|
||||||
</ol>
|
</ol>
|
||||||
<h2>Top Accounts by Follows</h2>
|
<h2>Top 10 Accounts by Follows</h2>
|
||||||
<ol>`;
|
<ol>`;
|
||||||
for (let item of most_follows) {
|
for (let item of most_follows) {
|
||||||
html += `<li>
|
html += `<li>
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "tildefriends-app",
|
|
||||||
"emoji": "📦",
|
|
||||||
"previous": "&mhBOscDHiJ4VNnod27NOdRVC+4cXYZXIdYjsQBfmTYg=.sha256"
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
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();
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": [],
|
|
||||||
"sourcesContent": [],
|
|
||||||
"mappings": "",
|
|
||||||
"names": []
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": [],
|
|
||||||
"sourcesContent": [],
|
|
||||||
"mappings": "",
|
|
||||||
"names": []
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": [],
|
|
||||||
"sourcesContent": [],
|
|
||||||
"mappings": "",
|
|
||||||
"names": []
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
|||||||
speedscope@1.25.0
|
|
||||||
Wed Dec 3 07:18:39 PM EST 2025
|
|
||||||
810efdf0a4868bb28f1300f4daacd0db6b8a95bd
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"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": []
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "tildefriends-app",
|
|
||||||
"emoji": "🕸",
|
|
||||||
"previous": "&n7hu5b8/TsfiG6FDlCRG5nPCrIdCr96+xpIJ/aQT/uM=.sha256"
|
|
||||||
}
|
|
||||||
100
apps/web/app.js
100
apps/web/app.js
@@ -1,100 +0,0 @@
|
|||||||
let g_hash;
|
|
||||||
|
|
||||||
async function query(sql, params) {
|
|
||||||
let results = [];
|
|
||||||
await ssb.sqlAsync(sql, params, function (row) {
|
|
||||||
results.push(row);
|
|
||||||
});
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resolve(id) {
|
|
||||||
try {
|
|
||||||
let blob = await ssb.blobGet(id);
|
|
||||||
if (blob) {
|
|
||||||
let json;
|
|
||||||
try {
|
|
||||||
json = JSON.parse(utf8Decode(blob));
|
|
||||||
} catch {
|
|
||||||
return {id: utf8Decode(blob)};
|
|
||||||
}
|
|
||||||
if (json?.links) {
|
|
||||||
for (let [key, value] of Object.entries(json.links)) {
|
|
||||||
json.links[key] = await resolve(value);
|
|
||||||
}
|
|
||||||
return json;
|
|
||||||
} else {
|
|
||||||
return 'huh?' + json;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return `missing<${id}>`;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return id + ': ' + e.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function get_names(identities) {
|
|
||||||
return Object.fromEntries(
|
|
||||||
(
|
|
||||||
await query(
|
|
||||||
`
|
|
||||||
SELECT author, name FROM (
|
|
||||||
SELECT
|
|
||||||
messages.author,
|
|
||||||
RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank,
|
|
||||||
messages.content ->> 'name' AS name
|
|
||||||
FROM messages
|
|
||||||
JOIN json_each(?) AS identities ON identities.value = messages.author
|
|
||||||
WHERE
|
|
||||||
json_extract(messages.content, '$.type') = 'about' AND
|
|
||||||
content ->> 'about' = messages.author AND name IS NOT NULL)
|
|
||||||
WHERE author_rank = 1
|
|
||||||
`,
|
|
||||||
[JSON.stringify(identities)]
|
|
||||||
)
|
|
||||||
).map((x) => [x.author, x.name])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function render(hash) {
|
|
||||||
g_hash = hash;
|
|
||||||
if (!hash) {
|
|
||||||
let sites = await query(
|
|
||||||
`
|
|
||||||
SELECT site.author, site.id
|
|
||||||
FROM messages site
|
|
||||||
WHERE site.content ->> 'type' = 'web-init'
|
|
||||||
`,
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
let names = await get_names(sites.map((x) => x.author));
|
|
||||||
if (hash === g_hash) {
|
|
||||||
await app.setDocument(
|
|
||||||
`<ul style="background-color: #ddd">${sites.map((x) => `<li><a target="_top" href="#${encodeURIComponent(x.id)}">${names[x.author] ?? x.author} - ${x.id}</a></li>`).join('\n')}</ul>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let site_id =
|
|
||||||
hash.charAt(0) == '#'
|
|
||||||
? decodeURIComponent(hash.substring(1))
|
|
||||||
: decodeURIComponent(hash);
|
|
||||||
await app.setDocument(`<html style="margin: 0; padding: 0; width: 100vw; height: 100vh; margin: 0; padding: 0">
|
|
||||||
<body style="display: flex; flex-direction: column; width: 100vw; height: 100vh">
|
|
||||||
<iframe src="${encodeURIComponent(site_id)}/index.html" style="flex: 1 1; border: 0; background-color: #fff"></iframe>
|
|
||||||
</body>
|
|
||||||
</html>`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
core.register('message', async function message_handler(message) {
|
|
||||||
if (message.event == 'hashChange') {
|
|
||||||
await render(message.hash);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
render(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
async function query(sql, params) {
|
|
||||||
let results = [];
|
|
||||||
await ssb.sqlAsync(sql, params, function (row) {
|
|
||||||
results.push(row);
|
|
||||||
});
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
function guess_content_type(name) {
|
|
||||||
if (name.endsWith('.html')) {
|
|
||||||
return 'text/html; charset=UTF-8';
|
|
||||||
} else if (name.endsWith('.js') || name.endsWith('.mjs')) {
|
|
||||||
return 'text/javascript; charset=UTF-8';
|
|
||||||
} else if (name.endsWith('.css')) {
|
|
||||||
return 'text/stylesheet; charset=UTF-8';
|
|
||||||
} else {
|
|
||||||
return 'application/binary';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
let path = request.path.replaceAll(/(%[0-9a-fA-F]{2})/g, (x) =>
|
|
||||||
String.fromCharCode(parseInt(x.substring(1), 16))
|
|
||||||
);
|
|
||||||
let match = path.match(/^(%.{44}\.sha256)(?:\/)?(.*)$/);
|
|
||||||
|
|
||||||
let content_type = guess_content_type(request.path);
|
|
||||||
let root = await query(
|
|
||||||
`
|
|
||||||
SELECT root.content ->> 'root' AS root
|
|
||||||
FROM messages site
|
|
||||||
JOIN messages root
|
|
||||||
ON site.id = ? AND root.author = site.author AND root.content ->> 'site' = site.id
|
|
||||||
ORDER BY root.sequence DESC LIMIT 1
|
|
||||||
`,
|
|
||||||
[match[1]]
|
|
||||||
);
|
|
||||||
let root_id = root[0]['root'];
|
|
||||||
let last_id = root_id;
|
|
||||||
let blob = await ssb.blobGet(root_id);
|
|
||||||
try {
|
|
||||||
for (let part of match[2]?.split('/')) {
|
|
||||||
let dir = JSON.parse(utf8Decode(blob));
|
|
||||||
last_id = dir?.links[part];
|
|
||||||
blob = await ssb.blobGet(dir?.links[part]);
|
|
||||||
content_type = guess_content_type(part);
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
respond({
|
|
||||||
status_code: 200,
|
|
||||||
data: blob ? utf8Decode(blob) : `${last_id} not found`,
|
|
||||||
content_type: content_type,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch(function (e) {
|
|
||||||
respond({
|
|
||||||
status_code: 200,
|
|
||||||
data: `${e.message}\n${e.stack}`,
|
|
||||||
content_type: 'text/plain',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "👋",
|
"emoji": "👋",
|
||||||
"previous": "&sVSmI40DUgnS4TUa2AiKrlNj+qN3WDeXII3364OSMIo=.sha256"
|
"previous": "&wAb7J6E35xEXpiXsQ6t1RaWTGIvlatUnyH8ipF6pVic=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
5
apps/welcome/app.js
Normal file
5
apps/welcome/app.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
async function main() {
|
||||||
|
await app.setDocument(utf8Decode(getFile('index.html')));
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 640 640" width="32" height="32"><path d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12" style="fill:#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6M125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1m300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1" style="fill:#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8s2 16.3 9.1 20c7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3s17.4 1.7 22.5-5.3c5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8z" style="fill:#609926"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.1 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 86 KiB |
@@ -8,10 +8,14 @@
|
|||||||
<link rel="stylesheet" href="regular.min.css" />
|
<link rel="stylesheet" href="regular.min.css" />
|
||||||
<link rel="stylesheet" href="solid.min.css" />
|
<link rel="stylesheet" href="solid.min.css" />
|
||||||
<link rel="stylesheet" href="brands.min.css" />
|
<link rel="stylesheet" href="brands.min.css" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
img {
|
img {
|
||||||
margin-bottom: -8px;
|
margin-bottom: -8px;
|
||||||
}
|
}
|
||||||
|
.mySlides {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<base target="_top" />
|
<base target="_top" />
|
||||||
</head>
|
</head>
|
||||||
@@ -24,39 +28,65 @@
|
|||||||
<b>😎 Tilde Friends</b>
|
<b>😎 Tilde Friends</b>
|
||||||
</h1>
|
</h1>
|
||||||
<h1 class="w3-xxlarge w3-text-green">
|
<h1 class="w3-xxlarge w3-text-green">
|
||||||
<b>a Secure Scuttlebutt decentralized social network client</b>
|
<b
|
||||||
|
>the Secure Scuttlebutt decentralized social network client that's
|
||||||
|
<i>fancy🎩</i></b
|
||||||
|
>
|
||||||
</h1>
|
</h1>
|
||||||
<p>
|
<p>
|
||||||
In addition to participating in Secure Scuttlebutt, Tilde Friends is
|
In addition to participating in Secure Scuttlebutt, Tilde Friends is
|
||||||
a platform for building, running, and sharing applications.
|
a platform for building, running, and sharing applications.
|
||||||
</p>
|
</p>
|
||||||
<a
|
<p>
|
||||||
class="w3-button w3-blue w3-padding-large"
|
Available for lots of devices:
|
||||||
href="https://www.tildefriends.net/~core/ssb/"
|
<i class="fa-brands fa-linux w3-xlarge"></i>
|
||||||
>🦀 Try It</a
|
<i class="fa-brands fa-android w3-xlarge"></i>
|
||||||
>
|
<i class="fa-brands fa-apple w3-xlarge"></i>
|
||||||
|
<i class="fa fa-mobile-screen w3-xlarge"></i>
|
||||||
|
<i class="fa-brands fa-windows w3-xlarge"></i>
|
||||||
|
</p>
|
||||||
<a
|
<a
|
||||||
class="w3-button w3-black w3-padding-large"
|
class="w3-button w3-black w3-padding-large"
|
||||||
href="https://dev.tildefriends.net/cory/tildefriends/releases/latest"
|
href="https://dev.tildefriends.net/cory/tildefriends/releases"
|
||||||
><i class="fa fa-download"></i> Download</a
|
><i class="fa fa-download"></i> Download</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="w3-button w3-black w3-padding-large"
|
class="w3-button w3-black w3-padding-large"
|
||||||
href="https://dev.tildefriends.net/cory/tildefriends"
|
href="https://www.tildefriends.net/~core/ssb/"
|
||||||
|
><i class="fa fa-link"></i> Try It</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="w3-button w3-black w3-padding-large"
|
||||||
|
href="https://dev.tildefriends.net/"
|
||||||
|
><i class="fa fa-mug-hot"></i> Development</a
|
||||||
>
|
>
|
||||||
<img src="gitea.svg" style="height: 1em; margin: 0" />
|
|
||||||
Development
|
|
||||||
</a>
|
|
||||||
<a
|
<a
|
||||||
class="w3-button w3-black w3-padding-large"
|
class="w3-button w3-black w3-padding-large"
|
||||||
href="https://docs.tildefriends.net/"
|
href="https://docs.tildefriends.net/"
|
||||||
><i class="fa fa-book"></i> Documentation</a
|
><i class="fa fa-book"></i> Documentation</a
|
||||||
>
|
>
|
||||||
<a
|
<p>
|
||||||
class="w3-button w3-black w3-padding-large"
|
<a
|
||||||
href="https://www.tildefriends.net/~cory/tildeblog/"
|
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||||
><i class="fa fa-solid fa-square-rss"></i> Blog</a
|
href="https://f-droid.org/en/packages/com.unprompted.tildefriends.fdroid/"
|
||||||
>
|
><img src="f-droid.svg" style="height: 2em; margin: 0" /> Get it
|
||||||
|
on F-Droid</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||||
|
href="https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage"
|
||||||
|
>
|
||||||
|
<img src="appimage.svg" style="height: 2em; margin: 0" />
|
||||||
|
Get Linux 64-bit AppImage
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||||
|
href="https://play.google.com/store/apps/details?id=com.unprompted.tildefriends"
|
||||||
|
>
|
||||||
|
<img src="googleplay.svg" style="height: 2em; margin: 0" />
|
||||||
|
Get it on Google Play (Open Testing)
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w3-col l4 m6">
|
<div class="w3-col l4 m6">
|
||||||
<img src="tildefriends.png" class="w3-image w3-right w3-hide-small" />
|
<img src="tildefriends.png" class="w3-image w3-right w3-hide-small" />
|
||||||
@@ -74,119 +104,15 @@
|
|||||||
<h2>First-time user checklist:</h2>
|
<h2>First-time user checklist:</h2>
|
||||||
<ol type="1" style="text-align: left">
|
<ol type="1" style="text-align: left">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a href="https://dev.tildefriends.net/cory/tildefriends/releases"
|
||||||
href="https://dev.tildefriends.net/cory/tildefriends/releases/latest"
|
|
||||||
>Download</a
|
>Download</a
|
||||||
>
|
>
|
||||||
Tilde Friends or use
|
Tilde Friends or use
|
||||||
<a href="https://www.tildefriends.net/"
|
<a href="https://www.tildefriends.net/"
|
||||||
>https://www.tildefriends.net/</a
|
>https://www.tildefriends.net/</a
|
||||||
>.
|
>.
|
||||||
<div class="w3-cell-row">
|
|
||||||
<div class="w3-container w3-cell w3-mobile">
|
|
||||||
<h3>Mobile</h3>
|
|
||||||
<p>
|
|
||||||
<a
|
|
||||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
|
||||||
href="https://f-droid.org/en/packages/com.unprompted.tildefriends.fdroid/"
|
|
||||||
><img src="f-droid.svg" style="height: 2em; margin: 0" />
|
|
||||||
Get it on F-Droid</a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
|
||||||
href="https://play.google.com/store/apps/details?id=com.unprompted.tildefriends"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="googleplay.svg"
|
|
||||||
style="height: 2em; margin: 0"
|
|
||||||
/>
|
|
||||||
Get it on Google Play
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
|
||||||
href="https://apps.apple.com/us/app/tilde-friends/id6742085604"
|
|
||||||
>
|
|
||||||
<img src="ios.svg" style="height: 2em; margin: 0" />
|
|
||||||
Get it on iOS
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p>Just launch the app.</p>
|
|
||||||
</div>
|
|
||||||
<div class="w3-container w3-cell w3-mobile">
|
|
||||||
<h3>Web</h3>
|
|
||||||
<p>
|
|
||||||
<a
|
|
||||||
class="w3-button w3-round-large w3-blue w3-padding-large"
|
|
||||||
href="https://www.tildefriends.net/~core/ssb/"
|
|
||||||
>🦀 Try It</a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a href="/login?return=/~core/intro"
|
|
||||||
>Register an account with tildefriends.net</a
|
|
||||||
>
|
|
||||||
to take it for a spin right away.
|
|
||||||
</p>
|
|
||||||
<h3>PeachCloud</h3>
|
|
||||||
<p>
|
|
||||||
Tilde Friends is also a part of 🍑☁️<a
|
|
||||||
href="https://peach-docs.commoninternet.net/"
|
|
||||||
>PeachCloud</a
|
|
||||||
>, which is available on
|
|
||||||
<a href="https://apps.yunohost.org/app/peachpub"
|
|
||||||
>YunoHost</a
|
|
||||||
>
|
|
||||||
for accessible self-hosting.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="w3-container w3-cell w3-mobile">
|
|
||||||
<h3>Desktop</h3>
|
|
||||||
<p>
|
|
||||||
<a
|
|
||||||
class="w3-button w3-round-large w3-black w3-padding-large"
|
|
||||||
href="https://dev.tildefriends.net/cory/tildefriends/releases"
|
|
||||||
><i class="fa fa-download"></i> Download</a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
class="w3-button w3-round-large w3-padding w3-blue-gray"
|
|
||||||
href="https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage"
|
|
||||||
>
|
|
||||||
<img src="appimage.svg" style="height: 2em; margin: 0" />
|
|
||||||
Get Linux x86_64 AppImage
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Tilde Friends is distributed as a single executable file (or
|
|
||||||
source that you can
|
|
||||||
<a href="http://dev.tildefriends.net">build yourself</a>)
|
|
||||||
and stores all of its data in a single
|
|
||||||
file(<code>db.sqlite</code>). You can generally download the
|
|
||||||
latest executable from
|
|
||||||
<a
|
|
||||||
href="https://dev.tildefriends.net/cory/tildefriends/releases"
|
|
||||||
>releases</a
|
|
||||||
>
|
|
||||||
for your platform, mark it as executable (<code
|
|
||||||
>chmod +x tildefriends*</code
|
|
||||||
>
|
|
||||||
on macOS and Linux), and run. Run with <code>-h</code> to
|
|
||||||
learn more.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Tilde Friends will run in the console and provide a web
|
|
||||||
interface at
|
|
||||||
<a href="http://localhost:12345/">http://localhost:12345/</a
|
|
||||||
>. You will have to register a username and password to sign
|
|
||||||
into your instance.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
After a <a href="/~core/intro">brief introduction</a>, Tilde
|
|
||||||
Friends will take you to the Secure Scuttlebutt social network
|
|
||||||
app.
|
|
||||||
</p>
|
|
||||||
</li>
|
</li>
|
||||||
|
<li>Create an account to identify yourself with that instance.</li>
|
||||||
<li>
|
<li>
|
||||||
Describe yourself in your profile in the <b>ssb</b> app. Give
|
Describe yourself in your profile in the <b>ssb</b> app. Give
|
||||||
yourself a name and an avatar if you like.
|
yourself a name and an avatar if you like.
|
||||||
@@ -220,11 +146,11 @@
|
|||||||
<!-- SSB Section -->
|
<!-- SSB Section -->
|
||||||
<div class="w3-light-grey">
|
<div class="w3-light-grey">
|
||||||
<div class="w3-row-padding w3-padding-64">
|
<div class="w3-row-padding w3-padding-64">
|
||||||
<div class="w3-col l4 m6 s4 w3-center">
|
<div class="w3-col l4 m6 s4">
|
||||||
<a href="https://scuttlebutt.nz/"
|
<a href="https://scuttlebutt.nz/"
|
||||||
><img
|
><img
|
||||||
class="w3-image"
|
class="w3-image w3-round-large"
|
||||||
src="hermietildefriends.svg"
|
src="ssb.png"
|
||||||
alt="Secure Scuttlebutt"
|
alt="Secure Scuttlebutt"
|
||||||
/></a>
|
/></a>
|
||||||
</div>
|
</div>
|
||||||
@@ -268,11 +194,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sandbox Section -->
|
<!-- Sandbox Section -->
|
||||||
<div class="w3-padding-64 w3-pale-blue">
|
<div class="w3-padding-64 w3-grey">
|
||||||
<div class="w3-row-padding">
|
<div class="w3-row-padding">
|
||||||
<div class="w3-col">
|
<div class="w3-col">
|
||||||
<h1 class="w3-jumbo" style="text-align: right">
|
<h1 class="w3-jumbo" style="text-align: right">
|
||||||
<b>App Sandboxes</b>
|
<b>Sandbox Security</b>
|
||||||
</h1>
|
</h1>
|
||||||
<i
|
<i
|
||||||
class="fa fa-road-barrier w3-right w3-jumbo w3-text-yellow"
|
class="fa fa-road-barrier w3-right w3-jumbo w3-text-yellow"
|
||||||
@@ -280,7 +206,7 @@
|
|||||||
></i>
|
></i>
|
||||||
<p>
|
<p>
|
||||||
Tilde Friends tries to make sure apps can be trusted using similar
|
Tilde Friends tries to make sure apps can be trusted using similar
|
||||||
techniques to web browsers and operating systems.
|
techniques to how web browsers and operating systems do it.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
This is all a work in progress, and it varies by platform, so don't
|
This is all a work in progress, and it varies by platform, so don't
|
||||||
@@ -294,24 +220,16 @@
|
|||||||
|
|
||||||
<!-- Technlology Section -->
|
<!-- Technlology Section -->
|
||||||
<div class="w3-container w3-padding-64 w3-light-grey w3-center">
|
<div class="w3-container w3-padding-64 w3-light-grey w3-center">
|
||||||
<h1 class="w3-jumbo"><b>One Pile of Code</b></h1>
|
<h1 class="w3-jumbo"><b>Built the Old Fashioned Way</b></h1>
|
||||||
<div class="w3-left-align">
|
<p>
|
||||||
<p>
|
Tilde Friends strives to use only simple and widely adopted dependencies
|
||||||
Tilde Friends diverges from the Node.js web of modules from which
|
in order to keep it easy to build for all sorts of platforms and
|
||||||
Secure Scuttlebutt was first developed. Here we strive to maintain a
|
maintainable for a very long time.
|
||||||
single C program that works as a foundation for building a wide
|
</p>
|
||||||
variety of social and other applications.
|
<p>
|
||||||
</p>
|
Though of course for building Tilde Friends apps, you are free to use
|
||||||
<p>
|
whatever fits on top.
|
||||||
Tilde Friends uses only a handful of simple and widely adopted
|
</p>
|
||||||
dependencies in order to keep it easy to build for all sorts of
|
|
||||||
platforms and maintainable for a very long time.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Though of course for building Tilde Friends apps, you are free to use
|
|
||||||
whatever works.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w3-row" style="margin-top: 64px">
|
<div class="w3-row" style="margin-top: 64px">
|
||||||
<a
|
<a
|
||||||
@@ -344,6 +262,10 @@
|
|||||||
<i class="fa fa-lock w3-text-purple w3-jumbo"></i>
|
<i class="fa fa-lock w3-text-purple w3-jumbo"></i>
|
||||||
<p>libsodium</p>
|
<p>libsodium</p>
|
||||||
</a>
|
</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
|
<a
|
||||||
href="https://github.com/ianlancetaylor/libbacktrace"
|
href="https://github.com/ianlancetaylor/libbacktrace"
|
||||||
class="w3-col s3"
|
class="w3-col s3"
|
||||||
@@ -351,13 +273,13 @@
|
|||||||
<i class="fa fa-burst w3-text-pink w3-jumbo"></i>
|
<i class="fa fa-burst w3-text-pink w3-jumbo"></i>
|
||||||
<p>libbacktrace</p>
|
<p>libbacktrace</p>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w3-row" style="margin-top: 64px">
|
||||||
<a href="https://codemirror.net/docs/changelog/" class="w3-col s3">
|
<a href="https://codemirror.net/docs/changelog/" class="w3-col s3">
|
||||||
<i class="fa fa-keyboard w3-text-indigo w3-jumbo"></i>
|
<i class="fa fa-keyboard w3-text-indigo w3-jumbo"></i>
|
||||||
<p>CodeMirror</p>
|
<p>CodeMirror</p>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w3-row" style="margin-top: 64px">
|
|
||||||
<a href="https://github.com/jlfwong/speedscope/" class="w3-col s3">
|
<a href="https://github.com/jlfwong/speedscope/" class="w3-col s3">
|
||||||
<i class="fa fa-microscope w3-text-orange w3-jumbo"></i>
|
<i class="fa fa-microscope w3-text-orange w3-jumbo"></i>
|
||||||
<p>Speedscope</p>
|
<p>Speedscope</p>
|
||||||
@@ -370,6 +292,9 @@
|
|||||||
<i class="fa fa-book-atlas w3-text-purple w3-jumbo"></i>
|
<i class="fa fa-book-atlas w3-text-purple w3-jumbo"></i>
|
||||||
<p>c-ares</p>
|
<p>c-ares</p>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w3-row" style="margin-top: 64px">
|
||||||
<a href="https://www.gnu.org/software/make/" class="w3-col s3">
|
<a href="https://www.gnu.org/software/make/" class="w3-col s3">
|
||||||
<i class="fa fa-hammer w3-text-teal w3-jumbo"></i>
|
<i class="fa fa-hammer w3-text-teal w3-jumbo"></i>
|
||||||
<p>GNU Make</p>
|
<p>GNU Make</p>
|
||||||
@@ -381,7 +306,7 @@
|
|||||||
<footer class="w3-container w3-padding-32 w3-blue-grey w3-center w3-xlarge">
|
<footer class="w3-container w3-padding-32 w3-blue-grey w3-center w3-xlarge">
|
||||||
<p class="w3-medium">
|
<p class="w3-medium">
|
||||||
This page and Tilde Friends itself was made by Cory mostly in coffee
|
This page and Tilde Friends itself was made by Cory mostly in coffee
|
||||||
shops and a local pizza place while listening to Gary's Bangers.
|
shops and a local pizza place.
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="814" height="1000">
|
|
||||||
<path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 660 B |
BIN
apps/welcome/ssb.png
Normal file
BIN
apps/welcome/ssb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
@@ -1,4 +1,4 @@
|
|||||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||||
@@ -108,8 +108,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
|
||||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
|
||||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||||
@@ -150,7 +148,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||||
.w3-hover-none:hover{box-shadow:none!important}
|
.w3-hover-none:hover{box-shadow:none!important}
|
||||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
|
||||||
/* Colors */
|
/* Colors */
|
||||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||||
@@ -178,19 +175,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
|
||||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
|
||||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
|
||||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
|
||||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
|
||||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
|
||||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
|
||||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
|
||||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
|
||||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
|
||||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
|
||||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
|
||||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
|
||||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||||
|
|||||||
42
apps/wiki/lit-all.min.js
vendored
42
apps/wiki/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
249
core/app.js
Normal file
249
core/app.js
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
import * as core from './core.js';
|
||||||
|
|
||||||
|
let g_next_id = 1;
|
||||||
|
let g_calls = {};
|
||||||
|
|
||||||
|
let gSessionIndex = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function makeSessionId() {
|
||||||
|
return 'session_' + (gSessionIndex++).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function App() {
|
||||||
|
this._on_output = null;
|
||||||
|
this._send_queue = [];
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} callback
|
||||||
|
*/
|
||||||
|
App.prototype.readOutput = function (callback) {
|
||||||
|
this._on_output = callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} api
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
App.prototype.makeFunction = function (api) {
|
||||||
|
let self = this;
|
||||||
|
let result = function () {
|
||||||
|
let id = g_next_id++;
|
||||||
|
while (!id || g_calls[id]) {
|
||||||
|
id = g_next_id++;
|
||||||
|
}
|
||||||
|
let promise = new Promise(function (resolve, reject) {
|
||||||
|
g_calls[id] = {resolve: resolve, reject: reject};
|
||||||
|
});
|
||||||
|
let message = {
|
||||||
|
message: 'tfrpc',
|
||||||
|
method: api[0],
|
||||||
|
params: [...arguments],
|
||||||
|
id: id,
|
||||||
|
};
|
||||||
|
self.send(message);
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
Object.defineProperty(result, 'name', {value: api[0], writable: false});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} message
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} request
|
||||||
|
* @param {*} response
|
||||||
|
*/
|
||||||
|
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('ERROR', error, event.data, event.data.length, event.opCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (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({
|
||||||
|
message: '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 = makeSessionId();
|
||||||
|
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.app.readOutput(function (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 (message.action == 'resetPermission') {
|
||||||
|
if (process) {
|
||||||
|
process.resetPermission(message.permission);
|
||||||
|
}
|
||||||
|
} else if (message.action == 'setActiveIdentity') {
|
||||||
|
process.setActiveIdentity(message.identity);
|
||||||
|
} else if (message.action == 'createIdentity') {
|
||||||
|
await process.createIdentity();
|
||||||
|
} else if (message.message == 'tfrpc') {
|
||||||
|
if (message.id && g_calls[message.id]) {
|
||||||
|
if (message.error !== undefined) {
|
||||||
|
g_calls[message.id].reject(message.error);
|
||||||
|
} else {
|
||||||
|
g_calls[message.id].resolve(message.result);
|
||||||
|
}
|
||||||
|
delete g_calls[message.id];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (process && process.eventHandlers['message']) {
|
||||||
|
await core.invoke(process.eventHandlers['message'], [message]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (event.opCode == 0x8) {
|
||||||
|
// 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,10 +75,6 @@
|
|||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
#code_of_conduct:has(>textarea:empty) {
|
|
||||||
display: none;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<div style="display: flex; flex-direction: column; max-width: 1280px; margin: auto">
|
<div style="display: flex; flex-direction: column; max-width: 1280px; margin: auto">
|
||||||
<h1 ?hidden=${this.name}>Welcome.</h1>
|
<h1 ?hidden=${this.name}>Welcome.</h1>
|
||||||
@@ -130,10 +126,8 @@
|
|||||||
There is currently no administrator. You will be made administrator.
|
There is currently no administrator. You will be made administrator.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="code_of_conduct">
|
<h2>Code of Conduct</h2>
|
||||||
<h2>Code of Conduct</h2>
|
<textarea readonly rows="20" cols="80" style="resize: none">${this.code_of_conduct}</textarea>
|
||||||
<textarea readonly rows="20" style="resize: none; width: 100%">${this.code_of_conduct}</textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
865
core/client.js
865
core/client.js
File diff suppressed because it is too large
Load Diff
655
core/core.js
655
core/core.js
@@ -1,24 +1,32 @@
|
|||||||
/**
|
import * as app from './app.js';
|
||||||
* \file
|
import * as http from './http.js';
|
||||||
* \defgroup tfcore Tilde Friends Core JS
|
|
||||||
* Tilde Friends process management, in JavaScript.
|
|
||||||
* @{
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** All running processes. */
|
|
||||||
let gProcesses = {};
|
let gProcesses = {};
|
||||||
/** Whether stats are currently being sent. */
|
|
||||||
let gStatsTimer = false;
|
let gStatsTimer = false;
|
||||||
/** Effectively a process ID. */
|
let kPingInterval = 60 * 1000;
|
||||||
let g_handler_index = 0;
|
|
||||||
/** Whether updating accounts information is currently scheduled. */
|
|
||||||
let g_update_accounts_scheduled;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoke a handler.
|
* TODOC
|
||||||
* @param handlers The handlers on which to invoke the callback.
|
* @param {*} out
|
||||||
* @param argv Arguments to pass to the handlers.
|
* @param {*} error
|
||||||
* @return A promise.
|
*/
|
||||||
|
function printError(out, error) {
|
||||||
|
if (error.stackTrace) {
|
||||||
|
out.print(error.fileName + ':' + error.lineNumber + ': ' + error.message);
|
||||||
|
out.print(error.stackTrace);
|
||||||
|
} else {
|
||||||
|
for (let [k, v] of Object.entries(error)) {
|
||||||
|
out.print(k, v);
|
||||||
|
}
|
||||||
|
out.print(error.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} handlers
|
||||||
|
* @param {*} argv
|
||||||
|
* @returns
|
||||||
*/
|
*/
|
||||||
function invoke(handlers, argv) {
|
function invoke(handlers, argv) {
|
||||||
let promises = [];
|
let promises = [];
|
||||||
@@ -41,10 +49,10 @@ function invoke(handlers, argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcast a named event to all registered apps.
|
* TODOC
|
||||||
* @param eventName the name of the event.
|
* @param {*} eventName
|
||||||
* @param argv Arguments to pass to the handlers.
|
* @param {*} argv
|
||||||
* @return A promise.
|
* @returns
|
||||||
*/
|
*/
|
||||||
function broadcastEvent(eventName, argv) {
|
function broadcastEvent(eventName, argv) {
|
||||||
let promises = [];
|
let promises = [];
|
||||||
@@ -57,9 +65,9 @@ function broadcastEvent(eventName, argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a message to all other instances of the same app.
|
* TODOC
|
||||||
* @param message The message.
|
* @param {*} message
|
||||||
* @return A promise.
|
* @returns
|
||||||
*/
|
*/
|
||||||
function broadcast(message) {
|
function broadcast(message) {
|
||||||
let sender = this;
|
let sender = this;
|
||||||
@@ -78,13 +86,10 @@ function broadcast(message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a message to all instances of the same app running as the same user.
|
* TODOC
|
||||||
* @param user The user.
|
* @param {String} eventName
|
||||||
* @param packageOwner The owner of the app.
|
* @param {*} argv
|
||||||
* @param packageName The name of the app.
|
* @returns
|
||||||
* @param eventName The name of the event.
|
|
||||||
* @param argv The arguments to pass.
|
|
||||||
* @return A promise.
|
|
||||||
*/
|
*/
|
||||||
function broadcastAppEventToUser(
|
function broadcastAppEventToUser(
|
||||||
user,
|
user,
|
||||||
@@ -109,9 +114,10 @@ function broadcastAppEventToUser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get user context information for a call.
|
* TODOC
|
||||||
* @param caller The calling process.
|
* @param {*} caller
|
||||||
* @param process The receiving process.
|
* @param {*} process
|
||||||
|
* @returns
|
||||||
*/
|
*/
|
||||||
function getUser(caller, process) {
|
function getUser(caller, process) {
|
||||||
return {
|
return {
|
||||||
@@ -124,11 +130,43 @@ function getUser(caller, process) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a message.
|
* TODOC
|
||||||
* @param from The calling process.
|
* @param {*} user
|
||||||
* @param to The receiving process.
|
* @param {*} process
|
||||||
* @param message The message.
|
* @returns
|
||||||
* @return A promise.
|
*/
|
||||||
|
async function getApps(user, process) {
|
||||||
|
if (
|
||||||
|
process.credentials &&
|
||||||
|
process.credentials.session &&
|
||||||
|
process.credentials.session.name
|
||||||
|
) {
|
||||||
|
if (user && user !== process.credentials.session.name && user !== 'core') {
|
||||||
|
return {};
|
||||||
|
} else if (!user) {
|
||||||
|
user = process.credentials.session.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (user) {
|
||||||
|
let db = new Database(user);
|
||||||
|
try {
|
||||||
|
let names = JSON.parse(await db.get('apps'));
|
||||||
|
let result = {};
|
||||||
|
for (let name of names) {
|
||||||
|
result[name] = await db.get('path:' + name);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} from
|
||||||
|
* @param {*} to
|
||||||
|
* @param {*} message
|
||||||
|
* @returns
|
||||||
*/
|
*/
|
||||||
function postMessageInternal(from, to, message) {
|
function postMessageInternal(from, to, message) {
|
||||||
if (to.eventHandlers['message']) {
|
if (to.eventHandlers['message']) {
|
||||||
@@ -137,13 +175,14 @@ function postMessageInternal(from, to, message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or create a process for an app blob.
|
* TODOC
|
||||||
* @param blobId The blob identifier.
|
* @param {*} blobId
|
||||||
* @param key A unique key for the invocation.
|
* @param {*} key
|
||||||
* @param options Other options.
|
* @param {*} options
|
||||||
* @return The process.
|
* @returns
|
||||||
*/
|
*/
|
||||||
exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
async function getProcessBlob(blobId, key, options) {
|
||||||
|
// TODO(tasiaiso): break this down ?
|
||||||
let process = gProcesses[key];
|
let process = gProcesses[key];
|
||||||
if (!process && !(options && 'create' in options && !options.create)) {
|
if (!process && !(options && 'create' in options && !options.create)) {
|
||||||
let resolveReady;
|
let resolveReady;
|
||||||
@@ -156,91 +195,114 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
process.task = new Task();
|
process.task = new Task();
|
||||||
process.packageOwner = options.packageOwner;
|
process.packageOwner = options.packageOwner;
|
||||||
process.packageName = options.packageName;
|
process.packageName = options.packageName;
|
||||||
process.url = options?.url;
|
|
||||||
process.eventHandlers = {};
|
process.eventHandlers = {};
|
||||||
if (!options?.script || options?.script === 'app.js') {
|
if (!options?.script || options?.script === 'app.js') {
|
||||||
process._send_queue = [];
|
process.app = new app.App();
|
||||||
process._calls = {};
|
|
||||||
process._next_call_id = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
** Create a function wrapper that when called invokes a function on the app
|
|
||||||
** itself.
|
|
||||||
** @param api The function and argument names.
|
|
||||||
** @return A function.
|
|
||||||
*/
|
|
||||||
process.makeFunction = function (api) {
|
|
||||||
let result = function () {
|
|
||||||
let id = process._next_call_id++;
|
|
||||||
while (!id || process._calls[id]) {
|
|
||||||
id = process._next_call_id++;
|
|
||||||
}
|
|
||||||
let promise = new Promise(function (resolve, reject) {
|
|
||||||
process._calls[id] = {resolve: resolve, reject: reject};
|
|
||||||
});
|
|
||||||
let message = {
|
|
||||||
action: 'tfrpc',
|
|
||||||
method: api[0],
|
|
||||||
params: [...arguments],
|
|
||||||
id: id,
|
|
||||||
};
|
|
||||||
process.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.
|
|
||||||
*/
|
|
||||||
process.send = function (message) {
|
|
||||||
if (process._send_queue) {
|
|
||||||
if (process._on_output) {
|
|
||||||
process._send_queue.forEach((x) => process._on_output(x));
|
|
||||||
process._send_queue = null;
|
|
||||||
} else if (message) {
|
|
||||||
process._send_queue.push(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (message && process._on_output) {
|
|
||||||
process._on_output(message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
process.makeFunction = function (api) {
|
|
||||||
return function () {};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
process.lastActive = Date.now();
|
||||||
|
process.lastPing = null;
|
||||||
|
process.timeout = kPingInterval;
|
||||||
process.ready = new Promise(function (resolve, reject) {
|
process.ready = new Promise(function (resolve, reject) {
|
||||||
resolveReady = resolve;
|
resolveReady = resolve;
|
||||||
rejectReady = reject;
|
rejectReady = reject;
|
||||||
});
|
});
|
||||||
gProcesses[key] = process;
|
gProcesses[key] = process;
|
||||||
process.task.onExit = function (exitCode, terminationSignal) {
|
process.task.onExit = function (exitCode, terminationSignal) {
|
||||||
|
broadcastEvent('onSessionEnd', [getUser(process, process)]);
|
||||||
process.task = null;
|
process.task = null;
|
||||||
delete gProcesses[key];
|
delete gProcesses[key];
|
||||||
};
|
};
|
||||||
let imports = {
|
let imports = {
|
||||||
core: {
|
core: {
|
||||||
broadcast: broadcast.bind(process),
|
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),
|
user: getUser(process, process),
|
||||||
permissionTest: async function (permission, description) {
|
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 user = process?.credentials?.session?.name;
|
||||||
let permissions = await imports.core.permissionsGranted();
|
let settings = await loadSettings();
|
||||||
if (permissions && permissions[permission] !== undefined) {
|
if (
|
||||||
if (permissions[permission]) {
|
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();
|
||||||
|
if (
|
||||||
|
user &&
|
||||||
|
options?.packageOwner &&
|
||||||
|
options?.packageName &&
|
||||||
|
settings.userPermissions &&
|
||||||
|
settings.userPermissions[user]
|
||||||
|
) {
|
||||||
|
return settings.userPermissions[user];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
permissionsForUser: async function (user) {
|
||||||
|
let settings = await loadSettings();
|
||||||
|
return settings?.permissions?.[user] ?? [];
|
||||||
|
},
|
||||||
|
apps: (user) => getApps(user, process),
|
||||||
|
getSockets: getSockets,
|
||||||
|
permissionTest: async function (permission) {
|
||||||
|
let user = process?.credentials?.session?.name;
|
||||||
|
let settings = await loadSettings();
|
||||||
|
if (!user || !options?.packageOwner || !options?.packageName) {
|
||||||
|
return;
|
||||||
|
} else if (
|
||||||
|
settings.userPermissions &&
|
||||||
|
settings.userPermissions[user] &&
|
||||||
|
settings.userPermissions[user][options.packageOwner] &&
|
||||||
|
settings.userPermissions[user][options.packageOwner][
|
||||||
|
options.packageName
|
||||||
|
] &&
|
||||||
|
settings.userPermissions[user][options.packageOwner][
|
||||||
|
options.packageName
|
||||||
|
][permission] !== undefined
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
settings.userPermissions[user][options.packageOwner][
|
||||||
|
options.packageName
|
||||||
|
][permission]
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
throw Error(`Permission denied: ${permission}.`);
|
throw Error(`Permission denied: ${permission}.`);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (process.app) {
|
||||||
return process
|
return process.app
|
||||||
.makeFunction(['requestPermission'])(permission, description)
|
.makeFunction(['requestPermission'])(permission)
|
||||||
.then(async function (value) {
|
.then(async function (value) {
|
||||||
if (value == 'allow') {
|
if (value == 'allow') {
|
||||||
await ssb.setUserPermission(
|
await ssb.setUserPermission(
|
||||||
@@ -269,28 +331,30 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
}
|
}
|
||||||
throw Error(`Permission denied: ${permission}.`);
|
throw Error(`Permission denied: ${permission}.`);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
throw Error(`Permission denied: ${permission}.`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
app: {
|
||||||
|
owner: options?.packageOwner,
|
||||||
|
name: options?.packageName,
|
||||||
|
},
|
||||||
|
url: options?.url,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
process.sendIdentities = async function () {
|
process.sendIdentities = async function () {
|
||||||
let identities = await ssb_internal.getIdentityInfo(
|
process.app.send(
|
||||||
process?.credentials?.session?.name,
|
Object.assign(
|
||||||
options?.packageOwner,
|
{
|
||||||
options?.packageName
|
action: 'identities',
|
||||||
);
|
},
|
||||||
let json = JSON.stringify(identities);
|
await ssb.getIdentityInfo(
|
||||||
if (process._last_sent_identities !== json) {
|
process?.credentials?.session?.name,
|
||||||
process.send(
|
options?.packageOwner,
|
||||||
Object.assign(
|
options?.packageName
|
||||||
{
|
|
||||||
action: 'identities',
|
|
||||||
},
|
|
||||||
identities
|
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
process._last_sent_identities = json;
|
);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
process.setActiveIdentity = async function (identity) {
|
process.setActiveIdentity = async function (identity) {
|
||||||
if (
|
if (
|
||||||
@@ -327,7 +391,7 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
options.packageName,
|
options.packageName,
|
||||||
'setActiveIdentity',
|
'setActiveIdentity',
|
||||||
[
|
[
|
||||||
await imports.ssb.getActiveIdentity(
|
await ssb.getActiveIdentity(
|
||||||
process.credentials?.session?.name,
|
process.credentials?.session?.name,
|
||||||
options.packageOwner,
|
options.packageOwner,
|
||||||
options.packageName
|
options.packageName
|
||||||
@@ -339,11 +403,49 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
throw new Error('Must be signed-in to create an account.');
|
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) {
|
if (options.api) {
|
||||||
imports.app = {};
|
imports.app = {};
|
||||||
for (let i in options.api) {
|
for (let i in options.api) {
|
||||||
let api = options.api[i];
|
let api = options.api[i];
|
||||||
imports.app[api[0]] = process.makeFunction(api);
|
imports.app[api[0]] = process.app.makeFunction(api);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let [name, f] of Object.entries(options?.imports || {})) {
|
for (let [name, f] of Object.entries(options?.imports || {})) {
|
||||||
@@ -354,12 +456,145 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
imports.app.print(...args);
|
imports.app.print(...args);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
process.task.onError = process.makeFunction(['error']);
|
process.task.onError = function (error) {
|
||||||
|
try {
|
||||||
|
if (process.app) {
|
||||||
|
process.app.makeFunction(['error'])(error);
|
||||||
|
} else {
|
||||||
|
printError({print: print}, error);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
printError({print: print}, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
imports.ssb = Object.fromEntries(
|
imports.ssb = Object.fromEntries(
|
||||||
Object.keys(ssb).map((key) => [key, ssb[key].bind(ssb)])
|
Object.keys(ssb).map((key) => [key, ssb[key].bind(ssb)])
|
||||||
);
|
);
|
||||||
imports.ssb.createIdentity = () => process.createIdentity();
|
imports.ssb.createIdentity = () => process.createIdentity();
|
||||||
|
imports.ssb.addIdentity = function (id) {
|
||||||
|
if (
|
||||||
|
process.credentials &&
|
||||||
|
process.credentials.session &&
|
||||||
|
process.credentials.session.name
|
||||||
|
) {
|
||||||
|
return Promise.resolve(
|
||||||
|
imports.core.permissionTest('ssb_id_add')
|
||||||
|
).then(function () {
|
||||||
|
return ssb.addIdentity(process.credentials.session.name, id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
imports.ssb.deleteIdentity = function (id) {
|
||||||
|
if (
|
||||||
|
process.credentials &&
|
||||||
|
process.credentials.session &&
|
||||||
|
process.credentials.session.name
|
||||||
|
) {
|
||||||
|
return Promise.resolve(
|
||||||
|
imports.core.permissionTest('ssb_id_delete')
|
||||||
|
).then(function () {
|
||||||
|
return ssb.deleteIdentity(process.credentials.session.name, id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
imports.ssb.setActiveIdentity = (id) => process.setActiveIdentity(id);
|
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 &&
|
||||||
|
process.credentials.session &&
|
||||||
|
process.credentials.session.name
|
||||||
|
) {
|
||||||
|
return Promise.resolve(
|
||||||
|
imports.core.permissionTest('ssb_id_export')
|
||||||
|
).then(function () {
|
||||||
|
return ssb.getPrivateKey(process.credentials.session.name, id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
imports.ssb.appendMessageWithIdentity = function (id, message) {
|
||||||
|
if (
|
||||||
|
process.credentials &&
|
||||||
|
process.credentials.session &&
|
||||||
|
process.credentials.session.name
|
||||||
|
) {
|
||||||
|
return Promise.resolve(
|
||||||
|
imports.core.permissionTest('ssb_append')
|
||||||
|
).then(function () {
|
||||||
|
return ssb.appendMessageWithIdentity(
|
||||||
|
process.credentials.session.name,
|
||||||
|
id,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
imports.ssb.privateMessageEncrypt = function (id, recipients, message) {
|
||||||
|
if (
|
||||||
|
process.credentials &&
|
||||||
|
process.credentials.session &&
|
||||||
|
process.credentials.session.name
|
||||||
|
) {
|
||||||
|
return ssb.privateMessageEncrypt(
|
||||||
|
process.credentials.session.name,
|
||||||
|
id,
|
||||||
|
recipients,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
imports.ssb.privateMessageDecrypt = function (id, message) {
|
||||||
|
if (
|
||||||
|
process.credentials &&
|
||||||
|
process.credentials.session &&
|
||||||
|
process.credentials.session.name
|
||||||
|
) {
|
||||||
|
return ssb.privateMessageDecrypt(
|
||||||
|
process.credentials.session.name,
|
||||||
|
id,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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 (
|
if (
|
||||||
process.credentials &&
|
process.credentials &&
|
||||||
process.credentials.session &&
|
process.credentials.session &&
|
||||||
@@ -409,58 +644,53 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
process.sendPermissions = async function sendPermissions() {
|
process.sendPermissions = async function sendPermissions() {
|
||||||
process.send({
|
process.app.send({
|
||||||
action: 'permissions',
|
action: 'permissions',
|
||||||
permissions: await imports.core.permissionsGranted(),
|
permissions: await imports.core.permissionsGranted(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
process.client_api = {
|
process.resetPermission = async function resetPermission(permission) {
|
||||||
createIdentity: function () {
|
let user = process?.credentials?.session?.name;
|
||||||
return process.createIdentity();
|
await ssb.setUserPermission(
|
||||||
},
|
user,
|
||||||
resetPermission: async function resetPermission(message) {
|
options?.packageOwner,
|
||||||
let user = process?.credentials?.session?.name;
|
options?.packageName,
|
||||||
await ssb.setUserPermission(
|
permission,
|
||||||
user,
|
undefined
|
||||||
options?.packageOwner,
|
);
|
||||||
options?.packageName,
|
return process.sendPermissions();
|
||||||
message.permission,
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
return process.sendPermissions();
|
|
||||||
},
|
|
||||||
setActiveIdentity: function setActiveIdentity(message) {
|
|
||||||
return process.setActiveIdentity(message.identity);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
ssb.registerImports(imports, process);
|
|
||||||
process.imports = imports;
|
|
||||||
process.task.setImports(imports);
|
process.task.setImports(imports);
|
||||||
process.task.activate();
|
process.task.activate();
|
||||||
let source = await ssb.blobGet(blobId);
|
let source = await ssb.blobGet(blobId);
|
||||||
let appSourceName = blobId;
|
let appSourceName = blobId;
|
||||||
let appSource = utf8Decode(source);
|
let appSource = utf8Decode(source);
|
||||||
let appObject = JSON.parse(appSource);
|
try {
|
||||||
if (appObject.type == 'tildefriends-app') {
|
let appObject = JSON.parse(appSource);
|
||||||
appSourceName = options?.script ?? 'app.js';
|
if (appObject.type == 'tildefriends-app') {
|
||||||
let id = appObject.files[appSourceName];
|
appSourceName = options?.script ?? 'app.js';
|
||||||
let blob = await ssb.blobGet(id);
|
let id = appObject.files[appSourceName];
|
||||||
appSource = utf8Decode(blob);
|
let blob = await ssb.blobGet(id);
|
||||||
await process.task.loadFile([
|
appSource = utf8Decode(blob);
|
||||||
'/tfrpc.js',
|
await process.task.loadFile([
|
||||||
await File.readFile('core/tfrpc.js'),
|
'/tfrpc.js',
|
||||||
]);
|
await File.readFile('core/tfrpc.js'),
|
||||||
await Promise.all(
|
]);
|
||||||
Object.keys(appObject.files).map(async function (f) {
|
await Promise.all(
|
||||||
await process.task.loadFile([
|
Object.keys(appObject.files).map(async function (f) {
|
||||||
f,
|
await process.task.loadFile([
|
||||||
await ssb.blobGet(appObject.files[f]),
|
f,
|
||||||
]);
|
await ssb.blobGet(appObject.files[f]),
|
||||||
})
|
]);
|
||||||
);
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
printError({print: print}, e);
|
||||||
}
|
}
|
||||||
if (process.send) {
|
broadcastEvent('onSessionBegin', [getUser(process, process)]);
|
||||||
process.send({action: 'ready', version: version()});
|
if (process.app) {
|
||||||
|
process.app.send({action: 'ready', version: version()});
|
||||||
await process.sendPermissions();
|
await process.sendPermissions();
|
||||||
}
|
}
|
||||||
await process.task.execute({name: appSourceName, source: appSource});
|
await process.task.execute({name: appSourceName, source: appSource});
|
||||||
@@ -470,62 +700,65 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
sendStats();
|
sendStats();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (process?.task?.onError) {
|
if (process.app) {
|
||||||
process.task.onError(error);
|
if (process?.task?.onError) {
|
||||||
}
|
process.task.onError(error);
|
||||||
if (rejectReady) {
|
} else {
|
||||||
rejectReady(error);
|
printError({print: print}, error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printError({print: print}, error);
|
||||||
}
|
}
|
||||||
|
rejectReady(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return process;
|
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]);
|
broadcastEvent('onMessage', [...arguments]);
|
||||||
|
|
||||||
if (!g_update_accounts_scheduled) {
|
|
||||||
setTimeout(updateAccounts, 1000);
|
|
||||||
g_update_accounts_scheduled = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ssb_internal.addEventListener('blob', function () {
|
ssb.addEventListener('broadcasts', function () {
|
||||||
broadcastEvent('onBlob', [...arguments]);
|
|
||||||
});
|
|
||||||
|
|
||||||
ssb_internal.addEventListener('broadcasts', function () {
|
|
||||||
broadcastEvent('onBroadcastsChanged', []);
|
broadcastEvent('onBroadcastsChanged', []);
|
||||||
});
|
});
|
||||||
|
|
||||||
ssb_internal.addEventListener('connections', function () {
|
ssb.addEventListener('connections', function () {
|
||||||
broadcastEvent('onConnectionsChanged', []);
|
broadcastEvent('onConnectionsChanged', []);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send periodic stats to all clients.
|
* TODOC
|
||||||
|
*/
|
||||||
|
async function loadSettings() {
|
||||||
|
let data = {};
|
||||||
|
try {
|
||||||
|
let settings = await new Database('core').get('settings');
|
||||||
|
if (settings) {
|
||||||
|
data = JSON.parse(settings);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
print('Settings not found in database:', error);
|
||||||
|
}
|
||||||
|
for (let [key, value] of Object.entries(defaultGlobalSettings())) {
|
||||||
|
if (data[key] === undefined) {
|
||||||
|
data[key] = value.default_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
*/
|
*/
|
||||||
function sendStats() {
|
function sendStats() {
|
||||||
let apps = Object.values(gProcesses).filter((process) => process.send);
|
let apps = Object.values(gProcesses)
|
||||||
|
.filter((process) => process.app)
|
||||||
|
.map((process) => process.app);
|
||||||
if (apps.length) {
|
if (apps.length) {
|
||||||
let stats = getStats();
|
let stats = getStats();
|
||||||
for (let process of apps) {
|
for (let app of apps) {
|
||||||
process.send({action: 'stats', stats: stats});
|
app.send({action: 'stats', stats: stats});
|
||||||
}
|
}
|
||||||
setTimeout(sendStats, 1000);
|
setTimeout(sendStats, 1000);
|
||||||
} else {
|
} else {
|
||||||
@@ -533,16 +766,8 @@ function sendStats() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
let g_handler_index = 0;
|
||||||
* Invoke an app's handler.js.
|
|
||||||
* @param response The response object.
|
|
||||||
* @param app_blob_id The app's blob identifier.
|
|
||||||
* @param path The request path.
|
|
||||||
* @param query The request query string.
|
|
||||||
* @param headers The request headers.
|
|
||||||
* @param package_owner The app's owner.
|
|
||||||
* @param package_name The app's name.
|
|
||||||
*/
|
|
||||||
exports.callAppHandler = async function callAppHandler(
|
exports.callAppHandler = async function callAppHandler(
|
||||||
response,
|
response,
|
||||||
app_blob_id,
|
app_blob_id,
|
||||||
@@ -608,4 +833,4 @@ exports.callAppHandler = async function callAppHandler(
|
|||||||
response.end(answer?.data);
|
response.end(answer?.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @} */
|
export {invoke, getProcessBlob};
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
<!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
Normal file
113
core/http.js
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* 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,10 +5,7 @@
|
|||||||
<link type="text/css" rel="stylesheet" href="/static/style.css" />
|
<link type="text/css" rel="stylesheet" href="/static/style.css" />
|
||||||
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
|
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
|
||||||
<link type="image/svg+xml" rel="icon" href="/static/tildefriends.svg" />
|
<link type="image/svg+xml" rel="icon" href="/static/tildefriends.svg" />
|
||||||
<meta
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1, maximum-scale=1"
|
|
||||||
/>
|
|
||||||
<meta
|
<meta
|
||||||
name="title"
|
name="title"
|
||||||
content="Tilde Friends - Make friends and apps from your web browser."
|
content="Tilde Friends - Make friends and apps from your web browser."
|
||||||
@@ -66,7 +63,7 @@
|
|||||||
class="vbox"
|
class="vbox"
|
||||||
style="flex: 0 1 100%; display: none; overflow: auto"
|
style="flex: 0 1 100%; display: none; overflow: auto"
|
||||||
>
|
>
|
||||||
<div class="w3-bar w3-blue">
|
<div class="navigation w3-bar" style="display: flex">
|
||||||
<button
|
<button
|
||||||
class="w3-bar-item w3-button w3-blue"
|
class="w3-bar-item w3-button w3-blue"
|
||||||
id="closeEditor"
|
id="closeEditor"
|
||||||
@@ -77,6 +74,16 @@
|
|||||||
>
|
>
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
class="w3-bar-item w3-button w3-blue"
|
||||||
|
id="save"
|
||||||
|
name="save"
|
||||||
|
accesskey="s"
|
||||||
|
onmouseover="set_access_key_title(event)"
|
||||||
|
data-tip="Save the app under the given path"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
class="w3-bar-item w3-button w3-blue"
|
class="w3-bar-item w3-button w3-blue"
|
||||||
id="icon"
|
id="icon"
|
||||||
@@ -127,6 +134,13 @@
|
|||||||
>
|
>
|
||||||
✨
|
✨
|
||||||
</button>
|
</button>
|
||||||
|
<input
|
||||||
|
class="w3-bar-item w3-input w3-border w3-blue"
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
style="flex: 1 1; min-width: 1em"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
class="w3-bar-item w3-button w3-blue"
|
class="w3-bar-item w3-button w3-blue"
|
||||||
id="delete"
|
id="delete"
|
||||||
@@ -146,23 +160,6 @@
|
|||||||
>
|
>
|
||||||
Trace
|
Trace
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
class="w3-bar-item w3-button w3-blue w3-right"
|
|
||||||
id="save"
|
|
||||||
name="save"
|
|
||||||
accesskey="s"
|
|
||||||
onmouseover="set_access_key_title(event)"
|
|
||||||
data-tip="Save the app under the given path"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
<input
|
|
||||||
class="w3-bar-item w3-input w3-cobalt w3-right"
|
|
||||||
type="text"
|
|
||||||
id="name"
|
|
||||||
name="name"
|
|
||||||
style="min-width: 1em"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="hbox" style="flex: 1 1; overflow: auto">
|
<div class="hbox" style="flex: 1 1; overflow: auto">
|
||||||
<div style="overflow: auto">
|
<div style="overflow: auto">
|
||||||
@@ -178,7 +175,7 @@
|
|||||||
<iframe
|
<iframe
|
||||||
id="document"
|
id="document"
|
||||||
sandbox="allow-forms allow-scripts allow-top-navigation allow-modals allow-popups allow-downloads"
|
sandbox="allow-forms allow-scripts allow-top-navigation allow-modals allow-popups allow-downloads"
|
||||||
allow="clipboard-write"
|
style="width: 100%; height: 100%; border: 0"
|
||||||
></iframe>
|
></iframe>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -152,11 +152,4 @@ body {
|
|||||||
border-bottom: 4px solid #fff;
|
border-bottom: 4px solid #fff;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#document {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: 0;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,11 @@
|
|||||||
/**
|
|
||||||
* \file
|
|
||||||
* \defgroup tfrpc Tilde Friends RPC.
|
|
||||||
* Tilde Friends RPC.
|
|
||||||
* @{
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** Whether this module is being run in a web browser. */
|
|
||||||
const k_is_browser = get_is_browser();
|
const k_is_browser = get_is_browser();
|
||||||
/** Registered methods. */
|
|
||||||
let g_api = {};
|
let g_api = {};
|
||||||
/** The next method identifier. */
|
|
||||||
let g_next_id = 1;
|
let g_next_id = 1;
|
||||||
/** Identifiers of pending calls. */
|
|
||||||
let g_calls = {};
|
let g_calls = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if being called from a browser vs. server-side.
|
* TODOC
|
||||||
* @return true if called from a browser.
|
* @returns
|
||||||
*/
|
*/
|
||||||
function get_is_browser() {
|
function get_is_browser() {
|
||||||
try {
|
try {
|
||||||
@@ -26,30 +15,16 @@ function get_is_browser() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** \cond */
|
|
||||||
if (k_is_browser) {
|
if (k_is_browser) {
|
||||||
print = console.log;
|
print = console.log;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (k_is_browser) {
|
|
||||||
window.addEventListener('message', function (event) {
|
|
||||||
call_rpc(event.data);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
core.register('message', function (message) {
|
|
||||||
call_rpc(message?.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export let rpc = new Proxy({}, {get: make_rpc});
|
|
||||||
/** \endcond */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a function to invoke a remote procedure.
|
* TODOC
|
||||||
* @param target The target.
|
* @param {*} target
|
||||||
* @param prop The name of the function.
|
* @param {*} prop
|
||||||
* @param receiver The receiver.
|
* @param {*} receiver
|
||||||
* @return A function.
|
* @returns
|
||||||
*/
|
*/
|
||||||
function make_rpc(target, prop, receiver) {
|
function make_rpc(target, prop, receiver) {
|
||||||
return function () {
|
return function () {
|
||||||
@@ -80,8 +55,8 @@ function make_rpc(target, prop, receiver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a response.
|
* TODOC
|
||||||
* @param response The response.
|
* @param {*} response
|
||||||
*/
|
*/
|
||||||
function send(response) {
|
function send(response) {
|
||||||
if (k_is_browser) {
|
if (k_is_browser) {
|
||||||
@@ -92,8 +67,8 @@ function send(response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoke a remote procedure.
|
* TODOC
|
||||||
* @param message An object describing the call.
|
* @param {*} message
|
||||||
*/
|
*/
|
||||||
function call_rpc(message) {
|
function call_rpc(message) {
|
||||||
if (message && message.message === 'tfrpc') {
|
if (message && message.message === 'tfrpc') {
|
||||||
@@ -137,12 +112,22 @@ function call_rpc(message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (k_is_browser) {
|
||||||
|
window.addEventListener('message', function (event) {
|
||||||
|
call_rpc(event.data);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
core.register('message', function (message) {
|
||||||
|
call_rpc(message?.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export let rpc = new Proxy({}, {get: make_rpc});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a function that to be called remotely.
|
* TODOC
|
||||||
* @param method The method.
|
* @param {*} method
|
||||||
*/
|
*/
|
||||||
export function register(method) {
|
export function register(method) {
|
||||||
g_api[method.name] = method;
|
g_api[method.name] = method;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @} */
|
|
||||||
|
|||||||
18
core/w3.css
18
core/w3.css
@@ -1,4 +1,4 @@
|
|||||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||||
@@ -108,8 +108,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
|
||||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
|
||||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||||
@@ -150,7 +148,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||||
.w3-hover-none:hover{box-shadow:none!important}
|
.w3-hover-none:hover{box-shadow:none!important}
|
||||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
|
||||||
/* Colors */
|
/* Colors */
|
||||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||||
@@ -178,19 +175,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
|||||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
|
||||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
|
||||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
|
||||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
|
||||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
|
||||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
|
||||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
|
||||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
|
||||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
|
||||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
|
||||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
|
||||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
|
||||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
|
||||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||||
|
|||||||
@@ -25,14 +25,14 @@
|
|||||||
}:
|
}:
|
||||||
pkgs.stdenv.mkDerivation rec {
|
pkgs.stdenv.mkDerivation rec {
|
||||||
pname = "tildefriends";
|
pname = "tildefriends";
|
||||||
version = "0.2025.11";
|
version = "0.0.28";
|
||||||
|
|
||||||
src = pkgs.fetchFromGitea {
|
src = pkgs.fetchFromGitea {
|
||||||
domain = "dev.tildefriends.net";
|
domain = "dev.tildefriends.net";
|
||||||
owner = "cory";
|
owner = "cory";
|
||||||
repo = "tildefriends";
|
repo = "tildefriends";
|
||||||
rev = "v${version}";
|
rev = "f02423d0846fefd5ab21fa4542fb77ce5714547c";
|
||||||
hash = "sha256-z4v4ghKOBTMv+agTUKg+HU8zfE4imluXFsozQCT4qX8=";
|
hash = "sha256-QyM7wmViXJc4r8uTu4oE/HO3Z9tzNbFIX2+AOTQz9ZY=";
|
||||||
fetchSubmodules = true;
|
fetchSubmodules = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user