Compare commits
15 Commits
latest_rel
...
3bd827a9f7
| Author | SHA1 | Date | |
|---|---|---|---|
| 3bd827a9f7 | |||
| 474e39c9c3 | |||
| 0272382e0e | |||
| b1c8b51377 | |||
| 1a5acca5cf | |||
| 2d5417f7dc | |||
| 2a10d26215 | |||
| b8e5caba0d | |||
| a4b324127a | |||
| acae3e9562 | |||
| 4aa7424977 | |||
| 758f177617 | |||
| 9291de41d8 | |||
| 3603ce5ba6 | |||
| bff231751e |
@@ -14,7 +14,7 @@ IndentWidth: 4
|
|||||||
MaxEmptyLinesToKeep: 1
|
MaxEmptyLinesToKeep: 1
|
||||||
ObjCBlockIndentWidth: 4
|
ObjCBlockIndentWidth: 4
|
||||||
ObjCBreakBeforeNestedBlockParam: false
|
ObjCBreakBeforeNestedBlockParam: false
|
||||||
SortIncludes: true
|
SortIncludes: false
|
||||||
TabWidth: 4
|
TabWidth: 4
|
||||||
UseTab: Always
|
UseTab: Always
|
||||||
...
|
...
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
.git
|
.svn
|
||||||
db.sqlite*
|
db.sqlite
|
||||||
out/
|
out/**/*.o
|
||||||
|
out/**/*.d
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
name: Build Tilde Friends
|
|
||||||
run-name: ${{ gitea.actor }} running 🚀
|
|
||||||
on: [push]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Build-All:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: node:23-bookworm-slim
|
|
||||||
valid_volumes:
|
|
||||||
- '/opt/keys'
|
|
||||||
- '/opt/deps'
|
|
||||||
volumes:
|
|
||||||
- /opt/keys:/opt/keys
|
|
||||||
- /opt/deps:/opt/deps
|
|
||||||
steps:
|
|
||||||
- name: Install build dependencies
|
|
||||||
run: >
|
|
||||||
apt update && apt install -y \
|
|
||||||
build-essential \
|
|
||||||
clang-19 \
|
|
||||||
cmake \
|
|
||||||
curl \
|
|
||||||
docker.io \
|
|
||||||
doxygen \
|
|
||||||
file \
|
|
||||||
gcc-aarch64-linux-gnu \
|
|
||||||
git \
|
|
||||||
graphviz \
|
|
||||||
libgpgme11 \
|
|
||||||
libssl-dev \
|
|
||||||
mingw-w64 \
|
|
||||||
rsync \
|
|
||||||
unzip \
|
|
||||||
zip \
|
|
||||||
zlib1g-dev
|
|
||||||
- name: Get code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Setup environment
|
|
||||||
run: |
|
|
||||||
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-19 100
|
|
||||||
update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-19 100
|
|
||||||
ln -s /opt/keys .keys
|
|
||||||
ln -sf /opt/deps/ios_toolchain deps/
|
|
||||||
ln -sf /opt/deps/macos_toolchain deps/
|
|
||||||
- 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/
|
|
||||||
- name: Setup JDK
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: '17'
|
|
||||||
distribution: 'temurin'
|
|
||||||
- name: Setup Android SDK
|
|
||||||
uses: android-actions/setup-android@v3
|
|
||||||
with:
|
|
||||||
packages: 'tools platform-tools build-tools;35.0.0 platforms;android-35 ndk;27.2.12479018'
|
|
||||||
- name: Docker build
|
|
||||||
run: DOCKER_BUILDKIT=1 docker build .
|
|
||||||
- name: Build
|
|
||||||
run: ANDROID_SDK=$HOME/.android/sdk make -j`nproc` all dist
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
path: dist/*
|
|
||||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -1,20 +1,13 @@
|
|||||||
build/
|
|
||||||
*.core
|
|
||||||
db.*
|
db.*
|
||||||
deps/ios_toolchain
|
deps/ios_toolchain/
|
||||||
deps/macos_toolchain
|
|
||||||
deps/openssl/
|
deps/openssl/
|
||||||
dist/
|
dist/
|
||||||
.flatpak-builder
|
|
||||||
.keys
|
.keys
|
||||||
**/.DS_Store
|
|
||||||
logs/
|
|
||||||
**/node_modules
|
**/node_modules
|
||||||
out
|
out
|
||||||
repo/
|
|
||||||
result
|
|
||||||
*.swo
|
*.swo
|
||||||
*.swp
|
*.swp
|
||||||
tmp/
|
|
||||||
unsigned/
|
|
||||||
.zsign_cache/
|
.zsign_cache/
|
||||||
|
|
||||||
|
deps/codemirror/cm6.js
|
||||||
|
deps/prettier/standalone.mjs
|
||||||
7
.gitmodules
vendored
7
.gitmodules
vendored
@@ -1,6 +1,7 @@
|
|||||||
[submodule "deps/zlib"]
|
[submodule "deps/zlib"]
|
||||||
path = deps/zlib
|
path = deps/zlib
|
||||||
url = https://github.com/madler/zlib.git
|
url = https://github.com/madler/zlib.git
|
||||||
|
branch = master
|
||||||
[submodule "deps/libsodium"]
|
[submodule "deps/libsodium"]
|
||||||
path = deps/libsodium
|
path = deps/libsodium
|
||||||
url = https://github.com/jedisct1/libsodium.git
|
url = https://github.com/jedisct1/libsodium.git
|
||||||
@@ -19,9 +20,3 @@
|
|||||||
[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/c-ares"]
|
|
||||||
path = deps/c-ares
|
|
||||||
url = https://github.com/c-ares/c-ares.git
|
|
||||||
[submodule "deps/zsign"]
|
|
||||||
path = deps/zsign
|
|
||||||
url = https://github.com/zhlynn/zsign.git
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ node_modules
|
|||||||
src
|
src
|
||||||
deps
|
deps
|
||||||
.clang-format
|
.clang-format
|
||||||
flake.lock
|
|
||||||
apps/trace/speedscope/**
|
|
||||||
|
|
||||||
# Minified files
|
# Minified files
|
||||||
**/*.min.css
|
**/*.min.css
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
FROM bitnami/minideb:bookworm AS build
|
FROM bitnami/minideb:bullseye AS build
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
gcc \
|
gcc \
|
||||||
libc6-dev \
|
libc6-dev \
|
||||||
|
libssl-dev \
|
||||||
make
|
make
|
||||||
|
|
||||||
COPY . /app
|
COPY . /app
|
||||||
RUN make -C /app -j $(nproc) release
|
RUN make -C /app -j $(nproc) release
|
||||||
|
|
||||||
FROM bitnami/minideb:bookworm
|
FROM bitnami/minideb:bullseye
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
libssl1.1
|
||||||
|
|
||||||
COPY --from=build /app/out/release/tildefriends /app/out/release/tildefriends
|
COPY --from=build /app/out/release/tildefriends /app/out/release/tildefriends
|
||||||
COPY --from=build /app/apps /app/apps
|
COPY --from=build /app/apps /app/apps
|
||||||
|
|||||||
379
Doxyfile
379
Doxyfile
@@ -1,4 +1,4 @@
|
|||||||
# Doxyfile 1.9.4
|
# Doxyfile 1.9.1
|
||||||
|
|
||||||
# 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.
|
||||||
@@ -12,15 +12,6 @@
|
|||||||
# For lists, items can also be appended using:
|
# For lists, items can also be appended using:
|
||||||
# TAG += value [value, ...]
|
# TAG += value [value, ...]
|
||||||
# Values that contain spaces should be placed between quotes (\" \").
|
# Values that contain spaces should be placed between quotes (\" \").
|
||||||
#
|
|
||||||
# Note:
|
|
||||||
#
|
|
||||||
# Use doxygen to compare the used configuration file with the template
|
|
||||||
# configuration file:
|
|
||||||
# doxygen -x [configFile]
|
|
||||||
# Use doxygen to compare the used configuration file with the template
|
|
||||||
# configuration file without replacing the environment variables:
|
|
||||||
# doxygen -x_noenv [configFile]
|
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
# Project related configuration options
|
# Project related configuration options
|
||||||
@@ -69,28 +60,16 @@ PROJECT_LOGO =
|
|||||||
|
|
||||||
OUTPUT_DIRECTORY =
|
OUTPUT_DIRECTORY =
|
||||||
|
|
||||||
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
|
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
|
||||||
# sub-directories (in 2 levels) under the output directory of each output format
|
# directories (in 2 levels) under the output directory of each output format and
|
||||||
# and will distribute the generated files over these directories. Enabling this
|
# will distribute the generated files over these directories. Enabling this
|
||||||
# option can be useful when feeding doxygen a huge amount of source files, where
|
# option can be useful when feeding doxygen a huge amount of source files, where
|
||||||
# putting all generated files in the same directory would otherwise causes
|
# putting all generated files in the same directory would otherwise causes
|
||||||
# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
|
# performance problems for the file system.
|
||||||
# control the number of sub-directories.
|
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
CREATE_SUBDIRS = NO
|
CREATE_SUBDIRS = NO
|
||||||
|
|
||||||
# Controls the number of sub-directories that will be created when
|
|
||||||
# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every
|
|
||||||
# level increment doubles the number of directories, resulting in 4096
|
|
||||||
# directories at level 8 which is the default and also the maximum value. The
|
|
||||||
# sub-directories are organized in 2 levels, the first level always has a fixed
|
|
||||||
# numer of 16 directories.
|
|
||||||
# Minimum value: 0, maximum value: 8, default value: 8.
|
|
||||||
# This tag requires that the tag CREATE_SUBDIRS is set to YES.
|
|
||||||
|
|
||||||
CREATE_SUBDIRS_LEVEL = 8
|
|
||||||
|
|
||||||
# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
|
# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
|
||||||
# characters to appear in the names of generated files. If set to NO, non-ASCII
|
# characters to appear in the names of generated files. If set to NO, non-ASCII
|
||||||
# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
|
# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
|
||||||
@@ -102,18 +81,26 @@ ALLOW_UNICODE_NAMES = NO
|
|||||||
# The OUTPUT_LANGUAGE tag is used to specify the language in which all
|
# The OUTPUT_LANGUAGE tag is used to specify the language in which all
|
||||||
# documentation generated by doxygen is written. Doxygen will use this
|
# documentation generated by doxygen is written. Doxygen will use this
|
||||||
# information to generate all constant output in the proper language.
|
# information to generate all constant output in the proper language.
|
||||||
# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian,
|
# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
|
||||||
# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English
|
# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
|
||||||
# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek,
|
# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
|
||||||
# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with
|
# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
|
||||||
# English messages), Korean, Korean-en (Korean with English messages), Latvian,
|
# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
|
||||||
# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese,
|
# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
|
||||||
# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish,
|
# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
|
||||||
# Swedish, Turkish, Ukrainian and Vietnamese.
|
# Ukrainian and Vietnamese.
|
||||||
# The default value is: English.
|
# The default value is: English.
|
||||||
|
|
||||||
OUTPUT_LANGUAGE = English
|
OUTPUT_LANGUAGE = English
|
||||||
|
|
||||||
|
# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
|
||||||
|
# documentation generated by doxygen is written. Doxygen will use this
|
||||||
|
# information to generate all generated output in the proper direction.
|
||||||
|
# Possible values are: None, LTR, RTL and Context.
|
||||||
|
# The default value is: None.
|
||||||
|
|
||||||
|
OUTPUT_TEXT_DIRECTION = None
|
||||||
|
|
||||||
# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
|
# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
|
||||||
# descriptions after the members that are listed in the file and class
|
# descriptions after the members that are listed in the file and class
|
||||||
# documentation (similar to Javadoc). Set to NO to disable this.
|
# documentation (similar to Javadoc). Set to NO to disable this.
|
||||||
@@ -271,16 +258,16 @@ TAB_SIZE = 4
|
|||||||
# the documentation. An alias has the form:
|
# the documentation. An alias has the form:
|
||||||
# name=value
|
# name=value
|
||||||
# For example adding
|
# For example adding
|
||||||
# "sideeffect=@par Side Effects:^^"
|
# "sideeffect=@par Side Effects:\n"
|
||||||
# will allow you to put the command \sideeffect (or @sideeffect) in the
|
# will allow you to put the command \sideeffect (or @sideeffect) in the
|
||||||
# documentation, which will result in a user-defined paragraph with heading
|
# documentation, which will result in a user-defined paragraph with heading
|
||||||
# "Side Effects:". Note that you cannot put \n's in the value part of an alias
|
# "Side Effects:". You can put \n's in the value part of an alias to insert
|
||||||
# to insert newlines (in the resulting output). You can put ^^ in the value part
|
# newlines (in the resulting output). You can put ^^ in the value part of an
|
||||||
# of an alias to insert a newline as if a physical newline was in the original
|
# alias to insert a newline as if a physical newline was in the original file.
|
||||||
# file. When you need a literal { or } or , in the value part of an alias you
|
# When you need a literal { or } or , in the value part of an alias you have to
|
||||||
# have to escape them by means of a backslash (\), this can lead to conflicts
|
# escape them by means of a backslash (\), this can lead to conflicts with the
|
||||||
# with the commands \{ and \} for these it is advised to use the version @{ and
|
# commands \{ and \} for these it is advised to use the version @{ and @} or use
|
||||||
# @} or use a double escape (\\{ and \\})
|
# a double escape (\\{ and \\})
|
||||||
|
|
||||||
ALIASES =
|
ALIASES =
|
||||||
|
|
||||||
@@ -325,8 +312,8 @@ OPTIMIZE_OUTPUT_SLICE = NO
|
|||||||
# extension. Doxygen has a built-in mapping, but you can override or extend it
|
# extension. Doxygen has a built-in mapping, but you can override or extend it
|
||||||
# using this tag. The format is ext=language, where ext is a file extension, and
|
# using this tag. The format is ext=language, where ext is a file extension, and
|
||||||
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
|
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
|
||||||
# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
|
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
|
||||||
# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
|
# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
|
||||||
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
|
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
|
||||||
# tries to guess whether the code is fixed or free formatted code, this is the
|
# tries to guess whether the code is fixed or free formatted code, this is the
|
||||||
# default for Fortran type files). For instance to make doxygen treat .inc files
|
# default for Fortran type files). For instance to make doxygen treat .inc files
|
||||||
@@ -341,7 +328,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
|
||||||
@@ -473,13 +460,13 @@ TYPEDEF_HIDES_STRUCT = NO
|
|||||||
|
|
||||||
LOOKUP_CACHE_SIZE = 0
|
LOOKUP_CACHE_SIZE = 0
|
||||||
|
|
||||||
# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use
|
# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use
|
||||||
# during processing. When set to 0 doxygen will based this on the number of
|
# during processing. When set to 0 doxygen will based this on the number of
|
||||||
# cores available in the system. You can set it explicitly to a value larger
|
# cores available in the system. You can set it explicitly to a value larger
|
||||||
# than 0 to get more control over the balance between CPU load and processing
|
# than 0 to get more control over the balance between CPU load and processing
|
||||||
# speed. At this moment only the input processing can be done using multiple
|
# speed. At this moment only the input processing can be done using multiple
|
||||||
# threads. Since this is still an experimental feature the default is set to 1,
|
# threads. Since this is still an experimental feature the default is set to 1,
|
||||||
# which effectively disables parallel processing. Please report any issues you
|
# which efficively disables parallel processing. Please report any issues you
|
||||||
# encounter. Generating dot graphs in parallel is controlled by the
|
# encounter. Generating dot graphs in parallel is controlled by the
|
||||||
# DOT_NUM_THREADS setting.
|
# DOT_NUM_THREADS setting.
|
||||||
# Minimum value: 0, maximum value: 32, default value: 1.
|
# Minimum value: 0, maximum value: 32, default value: 1.
|
||||||
@@ -598,7 +585,7 @@ INTERNAL_DOCS = NO
|
|||||||
# filesystem is case sensitive (i.e. it supports files in the same directory
|
# filesystem is case sensitive (i.e. it supports files in the same directory
|
||||||
# whose names only differ in casing), the option must be set to YES to properly
|
# whose names only differ in casing), the option must be set to YES to properly
|
||||||
# deal with such files in case they appear in the input. For filesystems that
|
# deal with such files in case they appear in the input. For filesystems that
|
||||||
# are not case sensitive the option should be set to NO to properly deal with
|
# are not case sensitive the option should be be set to NO to properly deal with
|
||||||
# output files written for symbols that only differ in casing, such as for two
|
# output files written for symbols that only differ in casing, such as for two
|
||||||
# classes, one named CLASS and the other named Class, and to also support
|
# classes, one named CLASS and the other named Class, and to also support
|
||||||
# references to files without having to specify the exact matching casing. On
|
# references to files without having to specify the exact matching casing. On
|
||||||
@@ -623,12 +610,6 @@ HIDE_SCOPE_NAMES = NO
|
|||||||
|
|
||||||
HIDE_COMPOUND_REFERENCE= NO
|
HIDE_COMPOUND_REFERENCE= NO
|
||||||
|
|
||||||
# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
|
|
||||||
# will show which file needs to be included to use the class.
|
|
||||||
# The default value is: YES.
|
|
||||||
|
|
||||||
SHOW_HEADERFILE = YES
|
|
||||||
|
|
||||||
# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
|
# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
|
||||||
# the files that are included by a file in the documentation of that file.
|
# the files that are included by a file in the documentation of that file.
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
@@ -786,8 +767,7 @@ FILE_VERSION_FILTER =
|
|||||||
# output files in an output format independent way. To create the layout file
|
# output files in an output format independent way. To create the layout file
|
||||||
# that represents doxygen's defaults, run doxygen with the -l option. You can
|
# that represents doxygen's defaults, run doxygen with the -l option. You can
|
||||||
# optionally specify a file name after the option, if omitted DoxygenLayout.xml
|
# optionally specify a file name after the option, if omitted DoxygenLayout.xml
|
||||||
# will be used as the name of the layout file. See also section "Changing the
|
# will be used as the name of the layout file.
|
||||||
# layout of pages" for information.
|
|
||||||
#
|
#
|
||||||
# Note that if you run doxygen from a directory containing a file called
|
# Note that if you run doxygen from a directory containing a file called
|
||||||
# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
|
# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
|
||||||
@@ -833,26 +813,18 @@ WARNINGS = YES
|
|||||||
WARN_IF_UNDOCUMENTED = YES
|
WARN_IF_UNDOCUMENTED = YES
|
||||||
|
|
||||||
# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
|
# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
|
||||||
# potential errors in the documentation, such as documenting some parameters in
|
# potential errors in the documentation, such as not documenting some parameters
|
||||||
# a documented function twice, or documenting parameters that don't exist or
|
# in a documented function, or documenting parameters that don't exist or using
|
||||||
# using markup commands wrongly.
|
# markup commands wrongly.
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
|
|
||||||
WARN_IF_DOC_ERROR = YES
|
WARN_IF_DOC_ERROR = YES
|
||||||
|
|
||||||
# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
|
|
||||||
# function parameter documentation. If set to NO, doxygen will accept that some
|
|
||||||
# parameters have no documentation without warning.
|
|
||||||
# The default value is: YES.
|
|
||||||
|
|
||||||
WARN_IF_INCOMPLETE_DOC = YES
|
|
||||||
|
|
||||||
# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
|
# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
|
||||||
# are documented, but have no documentation for their parameters or return
|
# are documented, but have no documentation for their parameters or return
|
||||||
# value. If set to NO, doxygen will only warn about wrong parameter
|
# value. If set to NO, doxygen will only warn about wrong or incomplete
|
||||||
# documentation, but not about the absence of documentation. If EXTRACT_ALL is
|
# parameter documentation, but not about the absence of documentation. If
|
||||||
# set to YES then this flag will automatically be disabled. See also
|
# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
|
||||||
# WARN_IF_INCOMPLETE_DOC
|
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
WARN_NO_PARAMDOC = NO
|
WARN_NO_PARAMDOC = NO
|
||||||
@@ -872,27 +844,13 @@ WARN_AS_ERROR = NO
|
|||||||
# and the warning text. Optionally the format may contain $version, which will
|
# and the warning text. Optionally the format may contain $version, which will
|
||||||
# be replaced by the version of the file (if it could be obtained via
|
# be replaced by the version of the file (if it could be obtained via
|
||||||
# FILE_VERSION_FILTER)
|
# FILE_VERSION_FILTER)
|
||||||
# See also: WARN_LINE_FORMAT
|
|
||||||
# The default value is: $file:$line: $text.
|
# The default value is: $file:$line: $text.
|
||||||
|
|
||||||
WARN_FORMAT = "$file:$line: $text"
|
WARN_FORMAT = "$file:$line: $text"
|
||||||
|
|
||||||
# In the $text part of the WARN_FORMAT command it is possible that a reference
|
|
||||||
# to a more specific place is given. To make it easier to jump to this place
|
|
||||||
# (outside of doxygen) the user can define a custom "cut" / "paste" string.
|
|
||||||
# Example:
|
|
||||||
# WARN_LINE_FORMAT = "'vi $file +$line'"
|
|
||||||
# See also: WARN_FORMAT
|
|
||||||
# The default value is: at line $line of file $file.
|
|
||||||
|
|
||||||
WARN_LINE_FORMAT = "at line $line of file $file"
|
|
||||||
|
|
||||||
# The WARN_LOGFILE tag can be used to specify a file to which warning and error
|
# The WARN_LOGFILE tag can be used to specify a file to which warning and error
|
||||||
# messages should be written. If left blank the output is written to standard
|
# messages should be written. If left blank the output is written to standard
|
||||||
# error (stderr). In case the file specified cannot be opened for writing the
|
# error (stderr).
|
||||||
# warning and error messages are written to standard error. When as file - is
|
|
||||||
# specified the warning and error messages are written to standard output
|
|
||||||
# (stdout).
|
|
||||||
|
|
||||||
WARN_LOGFILE =
|
WARN_LOGFILE =
|
||||||
|
|
||||||
@@ -906,13 +864,7 @@ 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 = src/
|
||||||
core/app.js \
|
|
||||||
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
|
||||||
@@ -936,14 +888,12 @@ INPUT_ENCODING = UTF-8
|
|||||||
#
|
#
|
||||||
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
|
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
|
||||||
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
|
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
|
||||||
# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
|
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
|
||||||
# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
|
# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
|
||||||
# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
|
# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl,
|
||||||
# *.vhdl, *.ucf, *.qsf and *.ice.
|
# *.ucf, *.qsf and *.ice.
|
||||||
|
|
||||||
FILE_PATTERNS = *.h \
|
FILE_PATTERNS = *.h *.md
|
||||||
*.js \
|
|
||||||
*.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
|
||||||
# be searched for input files as well.
|
# be searched for input files as well.
|
||||||
@@ -980,7 +930,7 @@ EXCLUDE_PATTERNS =
|
|||||||
# (namespaces, classes, functions, etc.) that should be excluded from the
|
# (namespaces, classes, functions, etc.) that should be excluded from the
|
||||||
# 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
|
# AClass::ANamespace, ANamespace::*Test
|
||||||
#
|
#
|
||||||
# Note that the wildcards are matched against the file with absolute path, so to
|
# Note that the wildcards are matched against the file with absolute path, so to
|
||||||
# exclude all test directories use the pattern */test/*
|
# exclude all test directories use the pattern */test/*
|
||||||
@@ -1011,7 +961,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
|
||||||
@@ -1067,7 +1017,7 @@ FILTER_SOURCE_PATTERNS =
|
|||||||
# (index.html). This can be useful if you have a project on for instance GitHub
|
# (index.html). This can be useful if you have a project on for instance GitHub
|
||||||
# and want to reuse the introduction page also for the doxygen output.
|
# and want to reuse the introduction page also for the doxygen output.
|
||||||
|
|
||||||
USE_MDFILE_AS_MAINPAGE = README.md
|
USE_MDFILE_AS_MAINPAGE =
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
# Configuration options related to source browsing
|
# Configuration options related to source browsing
|
||||||
@@ -1166,11 +1116,9 @@ VERBATIM_HEADERS = YES
|
|||||||
|
|
||||||
CLANG_ASSISTED_PARSING = NO
|
CLANG_ASSISTED_PARSING = NO
|
||||||
|
|
||||||
# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS
|
# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to
|
||||||
# tag is set to YES then doxygen will add the directory of each input to the
|
# YES then doxygen will add the directory of each input to the include path.
|
||||||
# include path.
|
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
|
|
||||||
|
|
||||||
CLANG_ADD_INC_PATHS = YES
|
CLANG_ADD_INC_PATHS = YES
|
||||||
|
|
||||||
@@ -1305,7 +1253,7 @@ HTML_EXTRA_FILES =
|
|||||||
|
|
||||||
# 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 colorwheel, see
|
||||||
# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
|
# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
|
||||||
# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
|
# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
|
||||||
# purple, and 360 is red again.
|
# purple, and 360 is red again.
|
||||||
@@ -1315,7 +1263,7 @@ HTML_EXTRA_FILES =
|
|||||||
HTML_COLORSTYLE_HUE = 220
|
HTML_COLORSTYLE_HUE = 220
|
||||||
|
|
||||||
# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
|
# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
|
||||||
# in the HTML output. For a value of 0 the output will use gray-scales only. A
|
# in the HTML output. For a value of 0 the output will use grayscales only. A
|
||||||
# value of 255 will produce the most vivid colors.
|
# value of 255 will produce the most vivid colors.
|
||||||
# Minimum value: 0, maximum value: 255, default value: 100.
|
# Minimum value: 0, maximum value: 255, default value: 100.
|
||||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||||
@@ -1340,7 +1288,7 @@ HTML_COLORSTYLE_GAMMA = 80
|
|||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
# 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_TIMESTAMP = NO
|
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
|
||||||
@@ -1397,13 +1345,6 @@ GENERATE_DOCSET = NO
|
|||||||
|
|
||||||
DOCSET_FEEDNAME = "Doxygen generated docs"
|
DOCSET_FEEDNAME = "Doxygen generated docs"
|
||||||
|
|
||||||
# This tag determines the URL of the docset feed. A documentation feed provides
|
|
||||||
# an umbrella under which multiple documentation sets from a single provider
|
|
||||||
# (such as a company or product suite) can be grouped.
|
|
||||||
# This tag requires that the tag GENERATE_DOCSET is set to YES.
|
|
||||||
|
|
||||||
DOCSET_FEEDURL =
|
|
||||||
|
|
||||||
# This tag specifies a string that should uniquely identify the documentation
|
# This tag specifies a string that should uniquely identify the documentation
|
||||||
# set bundle. This should be a reverse domain-name style string, e.g.
|
# set bundle. This should be a reverse domain-name style string, e.g.
|
||||||
# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
|
# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
|
||||||
@@ -1429,12 +1370,8 @@ DOCSET_PUBLISHER_NAME = Publisher
|
|||||||
# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
|
# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
|
||||||
# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
|
# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
|
||||||
# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
|
# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
|
||||||
# on Windows. In the beginning of 2021 Microsoft took the original page, with
|
# (see:
|
||||||
# a.o. the download links, offline the HTML help workshop was already many years
|
# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows.
|
||||||
# in maintenance mode). You can download the HTML help workshop from the web
|
|
||||||
# archives at Installation executable (see:
|
|
||||||
# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
|
|
||||||
# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
|
|
||||||
#
|
#
|
||||||
# The HTML Help Workshop contains a compiler that can convert all HTML output
|
# The HTML Help Workshop contains a compiler that can convert all HTML output
|
||||||
# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
|
# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
|
||||||
@@ -1593,27 +1530,15 @@ DISABLE_INDEX = NO
|
|||||||
# to work a browser that supports JavaScript, DHTML, CSS and frames is required
|
# to work a browser that supports JavaScript, DHTML, CSS and frames is required
|
||||||
# (i.e. any modern browser). Windows users are probably better off using the
|
# (i.e. any modern browser). Windows users are probably better off using the
|
||||||
# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
|
# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
|
||||||
# further fine tune the look of the index (see "Fine-tuning the output"). As an
|
# further fine-tune the look of the index. As an example, the default style
|
||||||
# example, the default style sheet generated by doxygen has an example that
|
# sheet generated by doxygen has an example that shows how to put an image at
|
||||||
# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
|
# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
|
||||||
# Since the tree basically has the same information as the tab index, you could
|
# the same information as the tab index, you could consider setting
|
||||||
# consider setting DISABLE_INDEX to YES when enabling this option.
|
# DISABLE_INDEX to YES when enabling this option.
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||||
|
|
||||||
GENERATE_TREEVIEW = YES
|
GENERATE_TREEVIEW = NO
|
||||||
|
|
||||||
# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
|
|
||||||
# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
|
|
||||||
# area (value NO) or if it should extend to the full height of the window (value
|
|
||||||
# YES). Setting this to YES gives a layout similar to
|
|
||||||
# https://docs.readthedocs.io with more room for contents, but less room for the
|
|
||||||
# project logo, title, and description. If either GENERATE_TREEVIEW or
|
|
||||||
# DISABLE_INDEX is set to NO, this option has no effect.
|
|
||||||
# The default value is: NO.
|
|
||||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
|
||||||
|
|
||||||
FULL_SIDEBAR = NO
|
|
||||||
|
|
||||||
# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
|
# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
|
||||||
# doxygen will group on one line in the generated HTML documentation.
|
# doxygen will group on one line in the generated HTML documentation.
|
||||||
@@ -1639,13 +1564,6 @@ TREEVIEW_WIDTH = 250
|
|||||||
|
|
||||||
EXT_LINKS_IN_WINDOW = NO
|
EXT_LINKS_IN_WINDOW = NO
|
||||||
|
|
||||||
# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email
|
|
||||||
# addresses.
|
|
||||||
# The default value is: YES.
|
|
||||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
|
||||||
|
|
||||||
OBFUSCATE_EMAILS = YES
|
|
||||||
|
|
||||||
# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
|
# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
|
||||||
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
|
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
|
||||||
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
|
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
|
||||||
@@ -1675,7 +1593,7 @@ FORMULA_FONTSIZE = 10
|
|||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||||
|
|
||||||
#FORMULA_TRANSPARENT = 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
|
||||||
@@ -1694,29 +1612,11 @@ FORMULA_MACROFILE =
|
|||||||
|
|
||||||
USE_MATHJAX = NO
|
USE_MATHJAX = NO
|
||||||
|
|
||||||
# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
|
|
||||||
# Note that the different versions of MathJax have different requirements with
|
|
||||||
# regards to the different settings, so it is possible that also other MathJax
|
|
||||||
# settings have to be changed when switching between the different MathJax
|
|
||||||
# versions.
|
|
||||||
# Possible values are: MathJax_2 and MathJax_3.
|
|
||||||
# The default value is: MathJax_2.
|
|
||||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
|
||||||
|
|
||||||
MATHJAX_VERSION = MathJax_2
|
|
||||||
|
|
||||||
# When MathJax is enabled you can set the default output format to be used for
|
# When MathJax is enabled you can set the default output format to be used for
|
||||||
# the MathJax output. For more details about the output format see MathJax
|
# the MathJax output. See the MathJax site (see:
|
||||||
# version 2 (see:
|
# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details.
|
||||||
# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
|
|
||||||
# (see:
|
|
||||||
# http://docs.mathjax.org/en/latest/web/components/output.html).
|
|
||||||
# Possible values are: HTML-CSS (which is slower, but has the best
|
# Possible values are: HTML-CSS (which is slower, but has the best
|
||||||
# compatibility. This is the name for Mathjax version 2, for MathJax version 3
|
# compatibility), NativeMML (i.e. MathML) and SVG.
|
||||||
# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
|
|
||||||
# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
|
|
||||||
# is the name for Mathjax version 3, for MathJax version 2 this will be
|
|
||||||
# translated into HTML-CSS) and SVG.
|
|
||||||
# The default value is: HTML-CSS.
|
# The default value is: HTML-CSS.
|
||||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||||
|
|
||||||
@@ -1729,21 +1629,15 @@ MATHJAX_FORMAT = HTML-CSS
|
|||||||
# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
|
# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
|
||||||
# Content Delivery Network so you can quickly see the result without installing
|
# Content Delivery Network so you can quickly see the result without installing
|
||||||
# MathJax. However, it is strongly recommended to install a local copy of
|
# MathJax. However, it is strongly recommended to install a local copy of
|
||||||
# MathJax from https://www.mathjax.org before deployment. The default value is:
|
# MathJax from https://www.mathjax.org before deployment.
|
||||||
# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
|
# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
|
||||||
# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
|
|
||||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||||
|
|
||||||
MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2
|
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
|
|
||||||
# #tex-and-latex-extensions):
|
|
||||||
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
|
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
|
||||||
# For example for MathJax version 3 (see
|
|
||||||
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
|
|
||||||
# MATHJAX_EXTENSIONS = ams
|
|
||||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||||
|
|
||||||
MATHJAX_EXTENSIONS =
|
MATHJAX_EXTENSIONS =
|
||||||
@@ -1923,31 +1817,29 @@ PAPER_TYPE = a4
|
|||||||
|
|
||||||
EXTRA_PACKAGES =
|
EXTRA_PACKAGES =
|
||||||
|
|
||||||
# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
|
# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
|
||||||
# the generated LaTeX document. The header should contain everything until the
|
# generated LaTeX document. The header should contain everything until the first
|
||||||
# first chapter. If it is left blank doxygen will generate a standard header. It
|
# chapter. If it is left blank doxygen will generate a standard header. See
|
||||||
# is highly recommended to start with a default header using
|
# section "Doxygen usage" for information on how to let doxygen write the
|
||||||
# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
|
# default header to a separate file.
|
||||||
# and then modify the file new_header.tex. See also section "Doxygen usage" for
|
|
||||||
# information on how to generate the default header that doxygen normally uses.
|
|
||||||
#
|
#
|
||||||
# Note: Only use a user-defined header if you know what you are doing!
|
# Note: Only use a user-defined header if you know what you are doing! The
|
||||||
# Note: The header is subject to change so you typically have to regenerate the
|
# following commands have a special meaning inside the header: $title,
|
||||||
# default header when upgrading to a newer version of doxygen. The following
|
# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
|
||||||
# commands have a special meaning inside the header (and footer): For a
|
# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
|
||||||
# description of the possible markers and block names see the documentation.
|
# string, for the replacement values of the other commands the user is referred
|
||||||
|
# to HTML_HEADER.
|
||||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||||
|
|
||||||
LATEX_HEADER =
|
LATEX_HEADER =
|
||||||
|
|
||||||
# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
|
# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
|
||||||
# the generated LaTeX document. The footer should contain everything after the
|
# generated LaTeX document. The footer should contain everything after the last
|
||||||
# last chapter. If it is left blank doxygen will generate a standard footer. See
|
# chapter. If it is left blank doxygen will generate a standard footer. See
|
||||||
# LATEX_HEADER for more information on how to generate a default footer and what
|
# LATEX_HEADER for more information on how to generate a default footer and what
|
||||||
# special commands can be used inside the footer. See also section "Doxygen
|
# special commands can be used inside the footer.
|
||||||
# usage" for information on how to generate the default footer that doxygen
|
#
|
||||||
# normally uses. Note: Only use a user-defined footer if you know what you are
|
# Note: Only use a user-defined footer if you know what you are doing!
|
||||||
# doing!
|
|
||||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||||
|
|
||||||
LATEX_FOOTER =
|
LATEX_FOOTER =
|
||||||
@@ -1992,7 +1884,8 @@ USE_PDFLATEX = YES
|
|||||||
|
|
||||||
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
|
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
|
||||||
# command to the generated LaTeX files. This will instruct LaTeX to keep running
|
# command to the generated LaTeX files. This will instruct LaTeX to keep running
|
||||||
# if errors occur, instead of asking the user for help.
|
# if errors occur, instead of asking the user for help. This option is also used
|
||||||
|
# when generating formulas in HTML.
|
||||||
# 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.
|
||||||
|
|
||||||
@@ -2005,6 +1898,16 @@ LATEX_BATCHMODE = NO
|
|||||||
|
|
||||||
LATEX_HIDE_INDICES = NO
|
LATEX_HIDE_INDICES = NO
|
||||||
|
|
||||||
|
# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
|
||||||
|
# code with syntax highlighting in the LaTeX output.
|
||||||
|
#
|
||||||
|
# Note that which sources are shown also depends on other settings such as
|
||||||
|
# SOURCE_BROWSER.
|
||||||
|
# The default value is: NO.
|
||||||
|
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||||
|
|
||||||
|
LATEX_SOURCE_CODE = NO
|
||||||
|
|
||||||
# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
|
# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
|
||||||
# bibliography, e.g. plainnat, or ieeetr. See
|
# bibliography, e.g. plainnat, or ieeetr. See
|
||||||
# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
|
# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
|
||||||
@@ -2019,7 +1922,7 @@ LATEX_BIB_STYLE = plain
|
|||||||
# 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.
|
||||||
|
|
||||||
#LATEX_TIMESTAMP = NO
|
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,
|
||||||
@@ -2085,6 +1988,16 @@ RTF_STYLESHEET_FILE =
|
|||||||
|
|
||||||
RTF_EXTENSIONS_FILE =
|
RTF_EXTENSIONS_FILE =
|
||||||
|
|
||||||
|
# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
|
||||||
|
# with syntax highlighting in the RTF output.
|
||||||
|
#
|
||||||
|
# Note that which sources are shown also depends on other settings such as
|
||||||
|
# SOURCE_BROWSER.
|
||||||
|
# The default value is: NO.
|
||||||
|
# This tag requires that the tag GENERATE_RTF is set to YES.
|
||||||
|
|
||||||
|
RTF_SOURCE_CODE = NO
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
# Configuration options related to the man page output
|
# Configuration options related to the man page output
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
@@ -2181,6 +2094,15 @@ GENERATE_DOCBOOK = NO
|
|||||||
|
|
||||||
DOCBOOK_OUTPUT = docbook
|
DOCBOOK_OUTPUT = docbook
|
||||||
|
|
||||||
|
# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
|
||||||
|
# program listings (including syntax highlighting and cross-referencing
|
||||||
|
# information) to the DOCBOOK output. Note that enabling this will significantly
|
||||||
|
# increase the size of the DOCBOOK output.
|
||||||
|
# The default value is: NO.
|
||||||
|
# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
|
||||||
|
|
||||||
|
DOCBOOK_PROGRAMLISTING = NO
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
# Configuration options for the AutoGen Definitions output
|
# Configuration options for the AutoGen Definitions output
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
@@ -2267,8 +2189,7 @@ SEARCH_INCLUDES = YES
|
|||||||
|
|
||||||
# The INCLUDE_PATH tag can be used to specify one or more directories that
|
# The INCLUDE_PATH tag can be used to specify one or more directories that
|
||||||
# contain include files that are not input files but should be processed by the
|
# contain include files that are not input files but should be processed by the
|
||||||
# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
|
# preprocessor.
|
||||||
# RECURSIVE has no effect here.
|
|
||||||
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
|
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
|
||||||
|
|
||||||
INCLUDE_PATH =
|
INCLUDE_PATH =
|
||||||
@@ -2360,6 +2281,15 @@ EXTERNAL_PAGES = YES
|
|||||||
# Configuration options related to the dot tool
|
# Configuration options related to the dot tool
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
|
||||||
|
# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
|
||||||
|
# NO turns the diagrams off. Note that this option also works with HAVE_DOT
|
||||||
|
# disabled, but it is recommended to install and use dot, since it yields more
|
||||||
|
# powerful graphs.
|
||||||
|
# The default value is: YES.
|
||||||
|
|
||||||
|
CLASS_DIAGRAMS = YES
|
||||||
|
|
||||||
# You can include diagrams made with dia in doxygen documentation. Doxygen will
|
# 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
|
# 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.
|
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
|
||||||
@@ -2378,7 +2308,7 @@ HIDE_UNDOC_RELATIONS = YES
|
|||||||
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
|
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
|
||||||
# Bell Labs. The other options in this section have no effect if this option is
|
# 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
|
||||||
|
|
||||||
@@ -2400,30 +2330,27 @@ DOT_NUM_THREADS = 0
|
|||||||
# The default value is: Helvetica.
|
# The default value is: Helvetica.
|
||||||
# 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_FONTNAME = Helvetica
|
||||||
|
|
||||||
# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
|
# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
|
||||||
# dot graphs.
|
# dot graphs.
|
||||||
# Minimum value: 4, maximum value: 24, default value: 10.
|
# Minimum value: 4, maximum value: 24, default value: 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_FONTSIZE = 10
|
||||||
|
|
||||||
# By default doxygen will tell dot to use the default font as specified with
|
# By default doxygen will tell dot to use the default font as specified with
|
||||||
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
|
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
|
||||||
# the path where dot can find it using this tag.
|
# the path where dot can find it using this tag.
|
||||||
# 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_FONTPATH =
|
||||||
|
|
||||||
# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
|
# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
|
||||||
# graph for each documented class showing the direct and indirect inheritance
|
# each documented class showing the direct and indirect inheritance relations.
|
||||||
# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
|
# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
|
||||||
# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
|
|
||||||
# to TEXT the direct and indirect inheritance relations will be shown as texts /
|
|
||||||
# links.
|
|
||||||
# Possible values are: NO, YES, TEXT and GRAPH.
|
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
CLASS_GRAPH = YES
|
CLASS_GRAPH = YES
|
||||||
|
|
||||||
@@ -2437,8 +2364,7 @@ CLASS_GRAPH = 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.
|
||||||
# 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.
|
||||||
|
|
||||||
@@ -2553,13 +2479,6 @@ GRAPHICAL_HIERARCHY = YES
|
|||||||
|
|
||||||
DIRECTORY_GRAPH = YES
|
DIRECTORY_GRAPH = YES
|
||||||
|
|
||||||
# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels
|
|
||||||
# of child directories generated in directory dependency graphs by dot.
|
|
||||||
# Minimum value: 1, maximum value: 25, default value: 1.
|
|
||||||
# This tag requires that the tag DIRECTORY_GRAPH is set to YES.
|
|
||||||
|
|
||||||
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:
|
||||||
@@ -2567,7 +2486,9 @@ DIR_GRAPH_MAX_DEPTH = 1
|
|||||||
# 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, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
|
||||||
|
# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
|
||||||
|
# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
|
||||||
# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
|
# 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.
|
||||||
@@ -2613,10 +2534,10 @@ MSCFILE_DIRS =
|
|||||||
DIAFILE_DIRS =
|
DIAFILE_DIRS =
|
||||||
|
|
||||||
# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
|
# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
|
||||||
# path where java can find the plantuml.jar file or to the filename of jar file
|
# path where java can find the plantuml.jar file. If left blank, it is assumed
|
||||||
# to be used. If left blank, it is assumed PlantUML is not used or called during
|
# PlantUML is not used or called during a preprocessing step. Doxygen will
|
||||||
# a preprocessing step. Doxygen will generate a warning when it encounters a
|
# generate a warning when it encounters a \startuml command in this case and
|
||||||
# \startuml command in this case and will not generate output for the diagram.
|
# will not generate output for the diagram.
|
||||||
|
|
||||||
PLANTUML_JAR_PATH =
|
PLANTUML_JAR_PATH =
|
||||||
|
|
||||||
@@ -2664,7 +2585,7 @@ MAX_DOT_GRAPH_DEPTH = 0
|
|||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
# 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_TRANSPARENT = NO
|
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
|
||||||
@@ -2678,8 +2599,6 @@ DOT_MULTI_TARGETS = NO
|
|||||||
# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
|
# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
|
||||||
# explaining the meaning of the various boxes and arrows in the dot generated
|
# explaining the meaning of the various boxes and arrows in the dot generated
|
||||||
# graphs.
|
# graphs.
|
||||||
# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal
|
|
||||||
# graphical representation for inheritance and collaboration diagrams is used.
|
|
||||||
# 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.
|
||||||
|
|
||||||
@@ -2688,8 +2607,8 @@ GENERATE_LEGEND = YES
|
|||||||
# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
|
# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
|
||||||
# files that are used to generate the various graphs.
|
# files that are used to generate the various graphs.
|
||||||
#
|
#
|
||||||
# Note: This setting is not only used for dot files but also for msc temporary
|
# Note: This setting is not only used for dot files but also for msc and
|
||||||
# files.
|
# plantuml temporary files.
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
|
|
||||||
DOT_CLEANUP = YES
|
DOT_CLEANUP = YES
|
||||||
|
|||||||
816
GNUmakefile
816
GNUmakefile
File diff suppressed because it is too large
Load Diff
69
README.md
69
README.md
@@ -1,68 +1,47 @@
|
|||||||
# Tilde Friends
|
# Tilde Friends
|
||||||
|
|
||||||
Tilde Friends participates in the Secure Scuttlebutt decentralized social
|
Tilde Friends is a tool for making and sharing.
|
||||||
network while also functioning as a platform for making, sharing, and running
|
|
||||||
web applications.
|
|
||||||
|
|
||||||
A public instance lives at https://www.tildefriends.net/.
|
A public instance lives at https://www.tildefriends.net/.
|
||||||
|
|
||||||
|
It is both a peer-to-peer social network client, participating in Secure
|
||||||
|
Scuttlebutt, as well as a platform for writing and running web applications.
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
1. Be the fanciest, best-maintained Secure Scuttlebutt client in town.
|
1. Make it easy and fun to run all sorts of web applications.
|
||||||
1. Make it easy to make, share, and run all sorts of applications while
|
2. Provide security that is easy to understand and protects your data.
|
||||||
respecting the privacy and safety of your data.
|
3. Make creating and sharing web applications accessible to anyone with a
|
||||||
|
browser.
|
||||||
## Getting the Source
|
|
||||||
|
|
||||||
Tilde Friends uses git submodules, so either:
|
|
||||||
|
|
||||||
```
|
|
||||||
git clone --recurse-submodules https://dev.tildefriends.net/cory/tildefriends.git
|
|
||||||
```
|
|
||||||
|
|
||||||
or:
|
|
||||||
|
|
||||||
```
|
|
||||||
git clone https://dev.tildefriends.net/cory/tildefriends.git
|
|
||||||
cd tildefriends
|
|
||||||
git submodule update --init --recursive
|
|
||||||
```
|
|
||||||
|
|
||||||
The `.tar.xz` source releases are all-inclusive.
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. It's possible
|
Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. Builds for
|
||||||
to build for Android, iOS, and Windows on Linux, if you have the right
|
all of those host platforms plus mingw64, iOS, and android.
|
||||||
dependencies in the right places.
|
|
||||||
|
|
||||||
### Requirements
|
1. Requires openssl (`libssl-dev`, in debian-speak). All other dependencies
|
||||||
|
are kept up to date in the tree.
|
||||||
On MacOS, Xcode's command-line tools are expected to be available.
|
2. To build, run `make debug` or `make release`. An executable will be
|
||||||
|
generated in a subdirectory of `out/`.
|
||||||
### Build Commands
|
3. It's possible to build for Android, iOS, and Windows on Linux, if you have
|
||||||
|
the right dependencies in the right places. `make windebug winrelease
|
||||||
Run `make` with no arguments to see available build targets and options. `make
|
iosdebug-ipa iosrelease-ipa release-apk`.
|
||||||
debug` is a good place to start.
|
4. To build in docker, `docker build .`.
|
||||||
|
5. `make format` will normalize formatting to the coding standard.
|
||||||
To build in docker, `docker build .`.
|
|
||||||
|
|
||||||
`make format` and `make prettier` will normalize formatting to the coding
|
|
||||||
standard.
|
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
By default, running the built `out/debug/tildefriends` executable will start a
|
By default, running the built `tildefriends` executable will start a web server
|
||||||
web server at <http://localhost:12345/>. `tildefriends -h` lists further
|
at <http://localhost:12345/>. `tildefriends -h` lists further options.
|
||||||
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 at
|
||||||
<http://localhost:12345/~core/admin/>.
|
<http://localhost:12345/~core/admin/>.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Docs live here: <https://docs.tildefriends.net/>.
|
Docs are a work in progress:
|
||||||
|
<https://www.tildefriends.net/~cory/wiki/#test-wiki/tf-app-quick-reference>.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🎛",
|
"emoji": "🎛"
|
||||||
"previous": "&kmKNyb/uaXNb24gCinJtfS8iWx4cLUWdtl0y2DwEUas=.sha256"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,38 +4,9 @@
|
|||||||
<script>
|
<script>
|
||||||
const g_data = $data;
|
const g_data = $data;
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="w3.css" />
|
|
||||||
<!-- prettier-ignore -->
|
|
||||||
<style>
|
|
||||||
/* 2018 Valiant Poppy */
|
|
||||||
.w3-theme-l5 {color:#000 !important; background-color:#fbf3f3 !important}
|
|
||||||
.w3-theme-l4 {color:#000 !important; background-color:#f3d7d6 !important}
|
|
||||||
.w3-theme-l3 {color:#000 !important; background-color:#e6afae !important}
|
|
||||||
.w3-theme-l2 {color:#fff !important; background-color:#da8785 !important}
|
|
||||||
.w3-theme-l1 {color:#fff !important; background-color:#cd5f5d !important}
|
|
||||||
.w3-theme-d1 {color:#fff !important; background-color:#a93634 !important}
|
|
||||||
.w3-theme-d2 {color:#fff !important; background-color:#96302e !important}
|
|
||||||
.w3-theme-d3 {color:#fff !important; background-color:#832a28 !important}
|
|
||||||
.w3-theme-d4 {color:#fff !important; background-color:#702423 !important}
|
|
||||||
.w3-theme-d5 {color:#fff !important; background-color:#5e1e1d !important}
|
|
||||||
|
|
||||||
.w3-theme-light {color:#000 !important; background-color:#fbf3f3 !important}
|
|
||||||
.w3-theme-dark {color:#fff !important; background-color:#5e1e1d !important}
|
|
||||||
.w3-theme-action {color:#fff !important; background-color:#5e1e1d !important}
|
|
||||||
|
|
||||||
.w3-theme {color:#fff !important; background-color:#bd3d3a !important}
|
|
||||||
.w3-text-theme {color:#bd3d3a !important}
|
|
||||||
.w3-border-theme {border-color:#bd3d3a !important}
|
|
||||||
|
|
||||||
.w3-hover-theme:hover {color:#fff !important; background-color:#bd3d3a !important}
|
|
||||||
.w3-hover-text-theme:hover {color:#bd3d3a !important}
|
|
||||||
.w3-hover-border-theme:hover {border-color:#bd3d3a !important}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="w3-theme-l4">
|
<body style="color: #fff; width: 100%">
|
||||||
<header class="w3-row w3-padding w3-header w3-theme-l1">
|
|
||||||
<h1>Tilde Friends Administration</h1>
|
<h1>Tilde Friends Administration</h1>
|
||||||
</header>
|
|
||||||
</body>
|
</body>
|
||||||
<script type="module" src="script.js"></script>
|
<script type="module" src="script.js"></script>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -27,87 +27,64 @@ function global_settings_set(key, value) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function title_case(name) {
|
|
||||||
return name
|
|
||||||
.split('_')
|
|
||||||
.map((x) => x.charAt(0).toUpperCase() + x.substring(1))
|
|
||||||
.join(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('load', function () {
|
window.addEventListener('load', function () {
|
||||||
const permission_template = (permission) => html` <code>${permission}</code>`;
|
const permission_template = (permission) => html` <code>${permission}</code>`;
|
||||||
function input_template(key, description) {
|
function input_template(key, description) {
|
||||||
if (description.type === 'boolean') {
|
if (description.type === 'boolean') {
|
||||||
return html`
|
return html`
|
||||||
<li class="w3-row">
|
<div style="margin-top: 1em">
|
||||||
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${title_case(key)}</label>
|
<label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
|
||||||
<div class="w3-quarter w3-padding">${description.description}</div>
|
<div>
|
||||||
<div class="w3-quarter w3-padding w3-center"><input class="w3-check" type="checkbox" ?checked=${description.value} id=${'gs_' + key}></input></div>
|
<input type="checkbox" ?checked=${description.value} id=${'gs_' + key}></input>
|
||||||
<button class="w3-quarter w3-button w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.firstChild.checked)}>Set</button>
|
<button @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.checked)}>Set</button>
|
||||||
</li>
|
<div>${description.description}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
} else if (description.type === 'textarea') {
|
} else if (description.type === 'textarea') {
|
||||||
return html`
|
return html`
|
||||||
<li class="w3-row">
|
<div style="margin-top: 1em"">
|
||||||
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold"
|
<label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
|
||||||
>${title_case(key)}</label
|
<div style="width: 100%; padding: 0; margin: 0">
|
||||||
>
|
<div style="width: 90%; padding: 0 margin: 0">
|
||||||
<div class="w3-rest w3-padding">${description.description}</div>
|
<textarea style="vertical-align: top; width: 100%" rows=20 cols=80 id=${'gs_' + key}>${description.value}</textarea>
|
||||||
<textarea
|
</div>
|
||||||
class="w3-input"
|
<button @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.firstElementChild.value)}>Set</button>
|
||||||
style="vertical-align: top; resize: vertical"
|
<div>${description.description}</div>
|
||||||
id=${'gs_' + key}
|
</div>
|
||||||
>
|
</div>
|
||||||
${description.value}</textarea
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="w3-button w3-right w3-quarter w3-theme-action"
|
|
||||||
@click=${(e) =>
|
|
||||||
global_settings_set(
|
|
||||||
key,
|
|
||||||
e.srcElement.previousElementSibling.value
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
Set
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
`;
|
`;
|
||||||
} else if (description.type != 'hidden') {
|
} else {
|
||||||
return html`
|
return html`
|
||||||
<li class="w3-row">
|
<div style="margin-top: 1em">
|
||||||
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${title_case(key)}</label>
|
<label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
|
||||||
<div class="w3-quarter w3-padding">${description.description}</div>
|
<div>
|
||||||
<input class="w3-input w3-quarter" type="text" value="${description.value}" id=${'gs_' + key}></input>
|
<input type="text" value="${description.value}" id=${'gs_' + key}></input>
|
||||||
<button class="w3-button w3-quarter w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.value)}>Set</button>
|
<button @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.value)}>Set</button>
|
||||||
</li>
|
<div>${description.description}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const user_template = (user, permissions) => html`
|
const user_template = (user, permissions) => html`
|
||||||
<li class="w3-card w3-margin">
|
<li>
|
||||||
<button
|
<button @click=${(e) => delete_user(user)}>Delete</button>
|
||||||
class="w3-button w3-theme-action"
|
|
||||||
@click=${(e) => delete_user(user)}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
${user}: ${permissions.map((x) => permission_template(x))}
|
${user}: ${permissions.map((x) => permission_template(x))}
|
||||||
</li>
|
</li>
|
||||||
`;
|
`;
|
||||||
const users_template = (users) =>
|
const users_template = (users) =>
|
||||||
html` <header class="w3-container w3-theme-l2"><h2>Users</h2></header>
|
html`<h2>Users</h2>
|
||||||
<ul class="w3-ul">
|
<ul>
|
||||||
${Object.entries(users).map((u) => user_template(u[0], u[1]))}
|
${Object.entries(users).map((u) => user_template(u[0], u[1]))}
|
||||||
</ul>`;
|
</ul>`;
|
||||||
const page_template = (data) =>
|
const page_template = (data) =>
|
||||||
html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%">
|
html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%">
|
||||||
<header class="w3-container w3-theme-l2"><h2>Global Settings</h2></header>
|
<h2>Global Settings</h2>
|
||||||
<div class="w3-container">
|
<div>
|
||||||
<ul class="w3-ul">
|
|
||||||
${Object.keys(data.settings)
|
${Object.keys(data.settings)
|
||||||
.sort()
|
.sort()
|
||||||
.map((x) => html`${input_template(x, data.settings[x])}`)}
|
.map((x) => html`${input_template(x, data.settings[x])}`)}
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
${users_template(data.users)}
|
${users_template(data.users)}
|
||||||
</div> `;
|
</div> `;
|
||||||
|
|||||||
@@ -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": "&sJqeyYjHys6Z8IqqtZ2ij2ZC1E2xieu/FU/u2hE+O1U=.sha256"
|
"previous": "&miGORZ8BwjHg2YO0t4bms6SI28XWPYqnqOZ8u9zsbZc=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function* treeify(prefix, o) {
|
|||||||
|
|
||||||
function markdown(md) {
|
function markdown(md) {
|
||||||
let parsed = new commonmark.Parser().parse(md ?? '*undocumented*');
|
let parsed = new commonmark.Parser().parse(md ?? '*undocumented*');
|
||||||
return new commonmark.HtmlRenderer({safe: true}).render(parsed);
|
return new commonmark.HtmlRenderer().render(parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
function document(api) {
|
function document(api) {
|
||||||
@@ -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,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "💻",
|
"emoji": "💻",
|
||||||
"previous": "&sFRTDn/RpxP1NJeECXHrXKwCRUJsEOEDVaCMPl50zpM=.sha256"
|
"previous": "&icsPplXHgmpkbNWyo/WTvRdT/A8BXxW4lJixOtP4ueQ=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ async function fetch_info(apps) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
async function fetch_shared_apps() {
|
async function fetch_shared_apps() {
|
||||||
let messages = {};
|
let messages = {};
|
||||||
|
|
||||||
@@ -65,17 +69,17 @@ async function main() {
|
|||||||
const stylesheet = `
|
const stylesheet = `
|
||||||
body {
|
body {
|
||||||
color: whitesmoke;
|
color: whitesmoke;
|
||||||
margin: 8px;
|
font-family: sans-serif;
|
||||||
|
margin: 16px;
|
||||||
}
|
}
|
||||||
|
.container {
|
||||||
.iconbox {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
|
grid-template-columns: repeat(auto-fill, 64px);
|
||||||
}
|
gap: 1em;
|
||||||
|
justify-content: space-around;
|
||||||
.iconbox::after {
|
background-color: #ffffff10;
|
||||||
content: "";
|
border: 2px solid #073642;
|
||||||
flex: auto;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app {
|
.app {
|
||||||
@@ -97,28 +101,16 @@ async function main() {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const body = `
|
const body = `
|
||||||
<h1>Welcome to Tilde Friends</h1>
|
<h1 style="text-shadow: #808080 0 0 10px;">Welcome to Tilde Friends.</h1>
|
||||||
|
|
||||||
<div class="w3-card-4 w3-margin-top">
|
<h2>your apps</h2>
|
||||||
<header class="w3-container w3-light-blue">
|
<div id="apps" class="container"></div>
|
||||||
<h2>Your Apps</h2>
|
|
||||||
</header>
|
|
||||||
<div id="apps" class="w3-indigo iconbox"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w3-card-4 w3-margin-top">
|
<h2>shared apps</h2>
|
||||||
<header class="w3-container w3-light-blue">
|
<div id="shared_apps" class="container"></div>
|
||||||
<h2>Shared Apps</h2>
|
|
||||||
</header>
|
|
||||||
<div id="shared_apps" class="w3-indigo iconbox"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w3-card-4 w3-margin-top">
|
<h2>core apps</h2>
|
||||||
<header class="w3-container w3-light-blue">
|
<div id="core_apps" class="container"></div>
|
||||||
<h2>Core Apps</h2>
|
|
||||||
</header>
|
|
||||||
<div id="core_apps" class="w3-indigo iconbox"></div>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const script = `
|
const script = `
|
||||||
@@ -134,13 +126,9 @@ async function main() {
|
|||||||
|
|
||||||
// For each app in the provided list
|
// For each app in the provided list
|
||||||
for (let app of Object.keys(apps).sort()) {
|
for (let app of Object.keys(apps).sort()) {
|
||||||
|
|
||||||
// Create the item
|
// Create the item
|
||||||
let inline = document.createElement('div');
|
let div = list.appendChild(document.createElement('div'));
|
||||||
inline.style.display = 'inline-block';
|
|
||||||
inline.classList.add('w3-button');
|
|
||||||
list.appendChild(inline);
|
|
||||||
let div = document.createElement('div');
|
|
||||||
inline.appendChild(div);
|
|
||||||
div.classList.add('app');
|
div.classList.add('app');
|
||||||
|
|
||||||
// The app's icon
|
// The app's icon
|
||||||
@@ -173,13 +161,12 @@ async function main() {
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link type="text/css" rel="stylesheet" href="w3.css"></link>
|
|
||||||
<style>
|
<style>
|
||||||
${stylesheet}
|
${stylesheet}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="w3-darkgray">
|
<body>
|
||||||
${body}
|
${body}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
251
apps/apps/w3.css
251
apps/apps/w3.css
@@ -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": "&3jabNEk6W2uolzTvfXX6fcWF50N3501vtgZ6ZxFVJ1s=.sha256"
|
"previous": "&TIrBnpN3iz3O9L9MCCteAcVJZjA83EKdcfu4SCM76VE=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ export async function get_blog_message(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function markdown(md) {
|
export function markdown(md) {
|
||||||
let reader = new commonmark.Parser();
|
let reader = new commonmark.Parser({safe: true});
|
||||||
let writer = new commonmark.HtmlRenderer({safe: true});
|
let writer = new commonmark.HtmlRenderer();
|
||||||
let parsed = reader.parse(md || '');
|
let parsed = reader.parse(md || '');
|
||||||
let walker = parsed.walker();
|
let walker = parsed.walker();
|
||||||
let event, node;
|
let event, node;
|
||||||
|
|||||||
2
apps/blog/commonmark.min.js
vendored
2
apps/blog/commonmark.min.js
vendored
File diff suppressed because one or more lines are too long
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 +1,4 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "💽",
|
"emoji": "💽"
|
||||||
"previous": "&uQzkIe/Aj8yNhLKe3hEq+5fEJsGwIUx8NVBjJKwoV2U=.sha256"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,19 +51,6 @@ async function key_list(db) {
|
|||||||
app.setDocument(doc);
|
app.setDocument(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
function load() {
|
|
||||||
if (core.user?.credentials?.session) {
|
|
||||||
database_list();
|
|
||||||
} else {
|
|
||||||
app.setDocument(`<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<body style="background: #888">
|
|
||||||
<h1>Must be signed in to examine databases.</h1>
|
|
||||||
</body>
|
|
||||||
</html>`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
core.register('message', async function (message) {
|
core.register('message', async function (message) {
|
||||||
if (message.event == 'hashChange') {
|
if (message.event == 'hashChange') {
|
||||||
let hash = message.hash.substring(1);
|
let hash = message.hash.substring(1);
|
||||||
@@ -75,9 +62,9 @@ core.register('message', async function (message) {
|
|||||||
} else if (hash.length) {
|
} else if (hash.length) {
|
||||||
key_list(await database(hash.split(':').slice(1).join(':')));
|
key_list(await database(hash.split(':').slice(1).join(':')));
|
||||||
} else {
|
} else {
|
||||||
load();
|
database_list();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
load();
|
database_list();
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "➡️",
|
"emoji": "➡️"
|
||||||
"previous": "&YDDSzbRD8NFAykYlZnk4r4hAK5qXjT5LmKE6rhS1s+A=.sha256"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ async function contacts_internal(id, last_row_id, following, max_row_id) {
|
|||||||
result.blocking = result.blocking || {};
|
result.blocking = result.blocking || {};
|
||||||
let contacts = await query(
|
let contacts = await query(
|
||||||
`
|
`
|
||||||
SELECT json(content) AS content FROM messages
|
SELECT content FROM messages
|
||||||
WHERE author = ? AND
|
WHERE author = ? AND
|
||||||
rowid > ? AND
|
rowid > ? AND
|
||||||
rowid <= ? AND
|
rowid <= ? AND
|
||||||
@@ -189,6 +189,50 @@ async function fetch_about(db, ids, users) {
|
|||||||
return Object.assign({}, users);
|
return Object.assign({}, users);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getAbout(db, id) {
|
||||||
|
if (g_about_cache[id]) {
|
||||||
|
return g_about_cache[id];
|
||||||
|
}
|
||||||
|
let o = await db.get(id + ':about');
|
||||||
|
const k_version = 4;
|
||||||
|
let f = o ? JSON.parse(o) : o;
|
||||||
|
if (!f || f.version != k_version) {
|
||||||
|
f = {about: {}, sequence: 0, version: k_version};
|
||||||
|
}
|
||||||
|
await ssb.sqlAsync(
|
||||||
|
'SELECT ' +
|
||||||
|
' sequence, ' +
|
||||||
|
' content ' +
|
||||||
|
'FROM messages ' +
|
||||||
|
'WHERE ' +
|
||||||
|
' author = ?1 AND ' +
|
||||||
|
' sequence > ?2 AND ' +
|
||||||
|
" json_extract(content, '$.type') = 'about' AND " +
|
||||||
|
" json_extract(content, '$.about') = ?1 " +
|
||||||
|
'UNION SELECT MAX(sequence) as sequence, NULL FROM messages WHERE author = ?1 ' +
|
||||||
|
'ORDER BY sequence',
|
||||||
|
[id, f.sequence],
|
||||||
|
function (row) {
|
||||||
|
f.sequence = row.sequence;
|
||||||
|
if (row.content) {
|
||||||
|
let about = {};
|
||||||
|
try {
|
||||||
|
about = JSON.parse(row.content);
|
||||||
|
} catch {}
|
||||||
|
delete about.about;
|
||||||
|
delete about.type;
|
||||||
|
f.about = Object.assign(f.about, about);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let j = JSON.stringify(f);
|
||||||
|
if (o != j) {
|
||||||
|
await db.set(id + ':about', j);
|
||||||
|
}
|
||||||
|
g_about_cache[id] = f.about;
|
||||||
|
return f.about;
|
||||||
|
}
|
||||||
|
|
||||||
async function getSize(db, id) {
|
async function getSize(db, id) {
|
||||||
let size = 0;
|
let size = 0;
|
||||||
await ssb.sqlAsync(
|
await ssb.sqlAsync(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🪪",
|
"emoji": "🪪",
|
||||||
"previous": "&5kw/2PgcySwOYCmAkjHTR2xTkIx3i7UjQmtQ8MfgWw8=.sha256"
|
"previous": "&kgukkyDk1RxgfzgMH6H/0QeDPIuwPZypLuAFax21ljk=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import * as tfrpc from '/tfrpc.js';
|
import * as tfrpc from '/tfrpc.js';
|
||||||
|
|
||||||
const is_admin = core.user?.credentials?.permissions?.administration;
|
|
||||||
|
|
||||||
tfrpc.register(async function get_private_key(id) {
|
tfrpc.register(async function get_private_key(id) {
|
||||||
return bip39Words(await ssb.getPrivateKey(id));
|
return bip39Words(await ssb.getPrivateKey(id));
|
||||||
});
|
});
|
||||||
@@ -17,44 +15,11 @@ tfrpc.register(async function delete_id(id) {
|
|||||||
tfrpc.register(async function reload() {
|
tfrpc.register(async function reload() {
|
||||||
await main();
|
await main();
|
||||||
});
|
});
|
||||||
tfrpc.register(async function make_server(id) {
|
|
||||||
return await ssb.swapWithServerIdentity(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
let ids = await ssb.getIdentities();
|
let ids = await ssb.getIdentities();
|
||||||
let server_id = await ssb.getServerIdentity();
|
|
||||||
await app.setDocument(
|
await app.setDocument(
|
||||||
`
|
`<body style="color: #fff">
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" href="w3.css"></link>
|
|
||||||
<style>
|
|
||||||
/* "2018 Sargasso Sea" */
|
|
||||||
.w3-theme-l5 {color:#000 !important; background-color:#f3f4f7 !important}
|
|
||||||
.w3-theme-l4 {color:#000 !important; background-color:#d7dbe3 !important}
|
|
||||||
.w3-theme-l3 {color:#000 !important; background-color:#b0b6c8 !important}
|
|
||||||
.w3-theme-l2 {color:#fff !important; background-color:#8892ac !important}
|
|
||||||
.w3-theme-l1 {color:#fff !important; background-color:#636f8e !important}
|
|
||||||
.w3-theme-d1 {color:#fff !important; background-color:#40485c !important}
|
|
||||||
.w3-theme-d2 {color:#fff !important; background-color:#394052 !important}
|
|
||||||
.w3-theme-d3 {color:#fff !important; background-color:#323848 !important}
|
|
||||||
.w3-theme-d4 {color:#fff !important; background-color:#2b303d !important}
|
|
||||||
.w3-theme-d5 {color:#fff !important; background-color:#242833 !important}
|
|
||||||
|
|
||||||
.w3-theme-light {color:#000 !important; background-color:#f3f4f7 !important}
|
|
||||||
.w3-theme-dark {color:#fff !important; background-color:#242833 !important}
|
|
||||||
.w3-theme-action {color:#fff !important; background-color:#242833 !important}
|
|
||||||
|
|
||||||
.w3-theme {color:#fff !important; background-color:#485167 !important}
|
|
||||||
.w3-text-theme {color:#485167 !important}
|
|
||||||
.w3-border-theme {border-color:#485167 !important}
|
|
||||||
|
|
||||||
.w3-hover-theme:hover {color:#fff !important; background-color:#485167 !important}
|
|
||||||
.w3-hover-text-theme:hover {color:#485167 !important}
|
|
||||||
.w3-hover-border-theme:hover {border-color:#485167 !important}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="w3-theme-l3">
|
|
||||||
<script>const handler = {};</script>
|
<script>const handler = {};</script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
@@ -62,8 +27,7 @@ async function main() {
|
|||||||
let id = event.srcElement.dataset.id;
|
let id = event.srcElement.dataset.id;
|
||||||
let element = document.createElement('textarea');
|
let element = document.createElement('textarea');
|
||||||
element.value = await tfrpc.rpc.get_private_key(id);
|
element.value = await tfrpc.rpc.get_private_key(id);
|
||||||
element.style = 'width: 100%; height: auto; read-only: true; resize: none';
|
element.style = 'width: 100%; read-only: true';
|
||||||
element.classList.add('w3-input');
|
|
||||||
element.readOnly = true;
|
element.readOnly = true;
|
||||||
event.srcElement.parentElement.appendChild(element);
|
event.srcElement.parentElement.appendChild(element);
|
||||||
event.srcElement.onclick = event => handler.hide_id(event, element);
|
event.srcElement.onclick = event => handler.hide_id(event, element);
|
||||||
@@ -84,7 +48,7 @@ async function main() {
|
|||||||
alert('Successfully created: ' + id);
|
alert('Successfully created: ' + id);
|
||||||
await tfrpc.rpc.reload();
|
await tfrpc.rpc.reload();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('Error creating identity: ' + e.message);
|
alert('Error creating identity: ' + e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handler.hide_id = function hide_id(event, element) {
|
handler.hide_id = function hide_id(event, element) {
|
||||||
@@ -104,48 +68,24 @@ async function main() {
|
|||||||
alert('Error deleting ID: ' + e);
|
alert('Error deleting ID: ' + e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handler.make_server = async function make_server(event) {
|
|
||||||
let id = event.srcElement.dataset.id;
|
|
||||||
try {
|
|
||||||
if (confirm('Are you sure you want to make "' + id + '" the server identity?\\n\\nFor it to take effect, you will need to both sign in again and restart Tilde Friends.')) {
|
|
||||||
await tfrpc.rpc.make_server(id);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
alert('Error making server ID: ' + e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
<header class="w3-theme w3-padding"><h1>SSB Identity Management</h1></header>
|
<h1>SSB Identity Management</h1>
|
||||||
<div class="w3-card-4 w3-margin">
|
<h2>Create a new identity</h2>
|
||||||
<header class="w3-container w3-theme-l2"><h2>Create a new identity</h2></header>
|
<button id="create_id" onclick="handler.create_id()">Create Identity</button>
|
||||||
<footer class="w3-padding">
|
<h2>Import an SSB Identity from 12 BIP39 English Words</h2>
|
||||||
<button id="create_id" onclick="handler.create_id()" class="w3-button w3-theme">Create Identity</button>
|
<textarea id="add_id" style="width: 100%" rows="4"></textarea><button id="add" onclick="handler.add_id(event)">Import Identity</button>
|
||||||
</footer>
|
<h2>Identities</h2>
|
||||||
</div>
|
<ul>` +
|
||||||
<div class="w3-card-4 w3-margin">
|
ids
|
||||||
<header class="w3-container w3-theme-l2"><h2>Import an SSB Identity from 12 BIP39 English Words</h2></header>
|
|
||||||
<textarea id="add_id" style="width: 100%" rows="4" class="w3-input"></textarea>
|
|
||||||
<footer class="w3-padding">
|
|
||||||
<button id="add" onclick="handler.add_id(event)" class="w3-button w3-theme">Import Identity</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
<div class="w3-card-4 w3-margin">
|
|
||||||
<header class="w3-container w3-theme-l2"><h2>Identities</h2></header>
|
|
||||||
<ul class="w3-ul">` +
|
|
||||||
(ids ?? [])
|
|
||||||
.map(
|
.map(
|
||||||
(
|
(id) => `<li>
|
||||||
id
|
<button onclick="handler.export_id(event)" data-id="${id}">Export Identity</button>
|
||||||
) => `<li style="overflow: hidden; text-wrap: nowrap; text-overflow: ellipsis">
|
<button onclick="handler.delete_id(event)" data-id="${id}">Delete Identity</button>
|
||||||
<button onclick="handler.export_id(event)" data-id="${id}" class="w3-button w3-theme">Export Identity</button>
|
${id}
|
||||||
<button onclick="handler.delete_id(event)" data-id="${id}" class="w3-button w3-theme">Delete Identity</button>
|
|
||||||
${is_admin && id != server_id ? `<button onclick="handler.make_server(event)" data-id="${id}" class="w3-button w3-theme">Make Server Identity</button>` : ''}
|
|
||||||
${id}${id == server_id ? ' <div class="w3-tag w3-theme-l4 w3-round">🖥 local server</div>' : ''}
|
|
||||||
</li>`
|
</li>`
|
||||||
)
|
)
|
||||||
.join('\n') +
|
.join('\n') +
|
||||||
` </ul>
|
` </ul>
|
||||||
</div>
|
|
||||||
</body>`
|
</body>`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "tildefriends-app",
|
|
||||||
"emoji": "💡",
|
|
||||||
"previous": "&eN6DNPpQUNhGvxneLuLPgsOXR6qyFZ7u+MAz0b4fa7k=.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,286 +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
|
|
||||||
style="
|
|
||||||
flex: 1 1 auto;
|
|
||||||
overflow: auto;
|
|
||||||
contain: content;
|
|
||||||
padding-top: 16px;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="slide">
|
|
||||||
<div
|
|
||||||
class="w3-content w3-xlarge w3-card-4 w3-blue w3-panel w3-padding-32 w3-round-xlarge"
|
|
||||||
style="margin: 32px"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div>Welcome to</div>
|
|
||||||
<div>~😎 Tilde Friends.</div>
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
<button class="w3-button w3-yellow proceed">Next</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="slide w3-card-4 w3-gray" style="width: 90%">
|
|
||||||
<header class="w3-container w3-blue w3-xlarge">
|
|
||||||
<h1>This brief tutorial will introduce:</h1>
|
|
||||||
</header>
|
|
||||||
<ul class="w3-large w3-left-align">
|
|
||||||
<li><b>Secure Scuttlebutt</b>, a decentralized social network.</li>
|
|
||||||
<li>
|
|
||||||
<b>Tilde Friends</b>, the application platform that you are using
|
|
||||||
right now.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<b>How to get started</b> if you want to get gossiping right away.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<footer class="w3-center w3-xlarge w3-padding">
|
|
||||||
<button class="w3-button w3-yellow proceed">Onward</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
<div class="slide w3-gray" style="width: 90%">
|
|
||||||
<div class="w3-card-4 w3-xlarge">
|
|
||||||
<header class="w3-container w3-blue">
|
|
||||||
<h1>💻Secure Scuttlebutt in a Nutshell🦀</h1>
|
|
||||||
</header>
|
|
||||||
<div class="w3-container w3-large w3-left-align">
|
|
||||||
<p>
|
|
||||||
Secure Scuttlebutt is a social network whose technical operation
|
|
||||||
attempts to mirror human social interaction.
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
You can create your own account and post to your own feed on
|
|
||||||
your own device. This is all <b>local</b> with no external
|
|
||||||
communication. This puts you fully in control of your own words
|
|
||||||
and actions.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Before you can interact with others, you need to
|
|
||||||
<b>connect over the network</b>, either directly to your friends
|
|
||||||
(i.e., peer-to-peer between your phones on coffee shop Wi-Fi) or
|
|
||||||
to 🚪<i>rooms</i> and 🍻<i>pubs</i> (hint: search the web for
|
|
||||||
<i>#ssbroom</i>).
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Who you choose to <b>follow</b> determines what you see, with
|
|
||||||
most people choosing to see messages from friends and friends of
|
|
||||||
those friends. If you encounter content you'd rather not see,
|
|
||||||
<b>block</b> the offending account to improve the experience for
|
|
||||||
you and your followers.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Your feed is an <b>immutable</b> log of your activity. Post with
|
|
||||||
care, because like your words in real life, posts can't be taken
|
|
||||||
back.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<footer class="w3-center w3-xlarge w3-padding">
|
|
||||||
<a
|
|
||||||
class="w3-button w3-light-gray"
|
|
||||||
href="https://scuttlebutt.nz/"
|
|
||||||
target="_blank"
|
|
||||||
>See scuttlebutt.nz</a
|
|
||||||
>
|
|
||||||
<button class="w3-button w3-yellow proceed">Got It</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="slide w3-gray" style="width: 90%">
|
|
||||||
<div class="w3-card-4 w3-xlarge">
|
|
||||||
<header class="w3-container w3-blue w3-center">
|
|
||||||
<h1>~😎 Let's Talk Tilde Friends ~😎</h1>
|
|
||||||
</header>
|
|
||||||
<div class="w3-container w3-large w3-left-align">
|
|
||||||
<p>
|
|
||||||
Tilde Friends is an application platform that is an application of
|
|
||||||
its own.
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
This intro is a Tilde Friends app. You can click <b>edit</b> at
|
|
||||||
the top to look under the hood and make changes.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
It is already possible to make and share new applications using
|
|
||||||
only Tilde Friends and Secure Scuttlebutt without having to set
|
|
||||||
up development environments, configure web servers, register
|
|
||||||
domain names, or pay for hosting services.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
But it's also set up so that you can't just break an app that
|
|
||||||
everybody is using or do malicious things with personal content.
|
|
||||||
There are <b>protections</b> in place like an operating system.
|
|
||||||
The intent is also for it to be <b>safe</b> to run strange apps
|
|
||||||
without worrying about adverse effects.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
But this is all a big 🚧work in progress🚧 and
|
|
||||||
<b>experiment</b>. Let's see where it takes us.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<footer class="w3-center w3-xlarge w3-padding">
|
|
||||||
<button class="w3-button w3-yellow proceed">Okay</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="slide w3-gray" style="width: 90%">
|
|
||||||
<div class="w3-card-4 w3-xlarge">
|
|
||||||
<header class="w3-container w3-blue w3-center">
|
|
||||||
<h1>🦀Let's Get this Tilde Friends Party Started🎉</h1>
|
|
||||||
</header>
|
|
||||||
<div class="w3-container w3-large w3-left-align">
|
|
||||||
<p>The button below will take you to the Secure Scuttlebutt app.</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Remember:
|
|
||||||
<ol>
|
|
||||||
<li>You are in charge. This is all on your device.</li>
|
|
||||||
<li>
|
|
||||||
Make network connections to exchange messages with others.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Follow more accounts to see more content, and block those
|
|
||||||
posting content you'd rather not see.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Be respectful, and consider the consequences of what you
|
|
||||||
post.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
This is all under active development. Exercise patience, and
|
|
||||||
report issues encountered.
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
To see this tutorial again later, select <b>apps</b> ->
|
|
||||||
<b>Core Apps</b> -> <b>intro</b>.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<footer class="w3-center w3-xlarge w3-padding">
|
|
||||||
<button class="w3-button w3-yellow" id="complete">Let's Go!</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="w3-text-white w3-xlarge w3-center w3-flex"
|
|
||||||
style="
|
|
||||||
width: 100%;
|
|
||||||
flex: 0 1;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="w3-jumbo" id="left" style="flex: 1 0; cursor: pointer">
|
|
||||||
❮
|
|
||||||
</div>
|
|
||||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
|
||||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
|
||||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
|
||||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
|
||||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
|
||||||
<div class="w3-jumbo" style="flex: 1 0; cursor: pointer" id="right">
|
|
||||||
❯
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="module">
|
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
|
||||||
|
|
||||||
let index = 0;
|
|
||||||
function set(i) {
|
|
||||||
show(i - index);
|
|
||||||
}
|
|
||||||
function show(delta) {
|
|
||||||
let slides = [...document.getElementsByClassName('slide')];
|
|
||||||
let dots = [...document.getElementsByClassName('dot')];
|
|
||||||
index = (index + delta + slides.length) % slides.length;
|
|
||||||
for (let slide of slides) {
|
|
||||||
slide.style.display =
|
|
||||||
slides.indexOf(slide) == index ? 'block' : 'none';
|
|
||||||
}
|
|
||||||
for (let dot of dots) {
|
|
||||||
if (dots.indexOf(dot) == index) {
|
|
||||||
dot.classList.add('w3-white');
|
|
||||||
} else {
|
|
||||||
dot.classList.remove('w3-white');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.getElementById('left').style.visibility =
|
|
||||||
index == 0 ? 'hidden' : 'visible';
|
|
||||||
document.getElementById('right').style.visibility =
|
|
||||||
index == slides.length - 1 ? 'hidden' : 'visible';
|
|
||||||
}
|
|
||||||
|
|
||||||
let dots = [...document.getElementsByClassName('dot')];
|
|
||||||
for (let dot of dots) {
|
|
||||||
dot.onclick = () => set(dots.indexOf(dot));
|
|
||||||
}
|
|
||||||
for (let button of document.getElementsByClassName('proceed')) {
|
|
||||||
button.onclick = () => show(1);
|
|
||||||
}
|
|
||||||
document.getElementById('left').onclick = () => show(-1);
|
|
||||||
document.getElementById('right').onclick = () => show(1);
|
|
||||||
document.getElementById('complete').onclick = function () {
|
|
||||||
console.log('completing');
|
|
||||||
tfrpc.rpc.complete().finally(function () {
|
|
||||||
console.log('completed');
|
|
||||||
let a = document.createElement('a');
|
|
||||||
a.href = '/~core/ssb/';
|
|
||||||
a.target = '_top';
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
window.addEventListener('keyup', function (event) {
|
|
||||||
if (event.key == 'ArrowLeft') {
|
|
||||||
show(-1);
|
|
||||||
} else if (event.key == 'ArrowRight') {
|
|
||||||
show(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
show(0);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -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": "&O0huuEgL/UQC9bkUfhPOyZFo9eRiz+koOkba6QwCGNA=.sha256"
|
"previous": "&TegdzvFE+im94shygaHkgDYSaSrwY2h0OKUXSRPBQDM=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ tfrpc.register(function getHash(id, message) {
|
|||||||
tfrpc.register(function setHash(hash) {
|
tfrpc.register(function setHash(hash) {
|
||||||
return app.setHash(hash);
|
return app.setHash(hash);
|
||||||
});
|
});
|
||||||
|
ssb.addEventListener('message', async function (id) {
|
||||||
|
await tfrpc.rpc.notifyNewMessage(id);
|
||||||
|
});
|
||||||
tfrpc.register(async function store_blob(blob) {
|
tfrpc.register(async function store_blob(blob) {
|
||||||
if (Array.isArray(blob)) {
|
if (Array.isArray(blob)) {
|
||||||
blob = Uint8Array.from(blob);
|
blob = Uint8Array.from(blob);
|
||||||
@@ -82,18 +85,13 @@ tfrpc.register(async function store_message(message) {
|
|||||||
tfrpc.register(function apps() {
|
tfrpc.register(function apps() {
|
||||||
return core.apps();
|
return core.apps();
|
||||||
});
|
});
|
||||||
tfrpc.register(function getActiveIdentity() {
|
|
||||||
return ssb.getActiveIdentity();
|
|
||||||
});
|
|
||||||
tfrpc.register(async function try_decrypt(id, content) {
|
tfrpc.register(async function try_decrypt(id, content) {
|
||||||
return await ssb.privateMessageDecrypt(id, content);
|
return await ssb.privateMessageDecrypt(id, content);
|
||||||
});
|
});
|
||||||
core.register('onMessage', async function (id) {
|
ssb.addEventListener('broadcasts', async function () {
|
||||||
await tfrpc.rpc.notifyNewMessage(id);
|
|
||||||
});
|
|
||||||
core.register('onBroadcastsChanged', async function () {
|
|
||||||
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
|
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
|
||||||
});
|
});
|
||||||
|
|
||||||
core.register('onConnectionsChanged', async function () {
|
core.register('onConnectionsChanged', async function () {
|
||||||
await tfrpc.rpc.set('connections', await ssb.connections());
|
await tfrpc.rpc.set('connections', await ssb.connections());
|
||||||
});
|
});
|
||||||
|
|||||||
2
apps/issues/commonmark.min.js
vendored
2
apps/issues/commonmark.min.js
vendored
File diff suppressed because one or more lines are too long
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
@@ -4,6 +4,48 @@ import * as tfutils from './tf-utils.js';
|
|||||||
|
|
||||||
const k_project = '%Hr+4xEVtjplidSKBlRWi4Aw/0Tfw7B+1OR9BzlDKmOI=.sha256';
|
const k_project = '%Hr+4xEVtjplidSKBlRWi4Aw/0Tfw7B+1OR9BzlDKmOI=.sha256';
|
||||||
|
|
||||||
|
class TfIdPickerElement extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
ids: {type: Array},
|
||||||
|
selected: {type: String},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
this.selected = await tfrpc.rpc.localStorageGet('whoami');
|
||||||
|
this.ids = (await tfrpc.rpc.getIdentities()) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
changed(event) {
|
||||||
|
this.selected = event.srcElement.value;
|
||||||
|
tfrpc.rpc.localStorageSet('whoami', this.selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.ids) {
|
||||||
|
return html`
|
||||||
|
<select @change=${this.changed} style="max-width: 100%">
|
||||||
|
${this.ids.map(
|
||||||
|
(id) =>
|
||||||
|
html`<option ?selected=${id == this.selected} value=${id}>
|
||||||
|
${id}
|
||||||
|
</option>`
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
return html`<div>Loading...</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define('tf-id-picker', TfIdPickerElement);
|
||||||
|
|
||||||
class TfComposeElement extends LitElement {
|
class TfComposeElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
@@ -63,10 +105,10 @@ class TfIssuesAppElement extends LitElement {
|
|||||||
let issues = {};
|
let issues = {};
|
||||||
let messages = await tfrpc.rpc.query(
|
let messages = await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
WITH issues AS (SELECT messages.id, json(messages.content) AS content, messages.author, messages.timestamp FROM messages_refs JOIN messages ON
|
WITH issues AS (SELECT messages.* FROM messages_refs JOIN messages ON
|
||||||
messages.id = messages_refs.message
|
messages.id = messages_refs.message
|
||||||
WHERE messages_refs.ref = ? AND json_extract(messages.content, '$.type') = 'issue'),
|
WHERE messages_refs.ref = ? AND json_extract(messages.content, '$.type') = 'issue'),
|
||||||
edits AS (SELECT messages.id, json(messages.content) AS content, messages.author, messages.timestamp FROM issues JOIN messages_refs ON
|
edits AS (SELECT messages.* FROM issues JOIN messages_refs ON
|
||||||
issues.id = messages_refs.ref JOIN messages ON
|
issues.id = messages_refs.ref JOIN messages ON
|
||||||
messages.id = messages_refs.message
|
messages.id = messages_refs.message
|
||||||
WHERE json_extract(messages.content, '$.type') IN ('issue-edit', 'post'))
|
WHERE json_extract(messages.content, '$.type') IN ('issue-edit', 'post'))
|
||||||
@@ -164,7 +206,7 @@ class TfIssuesAppElement extends LitElement {
|
|||||||
if (
|
if (
|
||||||
confirm(`Are you sure you want to ${open ? 'open' : 'close'} this issue?`)
|
confirm(`Are you sure you want to ${open ? 'open' : 'close'} this issue?`)
|
||||||
) {
|
) {
|
||||||
let whoami = await tfrpc.rpc.getActiveIdentity();
|
let whoami = this.shadowRoot.getElementById('picker').selected;
|
||||||
await tfrpc.rpc.appendMessage(whoami, {
|
await tfrpc.rpc.appendMessage(whoami, {
|
||||||
type: 'issue-edit',
|
type: 'issue-edit',
|
||||||
issues: [
|
issues: [
|
||||||
@@ -179,7 +221,7 @@ class TfIssuesAppElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async create_issue(event) {
|
async create_issue(event) {
|
||||||
let whoami = await tfrpc.rpc.getActiveIdentity();
|
let whoami = this.shadowRoot.getElementById('picker').selected;
|
||||||
await tfrpc.rpc.appendMessage(whoami, {
|
await tfrpc.rpc.appendMessage(whoami, {
|
||||||
type: 'issue',
|
type: 'issue',
|
||||||
project: k_project,
|
project: k_project,
|
||||||
@@ -189,7 +231,7 @@ class TfIssuesAppElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async reply_to_issue(event) {
|
async reply_to_issue(event) {
|
||||||
let whoami = await tfrpc.rpc.getActiveIdentity();
|
let whoami = this.shadowRoot.getElementById('picker').selected;
|
||||||
await tfrpc.rpc.appendMessage(whoami, {
|
await tfrpc.rpc.appendMessage(whoami, {
|
||||||
type: 'post',
|
type: 'post',
|
||||||
text: event.detail.value,
|
text: event.detail.value,
|
||||||
@@ -207,7 +249,10 @@ class TfIssuesAppElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let header = html` <h1>Tilde Friends Issues</h1> `;
|
let header = html`
|
||||||
|
<h1>Tilde Friends Issues</h1>
|
||||||
|
<tf-id-picker id="picker"></tf-id-picker>
|
||||||
|
`;
|
||||||
if (this.selected) {
|
if (this.selected) {
|
||||||
return html`
|
return html`
|
||||||
${header}
|
${header}
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import * as linkify from './commonmark-linkify.js';
|
import * as linkify from './commonmark-linkify.js';
|
||||||
|
|
||||||
var reUnsafeProtocol = /^javascript:|vbscript:|file:|data:/i;
|
|
||||||
var reSafeDataProtocol = /^data:image\/(?:png|gif|jpeg|webp)/i;
|
|
||||||
var potentiallyUnsafe = function (url) {
|
|
||||||
return reUnsafeProtocol.test(url) && !reSafeDataProtocol.test(url);
|
|
||||||
};
|
|
||||||
|
|
||||||
function image(node, entering) {
|
function image(node, entering) {
|
||||||
if (
|
if (
|
||||||
node.firstChild?.type === 'text' &&
|
node.firstChild?.type === 'text' &&
|
||||||
@@ -67,8 +61,8 @@ function image(node, entering) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function markdown(md) {
|
export function markdown(md) {
|
||||||
var reader = new commonmark.Parser();
|
var reader = new commonmark.Parser({safe: true});
|
||||||
var writer = new commonmark.HtmlRenderer({safe: true});
|
var writer = new commonmark.HtmlRenderer();
|
||||||
writer.image = image;
|
writer.image = image;
|
||||||
var parsed = reader.parse(md || '');
|
var parsed = reader.parse(md || '');
|
||||||
parsed = linkify.transform(parsed);
|
parsed = linkify.transform(parsed);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "📝",
|
"emoji": "📝",
|
||||||
"previous": "&5LpOTEnor/rYFk3axyfmmehAoq9aEwNQRH4jwNhRQ7o=.sha256"
|
"previous": "&b//KqE4Vx6kOSBRODK1p/8wjOLKZJ+CBB5IkaBt5YsM=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ function new_message() {
|
|||||||
return g_new_message_promise;
|
return g_new_message_promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
core.register('onMessage', function (id) {
|
ssb.addEventListener('message', function (id) {
|
||||||
let resolve = g_new_message_resolve;
|
let resolve = g_new_message_resolve;
|
||||||
g_new_message_promise = new Promise(function (resolve, reject) {
|
g_new_message_promise = new Promise(function (resolve, reject) {
|
||||||
g_new_message_resolve = resolve;
|
g_new_message_resolve = resolve;
|
||||||
|
|||||||
2
apps/journal/commonmark.min.js
vendored
2
apps/journal/commonmark.min.js
vendored
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
@@ -18,8 +18,8 @@ class TfJournalEntryElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
markdown(md) {
|
markdown(md) {
|
||||||
var reader = new commonmark.Parser();
|
var reader = new commonmark.Parser({safe: true});
|
||||||
var writer = new commonmark.HtmlRenderer({safe: true});
|
var writer = new commonmark.HtmlRenderer();
|
||||||
var parsed = reader.parse(md || '');
|
var parsed = reader.parse(md || '');
|
||||||
return writer.render(parsed);
|
return writer.render(parsed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🚪",
|
"emoji": "📦",
|
||||||
"previous": "&DJwkqNfYWtW9yBtJQMseEXm7l04Enpi+yAxZulLq9Vk=.sha256"
|
"previous": "&IU+TwyM7TznD8NBfnw7tgW2zxVlMqTVxSqWFjuosLwo=.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": "&7dPNAI4sffljUTiwGr3XEUeB8sBD72CFkWMk/o0Z2pw=.sha256"
|
"previous": "&Xs1X5TzLCk6KVr+5IDc80JAHYxJyoD10cXKBUYpFqWQ=.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);
|
||||||
@@ -22,6 +21,9 @@ tfrpc.register(async function createIdentity() {
|
|||||||
tfrpc.register(async function getServerIdentity() {
|
tfrpc.register(async function getServerIdentity() {
|
||||||
return ssb.getServerIdentity();
|
return ssb.getServerIdentity();
|
||||||
});
|
});
|
||||||
|
tfrpc.register(async function setServerFollowingMe(id, following) {
|
||||||
|
return ssb.setServerFollowingMe(id, following);
|
||||||
|
});
|
||||||
tfrpc.register(async function getIdentities() {
|
tfrpc.register(async function getIdentities() {
|
||||||
return ssb.getIdentities();
|
return ssb.getIdentities();
|
||||||
});
|
});
|
||||||
@@ -52,38 +54,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;
|
|
||||||
let entry = key ? g_sql_cache[key] : undefined;
|
|
||||||
const k_ideal_count = 64;
|
|
||||||
if (entry) {
|
|
||||||
result = entry.result;
|
|
||||||
} else {
|
|
||||||
await ssb.sqlAsync(sql, args, function callback(row) {
|
await ssb.sqlAsync(sql, args, function callback(row) {
|
||||||
result.push(row);
|
result.push(row);
|
||||||
});
|
});
|
||||||
if (key) {
|
|
||||||
g_sql_cache[key] = {
|
|
||||||
result: result,
|
|
||||||
time: new Date().valueOf(),
|
|
||||||
};
|
|
||||||
if (Object.keys(g_sql_cache).length > k_ideal_count * 2) {
|
|
||||||
let aged = Object.entries(g_sql_cache).map(([k, v]) => [v.time, k]);
|
|
||||||
aged.sort();
|
|
||||||
for (let i = 0; i < aged.length / 2; i++) {
|
|
||||||
delete g_sql_cache[aged[i][1]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let end = new Date();
|
|
||||||
print(
|
|
||||||
(end - start) / 1000,
|
|
||||||
entry ? 'from cache' : 'from db',
|
|
||||||
sql.replaceAll(/\s+/g, ' ').trim()
|
|
||||||
);
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
tfrpc.register(async function appendMessage(id, message) {
|
tfrpc.register(async function appendMessage(id, message) {
|
||||||
@@ -101,12 +76,9 @@ tfrpc.register(function getHash(id, message) {
|
|||||||
tfrpc.register(function setHash(hash) {
|
tfrpc.register(function setHash(hash) {
|
||||||
return app.setHash(hash);
|
return app.setHash(hash);
|
||||||
});
|
});
|
||||||
core.register('onMessage', async function (id) {
|
ssb.addEventListener('message', 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);
|
||||||
@@ -128,35 +100,13 @@ tfrpc.register(async function try_decrypt(id, content) {
|
|||||||
tfrpc.register(async function encrypt(id, recipients, content) {
|
tfrpc.register(async function encrypt(id, recipients, content) {
|
||||||
return await ssb.privateMessageEncrypt(id, recipients, content);
|
return await ssb.privateMessageEncrypt(id, recipients, content);
|
||||||
});
|
});
|
||||||
tfrpc.register(async function getActiveIdentity() {
|
ssb.addEventListener('broadcasts', async function () {
|
||||||
return await ssb.getActiveIdentity();
|
|
||||||
});
|
|
||||||
tfrpc.register(async function sync() {
|
|
||||||
return await ssb.sync();
|
|
||||||
});
|
|
||||||
tfrpc.register(async function url() {
|
|
||||||
return core.url;
|
|
||||||
});
|
|
||||||
tfrpc.register(async function globalSettingsGet(key) {
|
|
||||||
return core.globalSettingsGet(key);
|
|
||||||
});
|
|
||||||
tfrpc.register(async function globalSettingsSet(key, value) {
|
|
||||||
return core.globalSettingsSet(key, value);
|
|
||||||
});
|
|
||||||
tfrpc.register(function isAdministrator() {
|
|
||||||
return core.user?.credentials?.permissions?.administration;
|
|
||||||
});
|
|
||||||
|
|
||||||
core.register('onBroadcastsChanged', async function () {
|
|
||||||
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
|
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
|
||||||
});
|
});
|
||||||
|
|
||||||
core.register('onConnectionsChanged', async function () {
|
core.register('onConnectionsChanged', async function () {
|
||||||
await tfrpc.rpc.set('connections', await ssb.connections());
|
await tfrpc.rpc.set('connections', await ssb.connections());
|
||||||
});
|
});
|
||||||
core.register('setActiveIdentity', async function (id) {
|
|
||||||
await tfrpc.rpc.set('identity', id);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
if (typeof database !== 'undefined') {
|
if (typeof database !== 'undefined') {
|
||||||
|
|||||||
@@ -1,23 +1,19 @@
|
|||||||
function textNode(text) {
|
function textNode(text) {
|
||||||
const node = new commonmark.Node('text', undefined);
|
const node = new commonmark.Node("text", undefined);
|
||||||
node.literal = text;
|
node.literal = text;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
function linkNode(text, link) {
|
function linkNode(text, link) {
|
||||||
const linkNode = new commonmark.Node('link', undefined);
|
const linkNode = new commonmark.Node("link", undefined);
|
||||||
if (link.startsWith('#')) {
|
linkNode.destination = `#q=${encodeURIComponent(link)}`;
|
||||||
linkNode.destination = `#${encodeURIComponent(link)}`;
|
|
||||||
} else {
|
|
||||||
linkNode.destination = link;
|
|
||||||
}
|
|
||||||
linkNode.appendChild(textNode(text));
|
linkNode.appendChild(textNode(text));
|
||||||
return linkNode;
|
return linkNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function splitMatches(text, regexp) {
|
function splitMatches(text, regexp) {
|
||||||
// Regexp must be sticky.
|
// Regexp must be sticky.
|
||||||
regexp = new RegExp(regexp, 'gm');
|
regexp = new RegExp(regexp, "gm");
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
const result = [];
|
const result = [];
|
||||||
@@ -43,13 +39,13 @@ function splitMatches(text, regexp) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const regex = new RegExp('(?:https?://[^ ]+[^ .,])|(?:(?<!\\w)#[\\w-]+)|(?:@[A-Za-z0-9+/]+=.ed25519)|(?:[%&][A-Za-z0-9+/]+=.sha256)');
|
const regex = new RegExp("(?<!\\w)#[\\w-]+");
|
||||||
|
|
||||||
function split(textNodes) {
|
function split(textNodes) {
|
||||||
const text = textNodes.map((n) => n.literal).join('');
|
const text = textNodes.map(n => n.literal).join("");
|
||||||
const parts = splitMatches(text, regex);
|
const parts = splitMatches(text, regex);
|
||||||
|
|
||||||
return parts.map((part) => {
|
return parts.map(part => {
|
||||||
if (part[1]) {
|
if (part[1]) {
|
||||||
return linkNode(part[0], part[0]);
|
return linkNode(part[0], part[0]);
|
||||||
} else {
|
} else {
|
||||||
@@ -65,17 +61,17 @@ export function transform(parsed) {
|
|||||||
let nodes = [];
|
let nodes = [];
|
||||||
while ((event = walker.next())) {
|
while ((event = walker.next())) {
|
||||||
const node = event.node;
|
const node = event.node;
|
||||||
if (event.entering && node.type === 'text') {
|
if (event.entering && node.type === "text") {
|
||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
} else {
|
} else {
|
||||||
if (nodes.length > 0) {
|
if (nodes.length > 0) {
|
||||||
split(nodes)
|
split(nodes)
|
||||||
.reverse()
|
.reverse()
|
||||||
.forEach((newNode) => {
|
.forEach(newNode => {
|
||||||
nodes[0].insertAfter(newNode);
|
nodes[0].insertAfter(newNode);
|
||||||
});
|
});
|
||||||
|
|
||||||
nodes.forEach((n) => n.unlink());
|
nodes.forEach(n => n.unlink());
|
||||||
nodes = [];
|
nodes = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,10 +80,10 @@ export function transform(parsed) {
|
|||||||
if (nodes.length > 0) {
|
if (nodes.length > 0) {
|
||||||
split(nodes)
|
split(nodes)
|
||||||
.reverse()
|
.reverse()
|
||||||
.forEach((newNode) => {
|
.forEach(newNode => {
|
||||||
nodes[0].insertAfter(newNode);
|
nodes[0].insertAfter(newNode);
|
||||||
});
|
});
|
||||||
nodes.forEach((n) => n.unlink());
|
nodes.forEach(n => n.unlink());
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsed;
|
return parsed;
|
||||||
|
|||||||
91
apps/ssb/commonmark-linkify.js
Normal file
91
apps/ssb/commonmark-linkify.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
function textNode(text) {
|
||||||
|
const node = new commonmark.Node("text", undefined);
|
||||||
|
node.literal = text;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkNode(text, url) {
|
||||||
|
const urlNode = new commonmark.Node("link", undefined);
|
||||||
|
urlNode.destination = url;
|
||||||
|
urlNode.appendChild(textNode(text));
|
||||||
|
|
||||||
|
return urlNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitMatches(text, regexp) {
|
||||||
|
// Regexp must be sticky.
|
||||||
|
regexp = new RegExp(regexp, "gm");
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
let match = regexp.exec(text);
|
||||||
|
while (match) {
|
||||||
|
const matchText = match[0];
|
||||||
|
|
||||||
|
if (match.index > i) {
|
||||||
|
result.push([text.substring(i, match.index), false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push([matchText, true]);
|
||||||
|
i = match.index + matchText.length;
|
||||||
|
|
||||||
|
match = regexp.exec(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < text.length) {
|
||||||
|
result.push([text.substring(i, text.length), false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlRegexp = new RegExp("https?://[^ ]+[^ .,]");
|
||||||
|
|
||||||
|
function splitURLs(textNodes) {
|
||||||
|
const text = textNodes.map(n => n.literal).join("");
|
||||||
|
const parts = splitMatches(text, urlRegexp);
|
||||||
|
|
||||||
|
return parts.map(part => {
|
||||||
|
if (part[1]) {
|
||||||
|
return linkNode(part[0], part[0]);
|
||||||
|
} else {
|
||||||
|
return textNode(part[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transform(parsed) {
|
||||||
|
const walker = parsed.walker();
|
||||||
|
let event;
|
||||||
|
|
||||||
|
let nodes = [];
|
||||||
|
while ((event = walker.next())) {
|
||||||
|
const node = event.node;
|
||||||
|
if (event.entering && node.type === "text") {
|
||||||
|
nodes.push(node);
|
||||||
|
} else {
|
||||||
|
if (nodes.length > 0) {
|
||||||
|
splitURLs(nodes)
|
||||||
|
.reverse()
|
||||||
|
.forEach(newNode => {
|
||||||
|
nodes[0].insertAfter(newNode);
|
||||||
|
});
|
||||||
|
|
||||||
|
nodes.forEach(n => n.unlink());
|
||||||
|
nodes = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodes.length > 0) {
|
||||||
|
splitURLs(nodes)
|
||||||
|
.reverse()
|
||||||
|
.forEach(newNode => {
|
||||||
|
nodes[0].insertAfter(newNode);
|
||||||
|
});
|
||||||
|
nodes.forEach(n => n.unlink());
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
2
apps/ssb/commonmark.min.js
vendored
2
apps/ssb/commonmark.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,7 +1,3 @@
|
|||||||
import * as tfrpc from '/static/tfrpc.js';
|
|
||||||
import {html, render} from './lit-all.min.js';
|
|
||||||
import {styles, generate_theme} from './tf-styles.js';
|
|
||||||
|
|
||||||
let g_emojis;
|
let g_emojis;
|
||||||
|
|
||||||
function get_emojis() {
|
function get_emojis() {
|
||||||
@@ -14,20 +10,22 @@ function get_emojis() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function picker(callback, anchor, author, recent) {
|
export function picker(callback, anchor) {
|
||||||
let json = await get_emojis();
|
get_emojis().then(function (json) {
|
||||||
|
|
||||||
let div = document.createElement('div');
|
let div = document.createElement('div');
|
||||||
div.id = 'emoji_picker';
|
div.id = 'emoji_picker';
|
||||||
div.style.color = '#000';
|
div.style.color = '#000';
|
||||||
div.style.background = '#fff';
|
div.style.background = '#fff';
|
||||||
div.style.border = '1px solid #000';
|
div.style.border = '1px solid #000';
|
||||||
div.style.display = 'flex';
|
div.style.display = 'block';
|
||||||
|
div.style.position = 'absolute';
|
||||||
|
div.style.minWidth = 'min(16em, 90vw)';
|
||||||
|
div.style.width = 'min(16em, 90vw)';
|
||||||
|
div.style.maxWidth = 'min(16em, 90vw)';
|
||||||
|
div.style.maxHeight = '16em';
|
||||||
div.style.overflow = 'scroll';
|
div.style.overflow = 'scroll';
|
||||||
div.style.fontWeight = 'bold';
|
div.style.fontWeight = 'bold';
|
||||||
div.style.fontSize = 'xx-large';
|
div.style.fontSize = 'xx-large';
|
||||||
div.style.flex = '1 1';
|
|
||||||
div.style.flexDirection = 'column';
|
|
||||||
let input = document.createElement('input');
|
let input = document.createElement('input');
|
||||||
input.type = 'text';
|
input.type = 'text';
|
||||||
input.style.display = 'block';
|
input.style.display = 'block';
|
||||||
@@ -37,12 +35,19 @@ export async function picker(callback, anchor, author, recent) {
|
|||||||
input.style.position = 'relative';
|
input.style.position = 'relative';
|
||||||
div.appendChild(input);
|
div.appendChild(input);
|
||||||
let list = document.createElement('div');
|
let list = document.createElement('div');
|
||||||
list.style.overflow = 'scroll';
|
|
||||||
div.appendChild(list);
|
div.appendChild(list);
|
||||||
div.addEventListener('mousedown', function (event) {
|
div.addEventListener('mousedown', function (event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
console.log('emoji cleanup');
|
||||||
|
div.parentElement.removeChild(div);
|
||||||
|
window.removeEventListener('keydown', key_down);
|
||||||
|
console.log('removing click');
|
||||||
|
document.body.removeEventListener('mousedown', cleanup);
|
||||||
|
}
|
||||||
|
|
||||||
function key_down(event) {
|
function key_down(event) {
|
||||||
if (event.key == 'Escape') {
|
if (event.key == 'Escape') {
|
||||||
cleanup();
|
cleanup();
|
||||||
@@ -61,40 +66,6 @@ export async function picker(callback, anchor, author, recent) {
|
|||||||
}
|
}
|
||||||
let search = input.value.toLowerCase();
|
let search = input.value.toLowerCase();
|
||||||
let any_at_all = false;
|
let any_at_all = false;
|
||||||
if (recent) {
|
|
||||||
let emoji_to_name = {};
|
|
||||||
for (let row of Object.values(json)) {
|
|
||||||
for (let entry of Object.entries(row)) {
|
|
||||||
emoji_to_name[entry[1]] = entry[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let header = document.createElement('div');
|
|
||||||
header.appendChild(document.createTextNode('Recent'));
|
|
||||||
list.appendChild(header);
|
|
||||||
let any = false;
|
|
||||||
for (let entry of recent) {
|
|
||||||
if (
|
|
||||||
search &&
|
|
||||||
search.length &&
|
|
||||||
(emoji_to_name[entry] || '').toLowerCase().indexOf(search) == -1
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let emoji = document.createElement('span');
|
|
||||||
const k_size = '1.25em';
|
|
||||||
emoji.style.display = 'inline-block';
|
|
||||||
emoji.style.overflow = 'hidden';
|
|
||||||
emoji.style.cursor = 'pointer';
|
|
||||||
emoji.onclick = chosen;
|
|
||||||
emoji.title = emoji_to_name[entry] || entry;
|
|
||||||
emoji.appendChild(document.createTextNode(entry));
|
|
||||||
list.appendChild(emoji);
|
|
||||||
any = true;
|
|
||||||
}
|
|
||||||
if (!any) {
|
|
||||||
list.removeChild(header);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let row of Object.entries(json)) {
|
for (let row of Object.entries(json)) {
|
||||||
let header = document.createElement('div');
|
let header = document.createElement('div');
|
||||||
header.appendChild(document.createTextNode(row[0]));
|
header.appendChild(document.createTextNode(row[0]));
|
||||||
@@ -130,45 +101,14 @@ export async function picker(callback, anchor, author, recent) {
|
|||||||
}
|
}
|
||||||
refresh();
|
refresh();
|
||||||
input.oninput = refresh;
|
input.oninput = refresh;
|
||||||
let parent = document.createElement('div');
|
document.body.appendChild(div);
|
||||||
function cleanup() {
|
div.style.position = 'fixed';
|
||||||
parent.parentElement.removeChild(parent);
|
div.style.top = '50%';
|
||||||
window.removeEventListener('keydown', key_down);
|
div.style.left = '50%';
|
||||||
document.body.removeEventListener('mousedown', cleanup);
|
div.style.transform = 'translate(-50%, -50%)';
|
||||||
}
|
|
||||||
let modal = html`
|
|
||||||
<style>
|
|
||||||
${styles}
|
|
||||||
</style>
|
|
||||||
<style>
|
|
||||||
${generate_theme()}
|
|
||||||
</style>
|
|
||||||
<div
|
|
||||||
class="w3-modal"
|
|
||||||
style="display: block; box-sizing: border-box; z-index: 10"
|
|
||||||
>
|
|
||||||
<div class="w3-modal-content w3-card-4">
|
|
||||||
<div
|
|
||||||
class="w3-content w3-theme-d1"
|
|
||||||
style="display: flex; flex-direction: column; max-height: 80vh"
|
|
||||||
>
|
|
||||||
<header class="w3-container" style="flex: 0 0">
|
|
||||||
<h1>Choose a Reaction</h1>
|
|
||||||
<span class="w3-button w3-display-topright" @click=${cleanup}
|
|
||||||
>×</span
|
|
||||||
>
|
|
||||||
</header>
|
|
||||||
${div}
|
|
||||||
<footer class="w3-container w3-padding" style="flex: 0 0">
|
|
||||||
<button class="w3-button" @click=${cleanup}>Close</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
document.body.appendChild(parent);
|
|
||||||
render(modal, parent);
|
|
||||||
input.focus();
|
input.focus();
|
||||||
|
console.log('adding click');
|
||||||
document.body.addEventListener('mousedown', cleanup);
|
document.body.addEventListener('mousedown', cleanup);
|
||||||
window.addEventListener('keydown', key_down);
|
window.addEventListener('keydown', key_down);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html style="color: #fff">
|
||||||
<head>
|
<head>
|
||||||
<title>Tilde Friends</title>
|
<title>Tilde Friends</title>
|
||||||
<base target="_top" />
|
<base target="_top" />
|
||||||
@@ -10,14 +10,14 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body style="margin: 0; padding: 0">
|
<body style="background-color: #223a5e">
|
||||||
<tf-app></tf-app>
|
<tf-app class="w3-deep-purple" />
|
||||||
<tf-reactions-modal id="reactions_modal"></tf-reactions-modal>
|
|
||||||
<script>
|
<script>
|
||||||
window.litDisableBundleWarning = true;
|
window.litDisableBundleWarning = true;
|
||||||
</script>
|
</script>
|
||||||
<script src="filesaver.min.js"></script>
|
<script src="filesaver.min.js"></script>
|
||||||
<script src="commonmark.min.js"></script>
|
<script src="commonmark.min.js"></script>
|
||||||
|
<script src="commonmark-linkify.js" type="module"></script>
|
||||||
<script src="commonmark-hashtag.js" type="module"></script>
|
<script src="commonmark-hashtag.js" type="module"></script>
|
||||||
<script src="script.js" type="module"></script>
|
<script src="script.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
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
@@ -1,25 +1,17 @@
|
|||||||
import {LitElement, html} from './lit-all.min.js';
|
import {LitElement, html} from './lit-all.min.js';
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
|
|
||||||
|
import * as tf_id_picker from './tf-id-picker.js';
|
||||||
import * as tf_app from './tf-app.js';
|
import * as tf_app from './tf-app.js';
|
||||||
import * as tf_message from './tf-message.js';
|
import * as tf_message from './tf-message.js';
|
||||||
import * as tf_user from './tf-user.js';
|
import * as tf_user from './tf-user.js';
|
||||||
import * as tf_compose from './tf-compose.js';
|
import * as tf_compose from './tf-compose.js';
|
||||||
import * as tf_news from './tf-news.js';
|
import * as tf_news from './tf-news.js';
|
||||||
import * as tf_profile from './tf-profile.js';
|
import * as tf_profile from './tf-profile.js';
|
||||||
import * as tf_reactions_modal from './tf-reactions-modal.js';
|
import * as tf_tab_mentions from './tf-tab-mentions.js';
|
||||||
import * as tf_tab_news from './tf-tab-news.js';
|
import * as tf_tab_news from './tf-tab-news.js';
|
||||||
import * as tf_tab_news_feed from './tf-tab-news-feed.js';
|
import * as tf_tab_news_feed from './tf-tab-news-feed.js';
|
||||||
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';
|
|
||||||
|
|
||||||
window.addEventListener('load', function () {
|
|
||||||
let style = document.createElement('style');
|
|
||||||
style.innerText = tf_styles.styles;
|
|
||||||
Promise.resolve(tf_styles.generate_theme()).then(function (x) {
|
|
||||||
style.innerText += x;
|
|
||||||
});
|
|
||||||
document.body.appendChild(style);
|
|
||||||
});
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
import {LitElement, html, unsafeHTML, live} from './lit-all.min.js';
|
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||||
import * as tfutils from './tf-utils.js';
|
import * as tfutils from './tf-utils.js';
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
import {styles, 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 {
|
||||||
@@ -13,10 +13,6 @@ class TfComposeElement extends LitElement {
|
|||||||
branch: {type: String},
|
branch: {type: String},
|
||||||
apps: {type: Object},
|
apps: {type: Object},
|
||||||
drafts: {type: Object},
|
drafts: {type: Object},
|
||||||
author: {type: String},
|
|
||||||
channel: {type: String},
|
|
||||||
new_thread: {type: Boolean},
|
|
||||||
recipients: {type: Array},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,8 +25,6 @@ class TfComposeElement extends LitElement {
|
|||||||
this.branch = undefined;
|
this.branch = undefined;
|
||||||
this.apps = undefined;
|
this.apps = undefined;
|
||||||
this.drafts = {};
|
this.drafts = {};
|
||||||
this.author = undefined;
|
|
||||||
this.new_thread = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process_text(text) {
|
process_text(text) {
|
||||||
@@ -70,7 +64,7 @@ class TfComposeElement extends LitElement {
|
|||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
if (updated) {
|
if (updated) {
|
||||||
setTimeout(() => this.notify(draft), 0);
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
return tfutils.markdown(text);
|
return tfutils.markdown(text);
|
||||||
}
|
}
|
||||||
@@ -78,12 +72,14 @@ class TfComposeElement extends LitElement {
|
|||||||
input(event) {
|
input(event) {
|
||||||
let edit = this.renderRoot.getElementById('edit');
|
let edit = this.renderRoot.getElementById('edit');
|
||||||
let preview = this.renderRoot.getElementById('preview');
|
let preview = this.renderRoot.getElementById('preview');
|
||||||
preview.innerHTML = this.process_text(edit.innerText);
|
preview.innerHTML = this.process_text(edit.value);
|
||||||
let content_warning = this.renderRoot.getElementById('content_warning');
|
let content_warning = this.renderRoot.getElementById('content_warning');
|
||||||
let draft = this.get_draft();
|
let content_warning_preview = this.renderRoot.getElementById(
|
||||||
draft.text = edit.innerText;
|
'content_warning_preview'
|
||||||
draft.content_warning = content_warning?.value;
|
);
|
||||||
setTimeout(() => this.notify(draft), 0);
|
if (content_warning && content_warning_preview) {
|
||||||
|
content_warning_preview.innerText = content_warning.value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notify(draft) {
|
notify(draft) {
|
||||||
@@ -92,15 +88,21 @@ 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,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
change() {
|
||||||
|
let draft = this.get_draft();
|
||||||
|
draft.text = this.renderRoot.getElementById('edit')?.value;
|
||||||
|
draft.content_warning =
|
||||||
|
this.renderRoot.getElementById('content_warning')?.value;
|
||||||
|
this.notify(draft);
|
||||||
|
}
|
||||||
|
|
||||||
convert_to_format(buffer, type, mime_type) {
|
convert_to_format(buffer, type, mime_type) {
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
let img = new Image();
|
let img = new Image();
|
||||||
@@ -167,7 +169,8 @@ class TfComposeElement extends LitElement {
|
|||||||
size: buffer.length ?? buffer.byteLength,
|
size: buffer.length ?? buffer.byteLength,
|
||||||
};
|
};
|
||||||
let edit = self.renderRoot.getElementById('edit');
|
let edit = self.renderRoot.getElementById('edit');
|
||||||
edit.innerText += `\n`;
|
edit.value += `\n`;
|
||||||
|
self.change();
|
||||||
self.input();
|
self.input();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(e?.message);
|
alert(e?.message);
|
||||||
@@ -186,13 +189,6 @@ class TfComposeElement extends LitElement {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
document.execCommand(
|
|
||||||
'insertText',
|
|
||||||
false,
|
|
||||||
event.clipboardData.getData('text/plain')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
@@ -201,27 +197,12 @@ class TfComposeElement extends LitElement {
|
|||||||
let edit = this.renderRoot.getElementById('edit');
|
let edit = this.renderRoot.getElementById('edit');
|
||||||
let message = {
|
let message = {
|
||||||
type: 'post',
|
type: 'post',
|
||||||
text: edit.innerText,
|
text: edit.value,
|
||||||
channel: this.channel,
|
|
||||||
};
|
};
|
||||||
if (this.root || this.branch) {
|
if (this.root || this.branch) {
|
||||||
message.root = this.new_thread ? (this.branch ?? this.root) : this.root;
|
message.root = this.root;
|
||||||
message.branch = this.branch;
|
message.branch = this.branch;
|
||||||
}
|
}
|
||||||
let reply = Object.fromEntries(
|
|
||||||
(
|
|
||||||
await tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
SELECT messages.id, messages.author FROM messages
|
|
||||||
JOIN json_each(?) AS refs ON messages.id = refs.value
|
|
||||||
`,
|
|
||||||
[JSON.stringify([this.root, this.branch])]
|
|
||||||
)
|
|
||||||
).map((row) => [row.id, row.author])
|
|
||||||
);
|
|
||||||
if (Object.keys(reply).length) {
|
|
||||||
message.reply = reply;
|
|
||||||
}
|
|
||||||
if (Object.values(draft.mentions || {}).length) {
|
if (Object.values(draft.mentions || {}).length) {
|
||||||
message.mentions = Object.values(draft.mentions);
|
message.mentions = Object.values(draft.mentions);
|
||||||
}
|
}
|
||||||
@@ -243,27 +224,35 @@ class TfComposeElement extends LitElement {
|
|||||||
console.log('encrypted as', message);
|
console.log('encrypted as', message);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await tfrpc.rpc.appendMessage(this.whoami, message);
|
await tfrpc.rpc.appendMessage(this.whoami, message).then(function () {
|
||||||
|
edit.value = '';
|
||||||
|
self.change();
|
||||||
self.notify(undefined);
|
self.notify(undefined);
|
||||||
|
self.requestUpdate();
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert(error.message);
|
alert(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
discard() {
|
discard() {
|
||||||
|
let edit = this.renderRoot.getElementById('edit');
|
||||||
|
edit.value = '';
|
||||||
|
this.change();
|
||||||
|
let preview = this.renderRoot.getElementById('preview');
|
||||||
|
preview.innerHTML = '';
|
||||||
this.notify(undefined);
|
this.notify(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
attach() {
|
attach() {
|
||||||
let self = this;
|
let self = this;
|
||||||
|
let edit = this.renderRoot.getElementById('edit');
|
||||||
let input = document.createElement('input');
|
let input = document.createElement('input');
|
||||||
input.type = 'file';
|
input.type = 'file';
|
||||||
input.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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,9 +262,9 @@ class TfComposeElement extends LitElement {
|
|||||||
try {
|
try {
|
||||||
let rows = await tfrpc.rpc.query(
|
let rows = await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
SELECT json(messages.content) AS content FROM messages_fts(?)
|
SELECT json(messages.content) FROM messages_fts(?)
|
||||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||||
WHERE json(messages.content) LIKE ?
|
WHERE messages.content LIKE ?
|
||||||
ORDER BY timestamp DESC LIMIT 10
|
ORDER BY timestamp DESC LIMIT 10
|
||||||
`,
|
`,
|
||||||
['"' + text.replace('"', '""') + '"', `%%`]
|
['"' + text.replace('"', '""') + '"', `%%`]
|
||||||
@@ -294,65 +283,41 @@ class TfComposeElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get_values() {
|
|
||||||
let values = Object.entries(this.users).map((x) => ({
|
|
||||||
key: x[1].name ?? x[0],
|
|
||||||
value: x[0],
|
|
||||||
}));
|
|
||||||
if (this.author) {
|
|
||||||
values = [].concat(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
key: this.users[this.author]?.name,
|
|
||||||
value: this.author,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
values
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
firstUpdated() {
|
firstUpdated() {
|
||||||
let tribute = new Tribute({
|
let tribute = new Tribute({
|
||||||
iframe: this.shadowRoot,
|
|
||||||
collection: [
|
collection: [
|
||||||
{
|
{
|
||||||
values: this.get_values(),
|
values: Object.entries(this.users).map((x) => ({
|
||||||
|
key: x[1].name,
|
||||||
|
value: x[0],
|
||||||
|
})),
|
||||||
selectTemplate: function (item) {
|
selectTemplate: function (item) {
|
||||||
return item
|
return `[@${item.original.key}](${item.original.value})`;
|
||||||
? `[@${item.original.key}](${item.original.value})`
|
|
||||||
: undefined;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
trigger: '&',
|
trigger: '&',
|
||||||
values: this.autocomplete,
|
values: this.autocomplete,
|
||||||
selectTemplate: function (item) {
|
selectTemplate: function (item) {
|
||||||
return item
|
return ``;
|
||||||
? ``
|
|
||||||
: undefined;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
tribute.attach(this.renderRoot.getElementById('edit'));
|
tribute.attach(this.renderRoot.getElementById('edit'));
|
||||||
this._tribute = tribute;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updated() {
|
updated() {
|
||||||
super.updated();
|
super.updated();
|
||||||
let edit = this.renderRoot.getElementById('edit');
|
let edit = this.renderRoot.getElementById('edit');
|
||||||
if (this.last_updated_text !== edit.innerText) {
|
if (this.last_updated_text !== edit.value) {
|
||||||
let preview = this.renderRoot.getElementById('preview');
|
let preview = this.renderRoot.getElementById('preview');
|
||||||
preview.innerHTML = this.process_text(edit.innerText);
|
preview.innerHTML = this.process_text(edit.value);
|
||||||
this.last_updated_text = edit.innerText;
|
this.last_updated_text = edit.value;
|
||||||
}
|
}
|
||||||
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({
|
||||||
iframe: this.shadowRoot,
|
|
||||||
values: Object.entries(this.users).map((x) => ({
|
values: Object.entries(this.users).map((x) => ({
|
||||||
key: x[1].name,
|
key: x[1].name,
|
||||||
value: x[0],
|
value: x[0],
|
||||||
@@ -368,7 +333,8 @@ class TfComposeElement extends LitElement {
|
|||||||
remove_mention(id) {
|
remove_mention(id) {
|
||||||
let draft = this.get_draft();
|
let draft = this.get_draft();
|
||||||
delete draft.mentions[id];
|
delete draft.mentions[id];
|
||||||
setTimeout(() => this.notify(draft), 0);
|
this.notify(draft);
|
||||||
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
render_mention(mention) {
|
render_mention(mention) {
|
||||||
@@ -376,7 +342,7 @@ class TfComposeElement extends LitElement {
|
|||||||
return html` <div style="display: flex; flex-direction: row">
|
return html` <div style="display: flex; flex-direction: row">
|
||||||
<div style="align-self: center; margin: 0.5em">
|
<div style="align-self: center; margin: 0.5em">
|
||||||
<button
|
<button
|
||||||
class="w3-button w3-theme-d1"
|
class="w3-button w3-dark-grey"
|
||||||
title="Remove ${mention.name} mention"
|
title="Remove ${mention.name} mention"
|
||||||
@click=${() => self.remove_mention(mention.link)}
|
@click=${() => self.remove_mention(mention.link)}
|
||||||
>
|
>
|
||||||
@@ -430,16 +396,16 @@ class TfComposeElement extends LitElement {
|
|||||||
if (this.apps) {
|
if (this.apps) {
|
||||||
return html`
|
return html`
|
||||||
<div class="w3-card-4 w3-margin w3-padding">
|
<div class="w3-card-4 w3-margin w3-padding">
|
||||||
<select id="select" class="w3-select w3-theme-d1">
|
<select id="select" class="w3-select w3-dark-grey">
|
||||||
${Object.keys(self.apps).map(
|
${Object.keys(self.apps).map(
|
||||||
(app) => html`<option value=${app}>${app}</option>`
|
(app) => html`<option value=${app}>${app}</option>`
|
||||||
)}
|
)}
|
||||||
</select>
|
</select>
|
||||||
<button class="w3-button w3-theme-d1" @click=${attach_selected_app}>
|
<button class="w3-button w3-dark-grey" @click=${attach_selected_app}>
|
||||||
Attach
|
Attach
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="w3-button w3-theme-d1"
|
class="w3-button w3-dark-grey"
|
||||||
@click=${() => (this.apps = null)}
|
@click=${() => (this.apps = null)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
@@ -455,15 +421,12 @@ class TfComposeElement extends LitElement {
|
|||||||
self.apps = await tfrpc.rpc.apps();
|
self.apps = await tfrpc.rpc.apps();
|
||||||
}
|
}
|
||||||
if (!this.apps) {
|
if (!this.apps) {
|
||||||
return html`<button
|
return html`<button class="w3-button w3-dark-grey" @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-dark-grey"
|
||||||
@click=${() => (this.apps = null)}
|
@click=${() => (this.apps = null)}
|
||||||
>
|
>
|
||||||
Discard App
|
Discard App
|
||||||
@@ -484,38 +447,23 @@ 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">
|
||||||
<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>
|
<p>
|
||||||
|
<input type="checkbox" class="w3-check w3-dark-grey" id="cw" @change=${() => self.set_content_warning(undefined)} checked="checked"></input>
|
||||||
|
<label for="cw">CW</label>
|
||||||
|
</p>
|
||||||
|
<input type="text" class="w3-input w3-border w3-dark-grey" id="content_warning" placeholder="Enter a content warning here." @input=${this.input} @change=${this.change} value=${draft.content_warning}></input>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
render_new_thread() {
|
|
||||||
let self = this;
|
|
||||||
if (
|
|
||||||
this.root !== undefined &&
|
|
||||||
this.branch !== undefined &&
|
|
||||||
this.root != this.branch
|
|
||||||
) {
|
|
||||||
return html`
|
return html`
|
||||||
<input type="checkbox" class="w3-check w3-theme-d1" id="new_thread" @change=${() => (self.new_thread = !self.new_thread)} ?checked=${self.new_thread}></input>
|
<input type="checkbox" class="w3-check w3-dark-grey" id="cw" @change=${() => self.set_content_warning('')}></input>
|
||||||
<label for="new_thread">New Thread</label>
|
<label for="cw">CW</label>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,15 +485,15 @@ 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-dark-grey" @click=${() => this.set_encrypt(undefined)}>🚮</button>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
${draft.encrypt_to.map(
|
${draft.encrypt_to.map(
|
||||||
(x) => html`
|
(x) => html`
|
||||||
<li>
|
<li>
|
||||||
<tf-user id=${x} .users=${this.users}></tf-user>
|
<tf-user id=${x} .users=${this.users}></tf-user>
|
||||||
<input type="button" class="w3-button w3-theme-d1" value="🚮" @click=${() => this.set_encrypt(draft.encrypt_to.filter((id) => id != x))}></input>
|
<input type="button" class="w3-button w3-dark-grey" value="🚮" @click=${() => this.set_encrypt(draft.encrypt_to.filter((id) => id != x))}></input>
|
||||||
</li>`
|
</li>`
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -559,37 +507,12 @@ 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();
|
||||||
let content_warning =
|
let content_warning =
|
||||||
draft.content_warning !== undefined
|
draft.content_warning !== undefined
|
||||||
? html`<div class="w3-panel w3-round-xlarge w3-theme-d2">
|
? html`<div class="w3-panel w3-round-xlarge w3-blue">
|
||||||
<p id="content_warning_preview">${draft.content_warning}</p>
|
<p id="content_warning_preview">${draft.content_warning}</p>
|
||||||
</div>`
|
</div>`
|
||||||
: undefined;
|
: undefined;
|
||||||
@@ -597,103 +520,57 @@ 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-dark-grey"
|
||||||
@click=${() => this.set_encrypt([])}
|
@click=${() => this.set_encrypt([])}
|
||||||
>
|
>
|
||||||
🔐 Encrypt
|
🔐
|
||||||
</button>`;
|
</button>`;
|
||||||
let result = html`
|
let result = html`
|
||||||
<style>
|
|
||||||
${generate_theme()}
|
|
||||||
</style>
|
|
||||||
<style>
|
|
||||||
.w3-input:empty::before {
|
|
||||||
content: attr(placeholder);
|
|
||||||
}
|
|
||||||
.w3-input:empty:focus::before {
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div
|
<div
|
||||||
class="w3-card-4 w3-theme-d4 w3-padding w3-margin-top w3-margin-bottom"
|
class="w3-card-4 w3-blue-grey w3-padding"
|
||||||
style="box-sizing: border-box"
|
style="box-sizing: border-box"
|
||||||
>
|
>
|
||||||
<header class="w3-container">
|
|
||||||
${this.channel !== undefined
|
|
||||||
? html`<p>To #${this.channel}:</p>`
|
|
||||||
: undefined}
|
|
||||||
${this.render_encrypt()}
|
${this.render_encrypt()}
|
||||||
</header>
|
<div style="display: flex; flex-direction: row; width: 100%; gap: 4px">
|
||||||
<div class="w3-container" style="padding: 0 0 16px 0">
|
<div style="flex: 1 0 50%">
|
||||||
<div class="w3-half">
|
<p>
|
||||||
<span
|
<textarea
|
||||||
class="w3-input w3-theme-d1 w3-border"
|
class="w3-input w3-dark-grey w3-border"
|
||||||
style="resize: vertical; width: 100%; white-space: pre-wrap"
|
style="resize: vertical"
|
||||||
placeholder="Write a post here."
|
placeholder="Write a post here."
|
||||||
id="edit"
|
id="edit"
|
||||||
@input=${this.input}
|
@input=${this.input}
|
||||||
|
@change=${this.change}
|
||||||
@paste=${this.paste}
|
@paste=${this.paste}
|
||||||
contenteditable="plaintext-only"
|
>
|
||||||
.innerText=${live(draft.text ?? '')}
|
${draft.text}</textarea
|
||||||
></span>
|
>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w3-half w3-container">
|
<div style="flex: 1 0 50%">
|
||||||
${content_warning}
|
${content_warning}
|
||||||
<p id="preview"></p>
|
<div id="preview"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.values(draft.mentions || {}).map((x) =>
|
${Object.values(draft.mentions || {}).map((x) =>
|
||||||
self.render_mention(x)
|
self.render_mention(x)
|
||||||
)}
|
)}
|
||||||
<footer>
|
|
||||||
${this.render_attach_app()} ${this.render_content_warning()}
|
${this.render_attach_app()} ${this.render_content_warning()}
|
||||||
${this.render_new_thread()}
|
|
||||||
<button
|
<button
|
||||||
class="w3-button w3-theme-d1"
|
class="w3-button w3-dark-grey"
|
||||||
id="submit"
|
id="submit"
|
||||||
@click=${this.submit}
|
@click=${this.submit}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
<div class="w3-dropdown-click">
|
<button class="w3-button w3-dark-grey" @click=${this.attach}>
|
||||||
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
|
|
||||||
⚙️
|
|
||||||
</button>
|
|
||||||
<div class="w3-dropdown-content w3-bar-block">
|
|
||||||
${this.get_draft().content_warning === undefined
|
|
||||||
? html`
|
|
||||||
<button
|
|
||||||
class="w3-button w3-bar-item w3-theme-d1"
|
|
||||||
@click=${() => self.set_content_warning('')}
|
|
||||||
>
|
|
||||||
Add Content Warning
|
|
||||||
</button>
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<button
|
|
||||||
class="w3-button w3-bar-item w3-theme-d1"
|
|
||||||
@click=${() => self.set_content_warning(undefined)}
|
|
||||||
>
|
|
||||||
Remove Content Warning
|
|
||||||
</button>
|
|
||||||
`}
|
|
||||||
<button
|
|
||||||
class="w3-button w3-bar-item w3-theme-d1"
|
|
||||||
@click=${this.attach}
|
|
||||||
>
|
|
||||||
Attach
|
Attach
|
||||||
</button>
|
</button>
|
||||||
${this.render_attach_app_button()} ${encrypt}
|
${this.render_attach_app_button()} ${encrypt}
|
||||||
<button
|
<button class="w3-button w3-dark-grey" @click=${this.discard}>
|
||||||
class="w3-button w3-bar-item w3-theme-d1"
|
|
||||||
@click=${this.discard}
|
|
||||||
>
|
|
||||||
Discard
|
Discard
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
54
apps/ssb/tf-id-picker.js
Normal file
54
apps/ssb/tf-id-picker.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import {LitElement, html} from './lit-all.min.js';
|
||||||
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Provide a list of IDs, and this lets the user pick one.
|
||||||
|
*/
|
||||||
|
class TfIdentityPickerElement extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
ids: {type: Array},
|
||||||
|
selected: {type: String},
|
||||||
|
users: {type: Object},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = styles;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.ids = [];
|
||||||
|
this.users = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
changed(event) {
|
||||||
|
this.selected = event.srcElement.value;
|
||||||
|
this.dispatchEvent(
|
||||||
|
new Event('change', {
|
||||||
|
srcElement: this,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<select
|
||||||
|
class="w3-select w3-dark-grey w3-padding w3-border"
|
||||||
|
@change=${this.changed}
|
||||||
|
style="max-width: 100%; overflow: hidden"
|
||||||
|
>
|
||||||
|
${(this.ids ?? []).map(
|
||||||
|
(id) =>
|
||||||
|
html`<option ?selected=${id == this.selected} value=${id}>
|
||||||
|
${this.users[id]?.name
|
||||||
|
? this.users[id]?.name + ' - '
|
||||||
|
: undefined}<small>${id}</small>
|
||||||
|
</option>`
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('tf-id-picker', TfIdentityPickerElement);
|
||||||
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, 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() {
|
||||||
@@ -11,10 +11,6 @@ class TfNewsElement extends LitElement {
|
|||||||
following: {type: Array},
|
following: {type: Array},
|
||||||
drafts: {type: Object},
|
drafts: {type: Object},
|
||||||
expanded: {type: Object},
|
expanded: {type: Object},
|
||||||
channel: {type: String},
|
|
||||||
channel_unread: {type: Number},
|
|
||||||
recent_reactions: {type: Array},
|
|
||||||
hash: {type: String},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,8 +25,6 @@ class TfNewsElement extends LitElement {
|
|||||||
this.following = [];
|
this.following = [];
|
||||||
this.drafts = {};
|
this.drafts = {};
|
||||||
this.expanded = {};
|
this.expanded = {};
|
||||||
this.channel_unread = -1;
|
|
||||||
this.recent_reactions = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process_messages(messages) {
|
process_messages(messages) {
|
||||||
@@ -39,13 +33,12 @@ class TfNewsElement extends LitElement {
|
|||||||
|
|
||||||
console.log('processing', messages.length, 'messages');
|
console.log('processing', messages.length, 'messages');
|
||||||
|
|
||||||
function ensure_message(id, rowid) {
|
function ensure_message(id) {
|
||||||
let found = messages_by_id[id];
|
let found = messages_by_id[id];
|
||||||
if (found) {
|
if (found) {
|
||||||
return found;
|
return found;
|
||||||
} else {
|
} else {
|
||||||
let added = {
|
let added = {
|
||||||
rowid: rowid,
|
|
||||||
id: id,
|
id: id,
|
||||||
placeholder: true,
|
placeholder: true,
|
||||||
content: '"placeholder"',
|
content: '"placeholder"',
|
||||||
@@ -60,7 +53,7 @@ class TfNewsElement extends LitElement {
|
|||||||
|
|
||||||
function link_message(message) {
|
function link_message(message) {
|
||||||
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);
|
||||||
if (!parent.votes) {
|
if (!parent.votes) {
|
||||||
parent.votes = [];
|
parent.votes = [];
|
||||||
}
|
}
|
||||||
@@ -69,14 +62,14 @@ class TfNewsElement extends LitElement {
|
|||||||
} 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') {
|
||||||
let m = ensure_message(message.content.root, message.rowid);
|
let m = ensure_message(message.content.root);
|
||||||
if (!m.child_messages) {
|
if (!m.child_messages) {
|
||||||
m.child_messages = [];
|
m.child_messages = [];
|
||||||
}
|
}
|
||||||
m.child_messages.push(message);
|
m.child_messages.push(message);
|
||||||
message.parent_message = message.content.root;
|
message.parent_message = message.content.root;
|
||||||
} else {
|
} else {
|
||||||
let m = ensure_message(message.content.root[0], message.rowid);
|
let m = ensure_message(message.content.root[0]);
|
||||||
if (!m.child_messages) {
|
if (!m.child_messages) {
|
||||||
m.child_messages = [];
|
m.child_messages = [];
|
||||||
}
|
}
|
||||||
@@ -160,120 +153,43 @@ 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)),
|
type: 'contact_group',
|
||||||
type: `${type}_group`,
|
|
||||||
messages: group,
|
messages: group,
|
||||||
});
|
});
|
||||||
group = [];
|
group = [];
|
||||||
}
|
}
|
||||||
result.push(message);
|
result.push(message);
|
||||||
type = undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
if (this.unread_allowed()) {
|
|
||||||
for (let message of final_messages) {
|
|
||||||
if (message.rowid >= this.channel_unread) {
|
|
||||||
unread_rowid = message.rowid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<div style="display: flex; flex-direction: column">
|
||||||
${generate_theme()}
|
${final_messages.map(
|
||||||
</style>
|
(x) =>
|
||||||
<div>
|
html`<tf-message
|
||||||
${repeat(
|
|
||||||
final_messages,
|
|
||||||
(x) => x.id,
|
|
||||||
(x) => html`
|
|
||||||
<tf-message
|
|
||||||
.message=${x}
|
.message=${x}
|
||||||
whoami=${this.whoami}
|
whoami=${this.whoami}
|
||||||
.users=${this.users}
|
.users=${this.users}
|
||||||
.drafts=${this.drafts}
|
.drafts=${this.drafts}
|
||||||
.expanded=${this.expanded}
|
.expanded=${this.expanded}
|
||||||
collapsed="true"
|
collapsed="true"
|
||||||
channel=${this.channel}
|
></tf-message>`
|
||||||
channel_unread=${this.channel_unread}
|
|
||||||
.recent_reactions=${this.recent_reactions}
|
|
||||||
></tf-message>
|
|
||||||
${x.rowid == unread_rowid
|
|
||||||
? html`<div style="display: flex; flex-direction: row">
|
|
||||||
<div
|
|
||||||
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
|
|
||||||
></div>
|
|
||||||
<button
|
|
||||||
style="color: #f00; padding: 8px"
|
|
||||||
class="w3-button"
|
|
||||||
@click=${() =>
|
|
||||||
this.dispatchEvent(
|
|
||||||
new Event('mark_all_read', {
|
|
||||||
bubbles: true,
|
|
||||||
composed: true,
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
unread
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
|
|
||||||
></div>
|
|
||||||
</div>`
|
|
||||||
: undefined}
|
|
||||||
`
|
|
||||||
)}
|
)}
|
||||||
</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,9 @@ 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},
|
server_follows_me: {type: Boolean},
|
||||||
following: {type: Boolean},
|
following: {type: Boolean},
|
||||||
blocking: {type: Boolean},
|
blocking: {type: Boolean},
|
||||||
show_followed: {type: Boolean},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ class TfProfileElement extends LitElement {
|
|||||||
this.id = null;
|
this.id = null;
|
||||||
this.users = {};
|
this.users = {};
|
||||||
this.size = 0;
|
this.size = 0;
|
||||||
this.sequence = 0;
|
this.server_follows_me = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
@@ -37,22 +36,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,19 +54,36 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async initial_load() {
|
||||||
|
this.server_follows_me = undefined;
|
||||||
|
let server_id = await tfrpc.rpc.getServerIdentity();
|
||||||
|
let followed = await tfrpc.rpc.query(
|
||||||
|
`
|
||||||
|
SELECT json_extract(content, '$.following') AS following
|
||||||
|
FROM messages
|
||||||
|
WHERE author = ? AND
|
||||||
|
json_extract(content, '$.type') = 'contact' AND
|
||||||
|
json_extract(content, '$.contact') = ? ORDER BY sequence DESC LIMIT 1
|
||||||
|
`,
|
||||||
|
[server_id, this.whoami]
|
||||||
|
);
|
||||||
|
let is_followed = false;
|
||||||
|
for (let row of followed) {
|
||||||
|
is_followed = row.following != 0;
|
||||||
|
}
|
||||||
|
this.server_follows_me = is_followed;
|
||||||
|
}
|
||||||
|
|
||||||
modify(change) {
|
modify(change) {
|
||||||
let self = this;
|
|
||||||
tfrpc.rpc
|
tfrpc.rpc
|
||||||
.appendMessage(
|
.appendMessage(
|
||||||
this.whoami,
|
this.whoami,
|
||||||
@@ -85,10 +95,6 @@ class TfProfileElement extends LitElement {
|
|||||||
change
|
change
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.then(function () {
|
|
||||||
self._follow_whoami = undefined;
|
|
||||||
self.load();
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
alert(error?.message);
|
alert(error?.message);
|
||||||
});
|
});
|
||||||
@@ -150,8 +156,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,186 +171,126 @@ class TfProfileElement extends LitElement {
|
|||||||
.catch(function (e) {
|
.catch(function (e) {
|
||||||
alert(e.message);
|
alert(e.message);
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
document.body.appendChild(input);
|
|
||||||
input.click();
|
input.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
copy_id() {
|
async server_follow_me(follow) {
|
||||||
navigator.clipboard.writeText(this.id);
|
try {
|
||||||
|
await tfrpc.rpc.setServerFollowingMe(this.whoami, follow);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
show_image(link) {
|
await this.initial_load();
|
||||||
let div = document.createElement('div');
|
} catch (e) {
|
||||||
div.style.left = 0;
|
console.log(e);
|
||||||
div.style.top = 0;
|
|
||||||
div.style.width = '100%';
|
|
||||||
div.style.height = '100%';
|
|
||||||
div.style.position = 'fixed';
|
|
||||||
div.style.background = '#000';
|
|
||||||
div.style.zIndex = 100;
|
|
||||||
div.style.display = 'grid';
|
|
||||||
let img = document.createElement('img');
|
|
||||||
img.src = link;
|
|
||||||
img.style.maxWidth = '100vw';
|
|
||||||
img.style.maxHeight = '100vh';
|
|
||||||
img.style.display = 'block';
|
|
||||||
img.style.margin = 'auto';
|
|
||||||
img.style.objectFit = 'contain';
|
|
||||||
img.style.width = '100vw';
|
|
||||||
div.appendChild(img);
|
|
||||||
function image_close(event) {
|
|
||||||
document.body.removeChild(div);
|
|
||||||
window.removeEventListener('keydown', image_close);
|
|
||||||
}
|
}
|
||||||
div.onclick = image_close;
|
|
||||||
window.addEventListener('keydown', image_close);
|
|
||||||
document.body.appendChild(div);
|
|
||||||
}
|
|
||||||
|
|
||||||
body_click(event) {
|
|
||||||
if (event.srcElement.tagName == 'IMG') {
|
|
||||||
this.show_image(event.srcElement.src);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggle_account_list(event) {
|
|
||||||
let content = event.srcElement.nextElementSibling;
|
|
||||||
this.show_followed = !this.show_followed;
|
|
||||||
}
|
|
||||||
|
|
||||||
async load_follows() {
|
|
||||||
let accounts = await tfrpc.rpc.following([this.id], 1);
|
|
||||||
return html`
|
|
||||||
<div class="w3-container">
|
|
||||||
<button
|
|
||||||
class="w3-button w3-block w3-theme-d1 followed_accounts"
|
|
||||||
@click=${this.toggle_account_list}
|
|
||||||
>
|
|
||||||
${this.show_followed ? 'Hide' : 'Show'} Followed Accounts
|
|
||||||
(${Object.keys(accounts).length})
|
|
||||||
</button>
|
|
||||||
<div class=${'w3-card' + (this.show_followed ? '' : ' w3-hide')}>
|
|
||||||
<ul class="w3-ul w3-theme-d4 w3-border-theme">
|
|
||||||
${Object.keys(accounts).map(
|
|
||||||
(x) => html`
|
|
||||||
<li class="w3-border-theme">
|
|
||||||
<tf-user id=${x} .users=${this.users}></tf-user>
|
|
||||||
</li>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
if (
|
||||||
|
this.id == this.whoami &&
|
||||||
|
this.editing &&
|
||||||
|
this.server_follows_me === undefined
|
||||||
|
) {
|
||||||
|
this.initial_load();
|
||||||
|
}
|
||||||
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;
|
||||||
let block;
|
let block;
|
||||||
if (this.id === this.whoami) {
|
if (this.id === this.whoami) {
|
||||||
if (this.editing) {
|
if (this.editing) {
|
||||||
edit = html`
|
let server_follow;
|
||||||
<button
|
if (this.server_follows_me === true) {
|
||||||
id="save_profile"
|
server_follow = html`<button
|
||||||
class="w3-button w3-theme-d1"
|
class="w3-button w3-dark-grey"
|
||||||
@click=${this.save_edits}
|
@click=${() => this.server_follow_me(false)}
|
||||||
>
|
>
|
||||||
|
Server, Stop Following Me
|
||||||
|
</button>`;
|
||||||
|
} else if (this.server_follows_me === false) {
|
||||||
|
server_follow = html`<button
|
||||||
|
class="w3-button w3-dark-grey"
|
||||||
|
@click=${() => this.server_follow_me(true)}
|
||||||
|
>
|
||||||
|
Server, Follow Me
|
||||||
|
</button>`;
|
||||||
|
}
|
||||||
|
edit = html`
|
||||||
|
<button class="w3-button w3-dark-grey" @click=${this.save_edits}>
|
||||||
Save Profile
|
Save Profile
|
||||||
</button>
|
</button>
|
||||||
<button class="w3-button w3-theme-d1" @click=${this.discard_edits}>
|
<button class="w3-button w3-dark-grey" @click=${this.discard_edits}>
|
||||||
Discard
|
Discard
|
||||||
</button>
|
</button>
|
||||||
|
${server_follow}
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
edit = html`<button
|
edit = html`<button class="w3-button w3-dark-grey" @click=${this.edit}>
|
||||||
id="edit_profile"
|
|
||||||
class="w3-button w3-theme-d1"
|
|
||||||
@click=${this.edit}
|
|
||||||
>
|
|
||||||
Edit Profile
|
Edit Profile
|
||||||
</button>`;
|
</button>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.id !== this.whoami && this.following !== undefined) {
|
if (this.id !== this.whoami && this.following !== undefined) {
|
||||||
follow = this.following
|
follow = this.following
|
||||||
? html`<button class="w3-button w3-theme-d1" @click=${this.unfollow}>
|
? html`<button class="w3-button w3-dark-grey" @click=${this.unfollow}>
|
||||||
Unfollow
|
Unfollow
|
||||||
</button>`
|
</button>`
|
||||||
: html`<button class="w3-button w3-theme-d1" @click=${this.follow}>
|
: html`<button class="w3-button w3-dark-grey" @click=${this.follow}>
|
||||||
Follow
|
Follow
|
||||||
</button>`;
|
</button>`;
|
||||||
}
|
}
|
||||||
if (this.id !== this.whoami && this.blocking !== undefined) {
|
if (this.id !== this.whoami && this.blocking !== undefined) {
|
||||||
block = this.blocking
|
block = this.blocking
|
||||||
? html`<button class="w3-button w3-theme-d1" @click=${this.unblock}>
|
? html`<button class="w3-button w3-dark-grey" @click=${this.unblock}>
|
||||||
Unblock
|
Unblock
|
||||||
</button>`
|
</button>`
|
||||||
: html`<button class="w3-button w3-theme-d1" @click=${this.block}>
|
: html`<button class="w3-button w3-dark-grey" @click=${this.block}>
|
||||||
Block
|
Block
|
||||||
</button>`;
|
</button>`;
|
||||||
}
|
}
|
||||||
let edit_profile = this.editing
|
let edit_profile = this.editing
|
||||||
? html`
|
? html`
|
||||||
<div style="flex: 1 0 50%; display: flex; flex-direction: column; gap: 8px">
|
<div style="flex: 1 0 50%; display: flex; flex-direction: column; gap: 8px">
|
||||||
|
<div class="w3-container">
|
||||||
<div>
|
<div>
|
||||||
<label for="name">Name:</label>
|
<label for="name">Name:</label>
|
||||||
<input class="w3-input w3-theme-d1" type="text" id="name" value=${this.editing.name} @input=${(event) => (this.editing = Object.assign({}, this.editing, {name: event.srcElement.value}))} placeholder="Choose a name"></input>
|
<input class="w3-input w3-dark-grey" type="text" id="name" value=${this.editing.name} @input=${(event) => (this.editing = Object.assign({}, this.editing, {name: event.srcElement.value}))}></input>
|
||||||
</div>
|
</div>
|
||||||
<div><label for="description">Description:</label></div>
|
<div><label for="description">Description:</label></div>
|
||||||
<textarea class="w3-input w3-theme-d1" style="resize: vertical" rows="8" id="description" @input=${(event) => (this.editing = Object.assign({}, this.editing, {description: event.srcElement.value}))} placeholder="Tell people a little bit about yourself here, if you like.">${this.editing.description}</textarea>
|
<textarea class="w3-input w3-dark-grey" style="resize: vertical" rows="8" id="description" @input=${(event) => (this.editing = Object.assign({}, this.editing, {description: event.srcElement.value}))}>${this.editing.description}</textarea>
|
||||||
<div>
|
<div>
|
||||||
<label for="public_web_hosting">Public Web Hosting:</label>
|
<label for="public_web_hosting">Public Web Hosting:</label>
|
||||||
<input class="w3-check w3-theme-d1" type="checkbox" id="public_web_hosting" ?checked=${this.editing.publicWebHosting} @input=${(event) => (self.editing = Object.assign({}, self.editing, {publicWebHosting: event.srcElement.checked}))}></input>
|
<input class="w3-check w3-dark-grey" type="checkbox" id="public_web_hosting" ?checked=${this.editing.publicWebHosting} @input=${(event) => (self.editing = Object.assign({}, self.editing, {publicWebHosting: event.srcElement.checked}))}></input>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button class="w3-button w3-theme-d1" @click=${this.attach_image}>Attach Image</button>
|
<button class="w3-button w3-dark-grey" @click=${this.attach_image}>Attach Image</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
: 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 style="border: 2px solid black; background-color: rgba(255, 255, 255, 0.2); padding: 16px">
|
||||||
<style>${generate_theme()}</style>
|
<tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)})
|
||||||
<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box">
|
|
||||||
<header class="w3-container">
|
|
||||||
<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)} in ${this.sequence} messages)</p>
|
|
||||||
</header>
|
|
||||||
<div class="w3-container" @click=${this.body_click}>
|
|
||||||
<div class="w3-margin-bottom" style="display: flex; flex-direction: row">
|
|
||||||
<input type="text" class="w3-input w3-border w3-theme-d1" style="display: flex 1 1" readonly value=${this.id}></input>
|
|
||||||
<button class="w3-button w3-theme-d1 w3-ripple" style="flex: 0 0 auto" @click=${this.copy_id}>Copy</button>
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; flex-direction: row; gap: 1em">
|
<div style="display: flex; flex-direction: row; gap: 1em">
|
||||||
${edit_profile}
|
${edit_profile}
|
||||||
<div style="flex: 1 0 50%; contain: layout; overflow: auto; word-wrap: normal; word-break: normal">
|
<div style="flex: 1 0 50%">
|
||||||
${
|
<div><img src=${'/' + image + '/view'} style="width: 256px; height: auto"></img></div>
|
||||||
image
|
|
||||||
? html`<div><img src=${'/' + image + '/view'} style="width: 256px; height: auto"></img></div>`
|
|
||||||
: html`<div>
|
|
||||||
<div class="w3-jumbo">😎</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>
|
</div>
|
||||||
@@ -355,18 +300,11 @@ class TfProfileElement extends LitElement {
|
|||||||
Blocking ${profile.blocking} identities.
|
Blocking ${profile.blocking} identities.
|
||||||
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">
|
|
||||||
<p>
|
|
||||||
<a class="w3-button w3-theme-d1" href=${'#🔐' + (this.id != this.whoami ? this.id : '')}>
|
|
||||||
Open Private Chat
|
|
||||||
</a>
|
|
||||||
${edit}
|
${edit}
|
||||||
${follow}
|
${follow}
|
||||||
${block}
|
${block}
|
||||||
</p>
|
</div>
|
||||||
</footer>
|
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
|
||||||
import {styles, generate_theme} from './tf-styles.js';
|
|
||||||
|
|
||||||
class TfReactionsModalElement extends LitElement {
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
users: {type: Object},
|
|
||||||
votes: {type: Array},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = styles;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.votes = [];
|
|
||||||
this.users = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this.votes = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let self = this;
|
|
||||||
return this.votes?.length
|
|
||||||
? html` <style>
|
|
||||||
${generate_theme()}
|
|
||||||
</style>
|
|
||||||
<div
|
|
||||||
class="w3-modal w3-animate-opacity"
|
|
||||||
style="display: block; box-sizing: border-box; z-index: 10"
|
|
||||||
@click=${this.clear}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="w3-modal-content w3-card-4 w3-theme-d1"
|
|
||||||
onclick="event.stopPropagation()"
|
|
||||||
>
|
|
||||||
<div class="w3-container w3-padding">
|
|
||||||
<header class="w3-container">
|
|
||||||
<h2>Reactions</h2>
|
|
||||||
<span
|
|
||||||
class="w3-button w3-display-topright"
|
|
||||||
@click=${this.clear}
|
|
||||||
>×</span
|
|
||||||
>
|
|
||||||
</header>
|
|
||||||
<ul class="w3-theme-dark w3-container w3-ul">
|
|
||||||
${this.votes
|
|
||||||
.sort((x, y) => y.timestamp - x.timestamp)
|
|
||||||
.map(
|
|
||||||
(x) => html`
|
|
||||||
<li
|
|
||||||
style="display: flex; flex-direction: row; gap: 4px"
|
|
||||||
>
|
|
||||||
<span style="flex-basis: 3em"
|
|
||||||
>${x?.content?.vote?.expression}</span
|
|
||||||
>
|
|
||||||
<tf-user
|
|
||||||
style="flex: 1 1; overflow: hidden"
|
|
||||||
id=${x.author}
|
|
||||||
.users=${this.users}
|
|
||||||
></tf-user>
|
|
||||||
<span
|
|
||||||
style="flex-shrink: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
|
|
||||||
>${new Date(x?.timestamp).toLocaleString()}</span
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
<footer class="w3-container w3-padding">
|
|
||||||
<button class="w3-button" @click=${this.clear}>Close</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define('tf-reactions-modal', TfReactionsModalElement);
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,76 +1,41 @@
|
|||||||
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() {
|
||||||
return {
|
return {
|
||||||
broadcasts: {type: Array},
|
broadcasts: {type: Array},
|
||||||
identities: {type: Array},
|
identities: {type: Array},
|
||||||
my_identities: {type: Array},
|
|
||||||
connections: {type: Array},
|
connections: {type: Array},
|
||||||
stored_connections: {type: Array},
|
stored_connections: {type: Array},
|
||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
server_identity: {type: String},
|
|
||||||
connect_attempt: {type: Object},
|
|
||||||
connect_message: {type: String},
|
|
||||||
connect_success: {type: Boolean},
|
|
||||||
peer_exchange: {type: Boolean},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = styles;
|
static styles = styles;
|
||||||
|
|
||||||
static k_broadcast_emojis = {
|
|
||||||
discovery: '🏓',
|
|
||||||
room: '🚪',
|
|
||||||
peer_exchange: '🕸',
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
let self = this;
|
let self = this;
|
||||||
this.broadcasts = [];
|
this.broadcasts = [];
|
||||||
this.identities = [];
|
this.identities = [];
|
||||||
this.my_identities = [];
|
|
||||||
this.connections = [];
|
this.connections = [];
|
||||||
this.stored_connections = [];
|
this.stored_connections = [];
|
||||||
this.users = {};
|
this.users = {};
|
||||||
tfrpc.rpc.getIdentities().then(function (identities) {
|
|
||||||
self.my_identities = identities || [];
|
|
||||||
});
|
|
||||||
tfrpc.rpc.getAllIdentities().then(function (identities) {
|
tfrpc.rpc.getAllIdentities().then(function (identities) {
|
||||||
self.identities = identities || [];
|
self.identities = identities || [];
|
||||||
});
|
});
|
||||||
tfrpc.rpc.getStoredConnections().then(function (connections) {
|
tfrpc.rpc.getStoredConnections().then(function (connections) {
|
||||||
self.stored_connections = connections || [];
|
self.stored_connections = connections || [];
|
||||||
});
|
});
|
||||||
tfrpc.rpc.getServerIdentity().then(function (identity) {
|
|
||||||
self.server_identity = identity;
|
|
||||||
});
|
|
||||||
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) {
|
||||||
if (connection.address && connection.port) {
|
if (connection.address && connection.port) {
|
||||||
return html`<div>
|
return html`(<small>${connection.address}:${connection.port}</small>)`;
|
||||||
<small>${connection.address}:${connection.port}</small>
|
|
||||||
</div>`;
|
|
||||||
} else if (connection.tunnel) {
|
} else if (connection.tunnel) {
|
||||||
return html`<div>room peer</div>`;
|
return html`(room peer)`;
|
||||||
} else {
|
} else {
|
||||||
return JSON.stringify(connection);
|
return JSON.stringify(connection);
|
||||||
}
|
}
|
||||||
@@ -96,7 +61,7 @@ class TfTabConnectionsElement extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="w3-button w3-theme-d1"
|
class="w3-button w3-dark-grey"
|
||||||
@click=${() => self._tunnel(connection.tunnel.id, connection.pubkey)}
|
@click=${() => self._tunnel(connection.tunnel.id, connection.pubkey)}
|
||||||
>
|
>
|
||||||
Connect
|
Connect
|
||||||
@@ -106,53 +71,17 @@ class TfTabConnectionsElement extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
render_message(connection) {
|
|
||||||
return html`<div
|
|
||||||
?hidden=${this.connect_message === undefined ||
|
|
||||||
this.connect_attempt != connection}
|
|
||||||
style="cursor: pointer"
|
|
||||||
class=${'w3-panel ' + (this.connect_success ? 'w3-green' : 'w3-red')}
|
|
||||||
@click=${() => (this.connect_attempt = undefined)}
|
|
||||||
>
|
|
||||||
<p>${this.connect_message}</p>
|
|
||||||
</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;
|
|
||||||
return html`
|
return html`
|
||||||
<li>
|
<li>
|
||||||
<div class="w3-bar" style="overflow: hidden; overflow-wrap: nowrap">
|
|
||||||
<button
|
<button
|
||||||
class="w3-bar-item w3-button w3-theme-d1"
|
class="w3-button w3-dark-grey"
|
||||||
@click=${() => self.connect(connection)}
|
@click=${() => tfrpc.rpc.connect(connection)}
|
||||||
>
|
>
|
||||||
Connect
|
Connect
|
||||||
</button>
|
</button>
|
||||||
<div class="w3-bar-item">
|
|
||||||
${TfTabConnectionsElement.k_broadcast_emojis[connection.origin]}
|
|
||||||
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
|
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
|
||||||
${this.render_connection_summary(connection)}
|
${this.render_connection_summary(connection)}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
${this.render_message(connection)}
|
|
||||||
</li>
|
</li>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -163,225 +92,81 @@ class TfTabConnectionsElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render_connection(connection) {
|
render_connection(connection) {
|
||||||
let requests = Object.values(
|
|
||||||
connection.requests.reduce(function (accumulator, value) {
|
|
||||||
let key = `${value.name}:${Math.sign(value.request_number)}`;
|
|
||||||
if (!accumulator[key]) {
|
|
||||||
accumulator[key] = Object.assign({count: 0}, value);
|
|
||||||
}
|
|
||||||
accumulator[key].count++;
|
|
||||||
return accumulator;
|
|
||||||
}, {})
|
|
||||||
);
|
|
||||||
return html`
|
return html`
|
||||||
${connection.connected
|
|
||||||
? html`
|
|
||||||
<button
|
<button
|
||||||
class="w3-button w3-theme-d1"
|
class="w3-button w3-dark-grey"
|
||||||
@click=${() => tfrpc.rpc.closeConnection(connection.id)}
|
@click=${() => tfrpc.rpc.closeConnection(connection.id)}
|
||||||
>
|
>
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
`
|
|
||||||
: 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})`}
|
||||||
<div>
|
|
||||||
${requests.map(
|
|
||||||
(x) => html`
|
|
||||||
<span
|
|
||||||
class=${'w3-tag w3-small ' +
|
|
||||||
(x.active ? 'w3-theme-l3' : 'w3-theme-d3')}
|
|
||||||
>${x.request_number > 0 ? '🟩' : '🟥'} ${x.name}
|
|
||||||
<span
|
|
||||||
class="w3-badge w3-white"
|
|
||||||
style=${x.count > 1 ? undefined : 'display: none'}
|
|
||||||
>${x.count}</span
|
|
||||||
></span
|
|
||||||
>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<ul>
|
<ul>
|
||||||
${this.connections
|
${this.connections
|
||||||
.filter((x) => x.tunnel === this.connections.indexOf(connection))
|
.filter((x) => x.tunnel === this.connections.indexOf(connection))
|
||||||
.map((x) => html`<li>${this.render_connection(x)}</li>`)}
|
.map((x) => html`<li>${this.render_connection(x)}</li>`)}
|
||||||
${this.render_room_peers(connection.id)}
|
${this.render_room_peers(connection.id)}
|
||||||
</ul>
|
</ul>
|
||||||
<div ?hidden=${!connection.destroy_reason} class="w3-panel w3-red">
|
|
||||||
<p>${connection.destroy_reason}</p>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(address) {
|
|
||||||
let self = this;
|
|
||||||
self.connect_attempt = address;
|
|
||||||
self.connect_message = undefined;
|
|
||||||
self.connect_success = false;
|
|
||||||
tfrpc.rpc
|
|
||||||
.connect(address)
|
|
||||||
.then(function () {
|
|
||||||
if (self.connect_attempt == address) {
|
|
||||||
self.connect_message = 'Connected.';
|
|
||||||
self.connect_success = true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
if (self.connect_attempt == address) {
|
|
||||||
self.connect_message = 'Error: ' + error;
|
|
||||||
self.connect_success = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
<div class="w3-container">
|
||||||
${generate_theme()}
|
|
||||||
</style>
|
|
||||||
<div class="w3-container" style="box-sizing: border-box">
|
|
||||||
<div
|
|
||||||
class=${'w3-panel w3-padding w3-theme-l3' +
|
|
||||||
(this.peer_exchange !== false ? ' w3-hide' : '')}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
Looking for connections? Enabling this option will include publicly
|
|
||||||
advertised rooms and pubs among the list of discovered connections
|
|
||||||
to help you replicate.
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
class="w3-button w3-theme-d1"
|
|
||||||
@click=${this.enable_peer_exchange}
|
|
||||||
>
|
|
||||||
🔍🌐 Use publicly advertised peers
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<h2>New Connection</h2>
|
<h2>New Connection</h2>
|
||||||
<textarea class="w3-input w3-theme-d1" id="code"></textarea>
|
<textarea class="w3-input w3-dark-grey" id="code"></textarea>
|
||||||
${this.render_message(this.renderRoot.getElementById('code')?.value)}
|
|
||||||
<button
|
<button
|
||||||
class="w3-button w3-theme-d1"
|
class="w3-button w3-dark-grey"
|
||||||
@click=${() =>
|
@click=${() =>
|
||||||
self.connect(self.renderRoot.getElementById('code')?.value)}
|
tfrpc.rpc.connect(self.renderRoot.getElementById('code').value)}
|
||||||
>
|
>
|
||||||
Connect
|
Connect
|
||||||
</button>
|
</button>
|
||||||
<h2
|
<h2>Broadcasts</h2>
|
||||||
class="w3-button w3-block w3-theme-d1"
|
<ul>
|
||||||
@click=${() => self.toggle_accordian('connections')}
|
${this.broadcasts
|
||||||
>
|
.filter((x) => x.address)
|
||||||
Connections (${this.valid_connections().length})
|
.map((x) => self.render_broadcast(x))}
|
||||||
</h2>
|
|
||||||
<ul class="w3-ul w3-border" id="connections">
|
|
||||||
${this.valid_connections().map(
|
|
||||||
(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>
|
||||||
@click=${() => self.toggle_accordian('broadcasts')}
|
${this.connections
|
||||||
>
|
.filter((x) => x.tunnel === undefined)
|
||||||
Discovery (${this.valid_broadcasts().length})
|
.map((x) => html` <li>${this.render_connection(x)}</li> `)}
|
||||||
</h2>
|
|
||||||
<ul class="w3-ul w3-border w3-hide" id="broadcasts">
|
|
||||||
${this.valid_broadcasts().map((x) => self.render_broadcast(x))}
|
|
||||||
</ul>
|
</ul>
|
||||||
<h2
|
<h2>Stored Connections (WIP)</h2>
|
||||||
class="w3-button w3-block w3-theme-d1"
|
<ul>
|
||||||
@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>
|
||||||
<div class="w3-bar">
|
|
||||||
<button
|
<button
|
||||||
class="w3-bar-item w3-button w3-theme-d1"
|
class="w3-button w3-dark-grey"
|
||||||
@click=${() => self.forget_stored_connection(x)}
|
@click=${() => self.forget_stored_connection(x)}
|
||||||
>
|
>
|
||||||
Forget
|
Forget
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="w3-bar-item w3-button w3-theme-d1"
|
class="w3-button w3-dark-grey"
|
||||||
@click=${() => this.connect(x)}
|
@click=${() => tfrpc.rpc.connect(x)}
|
||||||
>
|
>
|
||||||
Connect
|
Connect
|
||||||
</button>
|
</button>
|
||||||
<div class="w3-bar-item">
|
${x.address}:${x.port}
|
||||||
<tf-user id=${x.pubkey} .users=${self.users}></tf-user>
|
<tf-user id=${x.pubkey} .users=${self.users}></tf-user>
|
||||||
<div><small>${x.address}:${x.port}</small></div>
|
|
||||||
<div>
|
|
||||||
<small
|
|
||||||
>Last connection:
|
|
||||||
${new Date(x.last_success * 1000)}</small
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
${this.render_message(x)}
|
|
||||||
</li>
|
</li>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
<h2
|
<h2>Local Accounts</h2>
|
||||||
class="w3-button w3-block w3-theme-d1"
|
<ul>
|
||||||
@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`<li><tf-user id=${x} .users=${this.users}></tf-user></li>`
|
||||||
class="w3-tag w3-round w3-theme-l3"
|
|
||||||
style="padding: 4px; margin: 2px; max-width: 100%; text-wrap: nowrap; overflow: hidden"
|
|
||||||
>
|
|
||||||
${x == this.server_identity
|
|
||||||
? html`<div class="w3-tag w3-medium w3-round w3-theme-l1">
|
|
||||||
🖥 local server
|
|
||||||
</div>`
|
|
||||||
: undefined}
|
|
||||||
${this.my_identities.indexOf(x) != -1
|
|
||||||
? html`<div class="w3-tag w3-medium w3-round w3-theme-d1">
|
|
||||||
😎 you
|
|
||||||
</div>`
|
|
||||||
: undefined}
|
|
||||||
<tf-user id=${x} .users=${this.users}></tf-user>
|
|
||||||
</div>`
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
78
apps/ssb/tf-tab-mentions.js
Normal file
78
apps/ssb/tf-tab-mentions.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||||
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
|
class TfTabMentionsElement extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
whoami: {type: String},
|
||||||
|
users: {type: Object},
|
||||||
|
following: {type: Array},
|
||||||
|
expanded: {type: Object},
|
||||||
|
messages: {type: Array},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = styles;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
let self = this;
|
||||||
|
this.whoami = null;
|
||||||
|
this.users = {};
|
||||||
|
this.following = [];
|
||||||
|
this.expanded = {};
|
||||||
|
this.messages = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
console.log('Loading...', this.whoami);
|
||||||
|
let results = await tfrpc.rpc.query(
|
||||||
|
`
|
||||||
|
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
|
FROM messages_fts(?)
|
||||||
|
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||||
|
JOIN json_each(?) AS following ON messages.author = following.value
|
||||||
|
WHERE messages.author != ?
|
||||||
|
ORDER BY timestamp DESC limit 20
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
'"' + this.whoami.replace('"', '""') + '"',
|
||||||
|
JSON.stringify(this.following),
|
||||||
|
this.whoami,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
console.log('Done.');
|
||||||
|
this.messages = results;
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
let self = this;
|
||||||
|
if (!this.loading) {
|
||||||
|
this.loading = true;
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<tf-news
|
||||||
|
id="news"
|
||||||
|
whoami=${this.whoami}
|
||||||
|
.messages=${this.messages}
|
||||||
|
.users=${this.users}
|
||||||
|
.expanded=${this.expanded}
|
||||||
|
@tf-expand=${this.on_expand}
|
||||||
|
></tf-news>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define('tf-tab-mentions', TfTabMentionsElement);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import {LitElement, cache, html, unsafeHTML, until} from './lit-all.min.js';
|
import {LitElement, 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() {
|
||||||
@@ -12,14 +12,6 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
messages: {type: Array},
|
messages: {type: Array},
|
||||||
drafts: {type: Object},
|
drafts: {type: Object},
|
||||||
expanded: {type: Object},
|
expanded: {type: Object},
|
||||||
channels_unread: {type: Object},
|
|
||||||
channels_latest: {type: Object},
|
|
||||||
loading: {type: Number},
|
|
||||||
time_range: {type: Array},
|
|
||||||
time_loading: {type: Array},
|
|
||||||
private_messages: {type: Array},
|
|
||||||
grouped_private_messages: {type: Object},
|
|
||||||
recent_reactions: {type: Array},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,296 +26,112 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
this.following = [];
|
this.following = [];
|
||||||
this.drafts = {};
|
this.drafts = {};
|
||||||
this.expanded = {};
|
this.expanded = {};
|
||||||
this.channels_unread = {};
|
this.start_time = new Date().valueOf() - 24 * 60 * 60 * 1000;
|
||||||
this.channels_latest = {};
|
|
||||||
this.start_time = new Date().valueOf();
|
|
||||||
this.time_range = [0, 0];
|
|
||||||
this.time_loading = undefined;
|
|
||||||
this.recent_reactions = [];
|
|
||||||
this.loading = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
channel() {
|
async fetch_messages() {
|
||||||
return this.hash.startsWith('##')
|
if (this.hash.startsWith('#@')) {
|
||||||
? this.hash.substring(2)
|
let r = await tfrpc.rpc.query(
|
||||||
: this.hash.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _fetch_related_messages(messages) {
|
|
||||||
let refs = await tfrpc.rpc.query(
|
|
||||||
`
|
`
|
||||||
WITH
|
WITH mine AS (SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||||
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
|
FROM messages
|
||||||
JOIN json_each(?2) refs ON messages.id = refs.value
|
WHERE messages.author = ?
|
||||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
ORDER BY sequence DESC
|
||||||
`,
|
LIMIT 20)
|
||||||
[JSON.stringify(this.following), JSON.stringify(refs.map((x) => x.ref))]
|
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
);
|
FROM mine
|
||||||
let combined = [].concat(messages, related_messages);
|
JOIN messages_refs ON mine.id = messages_refs.ref
|
||||||
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 t0 = new Date();
|
|
||||||
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)),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let t1 = new Date();
|
|
||||||
console.log((t1 - t0) / 1000);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetch_messages(start_time, end_time) {
|
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent('loadmessages', {
|
|
||||||
bubbles: true,
|
|
||||||
composed: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
this.time_loading = [start_time, end_time];
|
|
||||||
let result;
|
|
||||||
const k_max_results = 64;
|
|
||||||
if (this.hash == '#@') {
|
|
||||||
result = await tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
WITH mentions AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
|
||||||
FROM messages_fts(?1)
|
|
||||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
|
||||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
|
||||||
WHERE
|
|
||||||
messages.author != ?1 AND
|
|
||||||
(?3 IS NULL OR messages.timestamp >= ?3) AND messages.timestamp < ?4
|
|
||||||
ORDER BY timestamp DESC limit ?5)
|
|
||||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
|
||||||
FROM mentions
|
|
||||||
JOIN messages_refs ON mentions.id = messages_refs.ref
|
|
||||||
JOIN messages ON messages_refs.message = messages.id
|
JOIN messages ON messages_refs.message = messages.id
|
||||||
UNION
|
UNION
|
||||||
SELECT TRUE AS is_primary, * FROM mentions
|
SELECT * FROM mine
|
||||||
`,
|
`,
|
||||||
[
|
[this.hash.substring(1)]
|
||||||
'"' + this.whoami.replace('"', '""') + '"',
|
|
||||||
JSON.stringify(this.following),
|
|
||||||
start_time,
|
|
||||||
end_time,
|
|
||||||
k_max_results,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
} else if (this.hash.startsWith('#@')) {
|
|
||||||
result = await tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
WITH
|
|
||||||
selected AS (SELECT rowid, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
|
||||||
FROM messages
|
|
||||||
WHERE messages.author = ?1 AND (?2 IS NULL OR messages.timestamp >= 2) AND messages.timestamp < ?3
|
|
||||||
ORDER BY sequence DESC LIMIT ?4
|
|
||||||
)
|
|
||||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
|
||||||
FROM selected
|
|
||||||
JOIN messages_refs ON selected.id = messages_refs.ref
|
|
||||||
JOIN messages ON messages_refs.message = messages.id
|
|
||||||
UNION
|
|
||||||
SELECT TRUE AS is_primary, * FROM selected
|
|
||||||
`,
|
|
||||||
[this.hash.substring(1), start_time, end_time, k_max_results]
|
|
||||||
);
|
);
|
||||||
|
return r;
|
||||||
} else if (this.hash.startsWith('#%')) {
|
} else if (this.hash.startsWith('#%')) {
|
||||||
result = await tfrpc.rpc.query(
|
return await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
SELECT TRUE AS is_primary, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE messages.id = ?1
|
WHERE id = ?1
|
||||||
UNION
|
UNION
|
||||||
SELECT FALSE AS is_primary, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||||
FROM messages JOIN messages_refs
|
FROM messages JOIN messages_refs
|
||||||
ON messages.id = messages_refs.message
|
ON messages.id = messages_refs.message
|
||||||
WHERE messages_refs.ref = ?1
|
WHERE messages_refs.ref = ?1
|
||||||
`,
|
`,
|
||||||
[this.hash.substring(1)]
|
[this.hash.substring(1)]
|
||||||
);
|
);
|
||||||
} else if (this.hash.startsWith('##')) {
|
} else {
|
||||||
let initial_messages = await tfrpc.rpc.query(
|
let promises = [];
|
||||||
|
const k_following_limit = 256;
|
||||||
|
for (let i = 0; i < this.following.length; i += k_following_limit) {
|
||||||
|
promises.push(
|
||||||
|
tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
WITH
|
WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
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
|
|
||||||
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.timestamp > ? AND messages.timestamp < ?
|
||||||
|
ORDER BY messages.timestamp DESC)
|
||||||
|
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
|
FROM news
|
||||||
|
JOIN messages_refs ON news.id = messages_refs.ref
|
||||||
|
JOIN messages ON messages_refs.message = messages.id
|
||||||
UNION
|
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.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
FROM messages_refs
|
FROM news
|
||||||
JOIN messages ON messages.id = messages_refs.message
|
JOIN messages_refs ON news.id = messages_refs.message
|
||||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
JOIN messages ON messages_refs.ref = messages.id
|
||||||
WHERE messages_refs.ref = '#' || ?4 AND messages.content ->> 'type' != 'vote'
|
UNION
|
||||||
|
SELECT news.* FROM news
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
JSON.stringify(this.following.slice(i, i + k_following_limit)),
|
||||||
|
this.start_time,
|
||||||
|
/*
|
||||||
|
** Don't show messages more than a day into the future to prevent
|
||||||
|
** messages with far-future timestamps from staying at the top forever.
|
||||||
|
*/
|
||||||
|
new Date().valueOf() + 24 * 60 * 60 * 1000,
|
||||||
|
]
|
||||||
)
|
)
|
||||||
SELECT TRUE AS is_primary, all_news.* FROM all_news
|
|
||||||
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
|
|
||||||
ORDER BY all_news.timestamp DESC LIMIT ?5
|
|
||||||
`,
|
|
||||||
[
|
|
||||||
JSON.stringify(this.following),
|
|
||||||
start_time,
|
|
||||||
end_time,
|
|
||||||
this.hash.substring(2),
|
|
||||||
k_max_results,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
result = await this._fetch_related_messages(initial_messages);
|
|
||||||
} else if (this.hash.startsWith('#🔐')) {
|
|
||||||
let ids =
|
|
||||||
this.hash == '#🔐' ? [] : this.hash.substring('#🔐'.length).split(',');
|
|
||||||
result = await tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
|
||||||
FROM messages
|
|
||||||
JOIN json_each(?1) AS private_messages ON messages.id = private_messages.value
|
|
||||||
WHERE
|
|
||||||
(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND
|
|
||||||
json(messages.content) LIKE '"%'
|
|
||||||
ORDER BY messages.rowid DESC LIMIT ?4
|
|
||||||
`,
|
|
||||||
[
|
|
||||||
JSON.stringify(
|
|
||||||
this.grouped_private_messages?.[JSON.stringify(ids)]?.map(
|
|
||||||
(x) => x.id
|
|
||||||
) ?? []
|
|
||||||
),
|
|
||||||
start_time,
|
|
||||||
end_time,
|
|
||||||
k_max_results,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
result = (await this.decrypt(result)).filter((x) => x.decrypted);
|
|
||||||
} else if (this.hash == '#👍') {
|
|
||||||
result = await tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
WITH votes AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
|
||||||
FROM messages
|
|
||||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
|
||||||
WHERE
|
|
||||||
messages.content ->> 'type' = 'vote' AND
|
|
||||||
(?2 IS NULL OR messages.timestamp >= ?2) AND messages.timestamp < ?3
|
|
||||||
ORDER BY timestamp DESC limit ?4)
|
|
||||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
|
||||||
FROM votes
|
|
||||||
JOIN messages ON messages.id = votes.content ->> '$.vote.link'
|
|
||||||
UNION
|
|
||||||
SELECT TRUE AS is_primary, * FROM votes
|
|
||||||
`,
|
|
||||||
[JSON.stringify(this.following), start_time, end_time, k_max_results]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let initial_messages = await tfrpc.rpc.query(
|
|
||||||
`
|
|
||||||
WITH
|
|
||||||
channels AS (SELECT '#' || value AS value FROM json_each(?5))
|
|
||||||
SELECT TRUE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
|
||||||
FROM messages
|
|
||||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
|
||||||
WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
|
|
||||||
messages.content ->> 'type' != 'vote' AND
|
|
||||||
(messages.content ->> 'root' IS NULL OR (
|
|
||||||
NOT EXISTS (SELECT * FROM messages root JOIN channels ON ('#' || (root.content ->> 'channel')) = channels.value WHERE root.id = messages.content ->> 'root') AND
|
|
||||||
NOT EXISTS (SELECT * FROM messages root JOIN messages_refs ON root.id = messages.content ->> 'root' JOIN channels ON messages_refs.message = root.id AND messages_refs.ref = channels.value)
|
|
||||||
)) AND
|
|
||||||
(messages.content ->> 'channel' IS NULL OR ('#' || (messages.content ->> 'channel')) NOT IN (SELECT * FROM channels)) AND
|
|
||||||
NOT EXISTS (SELECT * FROM messages_refs JOIN channels ON messages_refs.message = messages.id AND messages_refs.ref = channels.value)
|
|
||||||
ORDER BY timestamp DESC LIMIT ?4
|
|
||||||
`,
|
|
||||||
[
|
|
||||||
JSON.stringify(this.following),
|
|
||||||
start_time,
|
|
||||||
end_time,
|
|
||||||
k_max_results,
|
|
||||||
JSON.stringify(Object.keys(this.channels_latest)),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
result = await this._fetch_related_messages(initial_messages);
|
|
||||||
}
|
}
|
||||||
this.time_loading = undefined;
|
return [].concat(...(await Promise.all(promises)));
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update_time_range_from_messages(messages) {
|
|
||||||
let only_primary = messages.filter((x) => x.is_primary);
|
|
||||||
this.time_range = [
|
|
||||||
only_primary.reduce(
|
|
||||||
(accumulator, current) => Math.min(accumulator, current.timestamp),
|
|
||||||
this.time_range[0]
|
|
||||||
),
|
|
||||||
only_primary.reduce(
|
|
||||||
(accumulator, current) => Math.max(accumulator, current.timestamp),
|
|
||||||
this.time_range[1]
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
unread_allowed() {
|
|
||||||
return (
|
|
||||||
this.hash == '#@' ||
|
|
||||||
(!this.hash.startsWith('#%') && !this.hash.startsWith('#@'))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async load_more() {
|
async load_more() {
|
||||||
this.loading++;
|
let last_start_time = this.start_time;
|
||||||
this.loading_canceled = false;
|
this.start_time = last_start_time - 24 * 60 * 60 * 1000;
|
||||||
try {
|
let more = await tfrpc.rpc.query(
|
||||||
let more = [];
|
`
|
||||||
let last_start_time = this.time_range[0];
|
WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
try {
|
FROM messages
|
||||||
more = await this.fetch_messages(null, last_start_time);
|
JOIN json_each(?) AS following ON messages.author = following.value
|
||||||
} catch (e) {
|
WHERE messages.timestamp > ?
|
||||||
console.log(e);
|
AND messages.timestamp <= ?
|
||||||
}
|
ORDER BY messages.timestamp DESC)
|
||||||
this.update_time_range_from_messages(
|
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
more.filter((x) => x.timestamp < last_start_time)
|
FROM news
|
||||||
|
JOIN messages_refs ON news.id = messages_refs.ref
|
||||||
|
JOIN messages ON messages_refs.message = messages.id
|
||||||
|
UNION
|
||||||
|
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
|
FROM news
|
||||||
|
JOIN messages_refs ON news.id = messages_refs.message
|
||||||
|
JOIN messages ON messages_refs.ref = messages.id
|
||||||
|
UNION
|
||||||
|
SELECT news.* FROM news
|
||||||
|
`,
|
||||||
|
[JSON.stringify(this.following), this.start_time, last_start_time]
|
||||||
);
|
);
|
||||||
this.messages = await this.decrypt([...more, ...this.messages]);
|
this.messages = await this.decrypt([...more, ...this.messages]);
|
||||||
} finally {
|
|
||||||
this.loading--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel_load() {
|
|
||||||
this.loading_canceled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async decrypt(messages) {
|
async decrypt(messages) {
|
||||||
|
console.log('decrypt');
|
||||||
let result = [];
|
let result = [];
|
||||||
for (let message of messages) {
|
for (let message of messages) {
|
||||||
let content;
|
let content;
|
||||||
@@ -348,203 +156,44 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
merge_messages(old_messages, new_messages) {
|
async add_messages(messages) {
|
||||||
let old_by_id = Object.fromEntries(old_messages.map((x) => [x.id, x]));
|
this.messages = await this.decrypt([...messages, ...this.messages]);
|
||||||
return new_messages.map((x) => (old_by_id[x.id] ? old_by_id[x.id] : x));
|
|
||||||
}
|
|
||||||
|
|
||||||
async load_latest() {
|
|
||||||
this.loading++;
|
|
||||||
let now = new Date().valueOf();
|
|
||||||
let end_time = now + 24 * 60 * 60 * 1000;
|
|
||||||
let messages = [];
|
|
||||||
try {
|
|
||||||
messages = await this.fetch_messages(this.time_range[0], end_time);
|
|
||||||
messages = await this.decrypt(messages);
|
|
||||||
this.update_time_range_from_messages(
|
|
||||||
messages.filter(
|
|
||||||
(x) => x.timestamp >= this.time_range[0] && x.timestamp < end_time
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
this.loading--;
|
|
||||||
}
|
|
||||||
this.messages = this.merge_messages(
|
|
||||||
this.messages,
|
|
||||||
Object.values(
|
|
||||||
Object.fromEntries(
|
|
||||||
[...this.messages, ...messages]
|
|
||||||
.sort((x, y) => x.timestamp - y.timestamp)
|
|
||||||
.slice(-1024)
|
|
||||||
.map((x) => [x.id, x])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
console.log('done loading latest messages.');
|
|
||||||
}
|
|
||||||
|
|
||||||
async load_messages() {
|
|
||||||
let start_time = new Date();
|
|
||||||
let self = this;
|
|
||||||
this.loading++;
|
|
||||||
let messages = [];
|
|
||||||
let original_hash = this.hash;
|
|
||||||
try {
|
|
||||||
if (this._messages_hash !== this.hash) {
|
|
||||||
this.messages = [];
|
|
||||||
this._messages_hash = this.hash;
|
|
||||||
}
|
|
||||||
this._messages_following = JSON.stringify(this.following);
|
|
||||||
this._private_messages = JSON.stringify([
|
|
||||||
this.private_messages,
|
|
||||||
this.grouped_private_messages,
|
|
||||||
]);
|
|
||||||
this._channels_latest = JSON.stringify(
|
|
||||||
Object.keys(this.channels_latest ?? {})
|
|
||||||
);
|
|
||||||
let now = new Date().valueOf();
|
|
||||||
let start_time = now - 24 * 60 * 60 * 1000;
|
|
||||||
this.start_time = start_time;
|
|
||||||
this.time_range = [now + 24 * 60 * 60 * 1000, now + 24 * 60 * 60 * 1000];
|
|
||||||
messages = await this.fetch_messages(null, this.time_range[1]);
|
|
||||||
this.update_time_range_from_messages(
|
|
||||||
messages.filter((x) => x.timestamp < this.time_range[1])
|
|
||||||
);
|
|
||||||
messages = await this.decrypt(messages);
|
|
||||||
} finally {
|
|
||||||
this.loading--;
|
|
||||||
}
|
|
||||||
if (this.hash == original_hash) {
|
|
||||||
this.messages = this.merge_messages(this.messages, messages);
|
|
||||||
}
|
|
||||||
this.time_loading = undefined;
|
|
||||||
console.log(
|
|
||||||
`loading ${messages.length} messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
mark_all_read() {
|
|
||||||
let newest = this.messages.reduce(
|
|
||||||
(accumulator, current) => Math.max(accumulator, current.rowid),
|
|
||||||
this.channels_latest[this.channel()] ?? -1
|
|
||||||
);
|
|
||||||
if (newest >= 0) {
|
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent('channelsetunread', {
|
|
||||||
bubbles: true,
|
|
||||||
composed: true,
|
|
||||||
detail: {
|
|
||||||
channel: this.channel(),
|
|
||||||
unread: newest + 1,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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_hash !== this.hash ||
|
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))
|
|
||||||
) {
|
) {
|
||||||
console.log(
|
console.log(
|
||||||
`loading messages for ${this.whoami} (messages=${!this.messages},${this._messages_hash != this.hash} following=${this._messages_following !== JSON.stringify(this.following)}, channels=${this._channels_latest !== JSON.stringify(Object.keys(this.channels_latest))}, private=${this._private_messages !== JSON.stringify([this.private_messages, this.grouped_private_messages])},${this.private_messages?.length},${Object.keys(this.grouped_private_messages ?? {}).length})`
|
`loading messages for ${this.whoami} (following ${this.following.length})`
|
||||||
);
|
);
|
||||||
this.load_messages();
|
let self = this;
|
||||||
|
this.messages = [];
|
||||||
|
this._messages_hash = this.hash;
|
||||||
|
this._messages_following = this.following;
|
||||||
|
this.fetch_messages()
|
||||||
|
.then(this.decrypt.bind(this))
|
||||||
|
.then(function (messages) {
|
||||||
|
self.messages = messages;
|
||||||
|
console.log(`loading mesages done for ${self.whoami}`);
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
alert(JSON.stringify(error, null, 2));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
let more;
|
let more;
|
||||||
if (!this.hash.startsWith('#%')) {
|
if (!this.hash.startsWith('#@') && !this.hash.startsWith('#%')) {
|
||||||
more = html`
|
more = html`
|
||||||
<p>
|
<p>
|
||||||
${this.unread_allowed()
|
<button class="w3-button w3-dark-grey" @click=${this.load_more}>
|
||||||
? html`
|
|
||||||
<button
|
|
||||||
class="w3-button w3-theme-d1"
|
|
||||||
@click=${this.mark_all_read}
|
|
||||||
>
|
|
||||||
Mark All Read
|
|
||||||
</button>
|
|
||||||
`
|
|
||||||
: undefined}
|
|
||||||
<button
|
|
||||||
?disabled=${this.loading}
|
|
||||||
class="w3-button w3-theme-d1"
|
|
||||||
@click=${this.load_more}
|
|
||||||
>
|
|
||||||
Load More
|
Load More
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
class=${'w3-button w3-theme-d1' + (this.loading ? '' : ' w3-hide')}
|
|
||||||
@click=${this.cancel_load}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<span
|
|
||||||
>Showing
|
|
||||||
${new Date(
|
|
||||||
this.time_loading
|
|
||||||
? Math.min(this.time_loading[0], this.time_range[0])
|
|
||||||
: this.time_range[0]
|
|
||||||
).toLocaleDateString()}
|
|
||||||
-
|
|
||||||
${new Date(
|
|
||||||
this.time_loading
|
|
||||||
? Math.max(this.time_loading[1], this.time_range[1])
|
|
||||||
: this.time_range[1]
|
|
||||||
).toLocaleDateString()}.</span
|
|
||||||
>
|
|
||||||
</p>
|
</p>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
return cache(html`
|
return html`
|
||||||
<style>
|
|
||||||
${generate_theme()}
|
|
||||||
</style>
|
|
||||||
${this.unread_allowed()
|
|
||||||
? html`<button
|
|
||||||
class="w3-button w3-theme-d1"
|
|
||||||
@click=${this.mark_all_read}
|
|
||||||
>
|
|
||||||
Mark All Read
|
|
||||||
</button>`
|
|
||||||
: undefined}
|
|
||||||
${this.render_close_chat_button()}
|
|
||||||
<tf-news
|
<tf-news
|
||||||
id="news"
|
id="news"
|
||||||
whoami=${this.whoami}
|
whoami=${this.whoami}
|
||||||
@@ -553,14 +202,9 @@ 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_unread=${this.channels_unread?.[this.channel()]}
|
|
||||||
.recent_reactions=${this.recent_reactions}
|
|
||||||
@mark_all_read=${this.mark_all_read}
|
|
||||||
></tf-news>
|
></tf-news>
|
||||||
${more}
|
${more}
|
||||||
`);
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
import {
|
import {LitElement, html, unsafeHTML, until} from './lit-all.min.js';
|
||||||
LitElement,
|
|
||||||
cache,
|
|
||||||
keyed,
|
|
||||||
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 TfTabNewsElement extends LitElement {
|
class TfTabNewsElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@@ -15,21 +8,10 @@ class TfTabNewsElement extends LitElement {
|
|||||||
whoami: {type: String},
|
whoami: {type: String},
|
||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
hash: {type: String},
|
hash: {type: String},
|
||||||
|
unread: {type: Array},
|
||||||
following: {type: Array},
|
following: {type: Array},
|
||||||
drafts: {type: Object},
|
drafts: {type: Object},
|
||||||
expanded: {type: Object},
|
expanded: {type: Object},
|
||||||
loading: {type: Boolean},
|
|
||||||
channels: {type: Array},
|
|
||||||
channels_unread: {type: Object},
|
|
||||||
channels_latest: {type: Object},
|
|
||||||
connections: {type: Array},
|
|
||||||
private_messages: {type: Array},
|
|
||||||
grouped_private_messages: {type: Object},
|
|
||||||
visible_private_messages: {type: Object},
|
|
||||||
recent_reactions: {type: Array},
|
|
||||||
peer_exchange: {type: Boolean},
|
|
||||||
is_administrator: {type: Boolean},
|
|
||||||
stay_connected: {type: Boolean},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,19 +23,14 @@ class TfTabNewsElement extends LitElement {
|
|||||||
this.whoami = null;
|
this.whoami = null;
|
||||||
this.users = {};
|
this.users = {};
|
||||||
this.hash = '#';
|
this.hash = '#';
|
||||||
|
this.unread = [];
|
||||||
this.following = [];
|
this.following = [];
|
||||||
this.cache = {};
|
this.cache = {};
|
||||||
this.drafts = {};
|
this.drafts = {};
|
||||||
this.expanded = {};
|
this.expanded = {};
|
||||||
this.channels_unread = {};
|
|
||||||
this.channels_latest = {};
|
|
||||||
this.channels = [];
|
|
||||||
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,19 +43,37 @@ 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() {
|
show_more() {
|
||||||
if (await tfrpc.rpc.isAdministrator()) {
|
let unread = this.unread;
|
||||||
this.peer_exchange = await tfrpc.rpc.globalSettingsGet('peer_exchange');
|
let news = this.shadowRoot?.getElementById('news');
|
||||||
} else {
|
if (news) {
|
||||||
this.peer_exchange = undefined;
|
console.log('injecting messages', news.messages);
|
||||||
|
news.add_messages(
|
||||||
|
Object.values(Object.fromEntries(this.unread.map((x) => [x.id, x])))
|
||||||
|
);
|
||||||
|
this.dispatchEvent(new CustomEvent('refresh'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
load_latest() {
|
new_messages_text() {
|
||||||
let news = this.shadowRoot?.getElementById('news');
|
if (!this.unread?.length) {
|
||||||
if (news) {
|
return 'No new messages.';
|
||||||
news.load_latest();
|
|
||||||
}
|
}
|
||||||
|
let counts = {};
|
||||||
|
for (let message of this.unread) {
|
||||||
|
let type = 'private';
|
||||||
|
try {
|
||||||
|
type = JSON.parse(message.content).type || type;
|
||||||
|
} catch {}
|
||||||
|
counts[type] = (counts[type] || 0) + 1;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
'↻ Show New: ' +
|
||||||
|
Object.keys(counts)
|
||||||
|
.sort()
|
||||||
|
.map((x) => counts[x].toString() + ' ' + x + 's')
|
||||||
|
.join(', ')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
draft(event) {
|
draft(event) {
|
||||||
@@ -89,7 +84,10 @@ class TfTabNewsElement extends LitElement {
|
|||||||
} else {
|
} else {
|
||||||
delete this.drafts[id];
|
delete this.drafts[id];
|
||||||
}
|
}
|
||||||
|
/* Only trigger a re-render if we're creating a new draft or discarding an old one. */
|
||||||
|
if ((previous !== undefined) != (event.detail.draft !== undefined)) {
|
||||||
this.drafts = Object.assign({}, this.drafts);
|
this.drafts = Object.assign({}, this.drafts);
|
||||||
|
}
|
||||||
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
|
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,331 +108,25 @@ class TfTabNewsElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unread_status(channel) {
|
|
||||||
if (channel === undefined) {
|
|
||||||
if (
|
|
||||||
Object.keys(this.channels_unread).some((x) => this.unread_status(x))
|
|
||||||
) {
|
|
||||||
return '✉️ ';
|
|
||||||
}
|
|
||||||
} else if (channel?.startsWith('🔐')) {
|
|
||||||
let key = JSON.stringify(channel.substring('🔐'.length).split(','));
|
|
||||||
if (this.grouped_private_messages?.[key]) {
|
|
||||||
let grouped_latest = Math.max(
|
|
||||||
...this.grouped_private_messages?.[key]?.map((x) => x.rowid)
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
this.channels_unread[channel] === undefined ||
|
|
||||||
grouped_latest > this.channels_unread[channel]
|
|
||||||
) {
|
|
||||||
return '✉️ ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
this.channels_latest[channel] &&
|
|
||||||
this.channels_latest[channel] > 0 &&
|
|
||||||
(this.channels_unread[channel] === undefined ||
|
|
||||||
this.channels_unread[channel] <= this.channels_latest[channel])
|
|
||||||
) {
|
|
||||||
return '✉️ ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
show_sidebar() {
|
|
||||||
this.renderRoot.getElementById('sidebar').style.display = 'block';
|
|
||||||
this.renderRoot.getElementById('sidebar_overlay').style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
hide_sidebar() {
|
|
||||||
this.renderRoot.getElementById('sidebar').style.display = 'none';
|
|
||||||
this.renderRoot.getElementById('sidebar_overlay').style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
async channel_toggle_subscribed() {
|
|
||||||
let channel = this.hash.substring(2);
|
|
||||||
let subscribed = this.channels.indexOf(channel) != -1;
|
|
||||||
subscribed = !subscribed;
|
|
||||||
|
|
||||||
await tfrpc.rpc.appendMessage(this.whoami, {
|
|
||||||
type: 'channel',
|
|
||||||
channel: channel,
|
|
||||||
subscribed: subscribed,
|
|
||||||
});
|
|
||||||
if (subscribed) {
|
|
||||||
this.channels = [].concat([channel], this.channels).sort();
|
|
||||||
} else {
|
|
||||||
this.channels = this.channels.filter((x) => x != channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
channel() {
|
|
||||||
return this.hash.startsWith('##') ? this.hash.substring(2) : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
compare_follows(a, b) {
|
|
||||||
return b[1].ts > a[1].ts ? 1 : b[1].ts < a[1].ts ? -1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
suggested_follows() {
|
|
||||||
/*
|
|
||||||
** Filter out people who have used future timestamps so that they aren't
|
|
||||||
** pinned at the top.
|
|
||||||
*/
|
|
||||||
let self = this;
|
|
||||||
let now = new Date().valueOf();
|
|
||||||
return Object.entries(this.users)
|
|
||||||
.filter((x) => x[1].ts < now)
|
|
||||||
.filter((x) => x[1].follow_depth > 1)
|
|
||||||
.sort(self.compare_follows)
|
|
||||||
.slice(0, 8)
|
|
||||||
.map((x) => x[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async enable_peer_exchange() {
|
|
||||||
await tfrpc.rpc.globalSettingsSet('peer_exchange', true);
|
|
||||||
await this.check_peer_exchange();
|
|
||||||
}
|
|
||||||
|
|
||||||
is_loading() {
|
|
||||||
return this.shadowRoot?.getElementById('news')?.loading;
|
|
||||||
}
|
|
||||||
|
|
||||||
render_sidebar() {
|
|
||||||
return html`
|
|
||||||
<div
|
|
||||||
class="w3-sidebar w3-bar-block w3-theme-d1 w3-collapse w3-animate-left"
|
|
||||||
style="width: 2in; left: 0; z-index: 5; box-sizing: border-box; top: 0"
|
|
||||||
id="sidebar"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="w3-right w3-button w3-hide-large"
|
|
||||||
@click=${this.hide_sidebar}
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</div>
|
|
||||||
${this.hash.startsWith('##') &&
|
|
||||||
this.channels.indexOf(this.hash.substring(2)) == -1
|
|
||||||
? html`
|
|
||||||
<div class="w3-bar-item w3-theme-d2">Viewing</div>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
class="w3-bar-item w3-button"
|
|
||||||
style="font-weight: bold"
|
|
||||||
>${this.hash.substring(1)}</a
|
|
||||||
>
|
|
||||||
`
|
|
||||||
: undefined}
|
|
||||||
<h4 class="w3-bar-item w3-theme-d2">Channels</h4>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
class="w3-bar-item w3-button"
|
|
||||||
style=${this.hash == '#' ? 'font-weight: bold' : undefined}
|
|
||||||
>${this.unread_status('')}general</a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="#@"
|
|
||||||
class="w3-bar-item w3-button"
|
|
||||||
style=${this.hash == '#@' ? 'font-weight: bold' : undefined}
|
|
||||||
>${this.unread_status('@')}@mentions</a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="#👍"
|
|
||||||
class="w3-bar-item w3-button"
|
|
||||||
style=${this.hash == '#👍' ? 'font-weight: bold' : undefined}
|
|
||||||
>${this.unread_status('👍')}👍votes</a
|
|
||||||
>
|
|
||||||
${Object.keys(this?.visible_private_messages ?? [])
|
|
||||||
?.sort()
|
|
||||||
?.map(
|
|
||||||
(key) => html`
|
|
||||||
<a
|
|
||||||
href=${'#🔐' + JSON.parse(key).join(',')}
|
|
||||||
class="w3-bar-item w3-button"
|
|
||||||
style=${this.hash == '#🔐' + JSON.parse(key).join(',')
|
|
||||||
? 'font-weight: bold'
|
|
||||||
: undefined}
|
|
||||||
>${this.unread_status('🔐' + JSON.parse(key).join(','))}
|
|
||||||
${(key != '[]' ? JSON.parse(key) : [this.whoami]).map(
|
|
||||||
(id) => html`
|
|
||||||
<tf-user
|
|
||||||
id=${id}
|
|
||||||
nolink="true"
|
|
||||||
.users=${this.users}
|
|
||||||
></tf-user>
|
|
||||||
`
|
|
||||||
)}</a
|
|
||||||
>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
${Object.keys(this.drafts)
|
|
||||||
.sort()
|
|
||||||
.map(
|
|
||||||
(x) => html`
|
|
||||||
<a
|
|
||||||
href=${'#' + encodeURIComponent(x)}
|
|
||||||
class="w3-bar-item w3-button"
|
|
||||||
style="text-wrap: nowrap; text-overflow: ellipsis"
|
|
||||||
>📝 ${this.drafts[x]?.text ?? x}</a
|
|
||||||
>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
${this.channels.map(
|
|
||||||
(x) => html`
|
|
||||||
<a
|
|
||||||
href=${'#' + encodeURIComponent('#' + x)}
|
|
||||||
class="w3-bar-item w3-button"
|
|
||||||
style=${this.hash == '##' + x ? 'font-weight: bold' : undefined}
|
|
||||||
>${this.unread_status(x)}#${x}</a
|
|
||||||
>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
|
|
||||||
<a class="w3-bar-item w3-theme-d2 w3-button" href="#connections">
|
|
||||||
<h4 style="margin: 0">Connections</h4>
|
|
||||||
</a>
|
|
||||||
${this.connections?.filter((x) => x.id)?.length == 0
|
|
||||||
? html`
|
|
||||||
<button
|
|
||||||
class=${'w3-bar-item w3-button' +
|
|
||||||
(this.connections?.some((x) => x.flags.one_shot)
|
|
||||||
? ' w3-spin'
|
|
||||||
: '')}
|
|
||||||
@click=${() =>
|
|
||||||
this.dispatchEvent(
|
|
||||||
new Event('refresh', {bubbles: true, composed: true})
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
↻ Sync now
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="w3-bar-item w3-button w3-ripple"
|
|
||||||
@click=${() =>
|
|
||||||
this.dispatchEvent(
|
|
||||||
new Event('toggle_stay_connected', {
|
|
||||||
bubbles: true,
|
|
||||||
composed: true,
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span style="display: inline-block; width: 1.8em"
|
|
||||||
>${this.stay_connected ? '🔗' : '⛓️💥'}</span
|
|
||||||
>
|
|
||||||
${this.stay_connected ? 'Online mode' : 'Passive mode'}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class=${'w3-bar-item w3-button w3-border w3-leftbar w3-rightbar' +
|
|
||||||
(this.peer_exchange !== false ? ' w3-hide' : '')}
|
|
||||||
@click=${this.enable_peer_exchange}
|
|
||||||
>
|
|
||||||
🔍🌐 Use publicly advertised peers
|
|
||||||
</button>
|
|
||||||
`
|
|
||||||
: undefined}
|
|
||||||
${this.connections
|
|
||||||
.filter((x) => x.id)
|
|
||||||
.map(
|
|
||||||
(x) => html`
|
|
||||||
<tf-user
|
|
||||||
class="w3-bar-item"
|
|
||||||
style=${x.destroy_reason
|
|
||||||
? 'border-left: 4px solid red; border-right: 4px solid red'
|
|
||||||
: x.connected
|
|
||||||
? x.flags?.one_shot
|
|
||||||
? 'border-left: 4px solid blue; border-right: 4px solid blue'
|
|
||||||
: 'border-left: 4px solid green; border-right: 4px solid green'
|
|
||||||
: ''}
|
|
||||||
id=${x.id}
|
|
||||||
fallback_name=${x.host}
|
|
||||||
.users=${this.users}
|
|
||||||
></tf-user>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
<h4 class="w3-bar-item w3-theme-d2">Suggested Follows</h4>
|
|
||||||
${this.suggested_follows().map(
|
|
||||||
(x) => html`
|
|
||||||
<tf-user
|
|
||||||
class="w3-bar-item"
|
|
||||||
style="max-width: 100%"
|
|
||||||
id=${x}
|
|
||||||
.users=${this.users}
|
|
||||||
></tf-user>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="w3-overlay"
|
|
||||||
id="sidebar_overlay"
|
|
||||||
@click=${this.hide_sidebar}
|
|
||||||
></div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let profile =
|
let profile = this.hash.startsWith('#@')
|
||||||
this.hash.startsWith('#@') && this.hash != '#@'
|
? html`<tf-profile
|
||||||
? keyed(
|
|
||||||
this.hash.substring(1),
|
|
||||||
html`<tf-profile
|
|
||||||
class="tf-profile"
|
|
||||||
id=${this.hash.substring(1)}
|
id=${this.hash.substring(1)}
|
||||||
whoami=${this.whoami}
|
whoami=${this.whoami}
|
||||||
.users=${this.users}
|
.users=${this.users}
|
||||||
></tf-profile>`
|
></tf-profile>`
|
||||||
)
|
|
||||||
: undefined;
|
: undefined;
|
||||||
let edit_profile;
|
return html`
|
||||||
if (
|
<p class="w3-bar">
|
||||||
!this.loading &&
|
|
||||||
this.users[this.whoami]?.name === undefined &&
|
|
||||||
this.hash.substring(1) != this.whoami
|
|
||||||
) {
|
|
||||||
edit_profile = html` <div
|
|
||||||
class="w3-panel w3-padding w3-round w3-card-4 w3-theme-l3"
|
|
||||||
>
|
|
||||||
ℹ️ Follow your identity link ☝️ above to edit your profile and set your
|
|
||||||
name.
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
return cache(html`
|
|
||||||
<style>
|
|
||||||
${generate_theme()}
|
|
||||||
</style>
|
|
||||||
${this.render_sidebar()}
|
|
||||||
<div
|
|
||||||
style="margin-left: 2in; padding: 0px; top: 0; height: 100vh; max-height: 100%; overflow: auto; contain: layout"
|
|
||||||
id="main"
|
|
||||||
class="w3-main"
|
|
||||||
>
|
|
||||||
<div style="padding: 8px">
|
|
||||||
<p>
|
|
||||||
${this.hash.startsWith('##')
|
|
||||||
? html`
|
|
||||||
<button
|
<button
|
||||||
class="w3-button w3-theme-d1"
|
class="w3-bar-item w3-button w3-dark-grey"
|
||||||
@click=${this.channel_toggle_subscribed}
|
@click=${this.show_more}
|
||||||
>
|
>
|
||||||
${this.channels.indexOf(this.hash.substring(2)) != -1
|
${this.new_messages_text()}
|
||||||
? 'Unsubscribe from #'
|
|
||||||
: 'Subscribe to #'}${this.hash.substring(2)}
|
|
||||||
</button>
|
</button>
|
||||||
`
|
|
||||||
: undefined}
|
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<div
|
Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
|
||||||
id="show_sidebar"
|
|
||||||
class="w3-button w3-hide-large"
|
|
||||||
@click=${this.show_sidebar}
|
|
||||||
>
|
|
||||||
${this.unread_status()}☰
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
style="display: inline-block; width: 100%; max-width: 100%; white-space: nowrap; overflow: hidden"
|
|
||||||
>
|
|
||||||
Welcome,
|
|
||||||
<tf-user id=${this.whoami} .users=${this.users}></tf-user>!
|
|
||||||
</span>
|
|
||||||
${edit_profile}
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<tf-compose
|
<tf-compose
|
||||||
@@ -443,10 +135,6 @@ class TfTabNewsElement extends LitElement {
|
|||||||
.users=${this.users}
|
.users=${this.users}
|
||||||
.drafts=${this.drafts}
|
.drafts=${this.drafts}
|
||||||
@tf-draft=${this.draft}
|
@tf-draft=${this.draft}
|
||||||
.channel=${this.channel()}
|
|
||||||
.recipients=${this.hash.startsWith('#🔐')
|
|
||||||
? this.hash.substring('#🔐'.length).split(',')
|
|
||||||
: undefined}
|
|
||||||
></tf-compose>
|
></tf-compose>
|
||||||
</div>
|
</div>
|
||||||
${profile}
|
${profile}
|
||||||
@@ -460,15 +148,8 @@ class TfTabNewsElement extends LitElement {
|
|||||||
.expanded=${this.expanded}
|
.expanded=${this.expanded}
|
||||||
@tf-draft=${this.draft}
|
@tf-draft=${this.draft}
|
||||||
@tf-expand=${this.on_expand}
|
@tf-expand=${this.on_expand}
|
||||||
.channels_unread=${this.channels_unread}
|
|
||||||
.channels_latest=${this.channels_latest}
|
|
||||||
.private_messages=${this.private_messages}
|
|
||||||
.grouped_private_messages=${this.grouped_private_messages}
|
|
||||||
.recent_reactions=${this.recent_reactions}
|
|
||||||
></tf-tab-news-feed>
|
></tf-tab-news-feed>
|
||||||
</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-dark-grey"
|
||||||
|
style="flex: 1; resize: vertical"
|
||||||
|
@keydown=${this.search_keydown}
|
||||||
|
>
|
||||||
|
${this.query}</textarea
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="w3-button w3-dark-grey"
|
||||||
|
@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,19 +1,15 @@
|
|||||||
import {LitElement, html, 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 {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() {
|
||||||
return {
|
return {
|
||||||
drafts: {type: Object},
|
|
||||||
whoami: {type: String},
|
whoami: {type: String},
|
||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
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},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,10 +22,6 @@ class TfTabSearchElement extends LitElement {
|
|||||||
this.users = {};
|
this.users = {};
|
||||||
this.following = [];
|
this.following = [];
|
||||||
this.expanded = {};
|
this.expanded = {};
|
||||||
this.drafts = {};
|
|
||||||
tfrpc.rpc.localStorageGet('drafts').then(function (d) {
|
|
||||||
self.drafts = JSON.parse(d || '{}');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(query) {
|
async search(query) {
|
||||||
@@ -41,21 +33,6 @@ 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;
|
|
||||||
this.results = [];
|
|
||||||
this.messages = [];
|
|
||||||
if (query.startsWith('sql:')) {
|
|
||||||
this.messages = [];
|
|
||||||
try {
|
|
||||||
this.results = await tfrpc.rpc.query(
|
|
||||||
query.substring('sql:'.length),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
this.results = [];
|
|
||||||
this.error = e;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let results = await tfrpc.rpc.query(
|
let results = await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
@@ -73,8 +50,7 @@ class TfTabSearchElement extends LitElement {
|
|||||||
search.focus();
|
search.focus();
|
||||||
search.select();
|
search.select();
|
||||||
}
|
}
|
||||||
this.messages = results;
|
this.renderRoot.getElementById('news').messages = results;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
search_keydown(event) {
|
search_keydown(event) {
|
||||||
@@ -94,51 +70,6 @@ class TfTabSearchElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
draft(event) {
|
|
||||||
let id = event.detail.id || '';
|
|
||||||
let previous = this.drafts[id];
|
|
||||||
if (event.detail.draft !== undefined) {
|
|
||||||
this.drafts[id] = event.detail.draft;
|
|
||||||
} else {
|
|
||||||
delete this.drafts[id];
|
|
||||||
}
|
|
||||||
this.drafts = Object.assign({}, this.drafts);
|
|
||||||
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
|
|
||||||
}
|
|
||||||
|
|
||||||
render_results() {
|
|
||||||
if (this.error) {
|
|
||||||
return html`<h2 style="color: red">${this.error.message}</h2>
|
|
||||||
<pre style="color: red">${this.error.stack}</pre>`;
|
|
||||||
} else if (this.messages?.length) {
|
|
||||||
return html`<tf-news
|
|
||||||
id="news"
|
|
||||||
whoami=${this.whoami}
|
|
||||||
.messages=${this.messages}
|
|
||||||
.users=${this.users}
|
|
||||||
.expanded=${this.expanded}
|
|
||||||
.drafts=${this.drafts}
|
|
||||||
@tf-expand=${this.on_expand}
|
|
||||||
@tf-draft=${this.draft}
|
|
||||||
></tf-news>`;
|
|
||||||
} else if (this.results?.length) {
|
|
||||||
let keys = Object.keys(this.results[0]).sort();
|
|
||||||
return html`<table style="width: 100%; max-width: 100%">
|
|
||||||
<tr>
|
|
||||||
${keys.map((key) => html`<th>${key}</th>`)}
|
|
||||||
</tr>
|
|
||||||
${this.results.map(
|
|
||||||
(row) =>
|
|
||||||
html`<tr>
|
|
||||||
${keys.map((key) => html`<td>${row[key]}</td>`)}
|
|
||||||
</tr>`
|
|
||||||
)}
|
|
||||||
</table>`;
|
|
||||||
} else {
|
|
||||||
return html`<div>No results.</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.query !== this.last_query) {
|
if (this.query !== this.last_query) {
|
||||||
this.last_query = this.query;
|
this.last_query = this.query;
|
||||||
@@ -146,14 +77,11 @@ class TfTabSearchElement extends LitElement {
|
|||||||
}
|
}
|
||||||
let self = this;
|
let self = this;
|
||||||
return html`
|
return html`
|
||||||
<style>${generate_theme()}</style>
|
|
||||||
<div class="w3-padding">
|
|
||||||
<div style="display: flex; flex-direction: row; gap: 4px">
|
<div style="display: flex; flex-direction: row; gap: 4px">
|
||||||
<input type="text" class="w3-input w3-theme-d1" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
|
<input type="text" class="w3-input w3-dark-grey" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
|
||||||
<button class="w3-button w3-theme-d1" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button>
|
<button class="w3-button w3-dark-grey" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button>
|
||||||
</div>
|
|
||||||
${this.render_results()}
|
|
||||||
</div>
|
</div>
|
||||||
|
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} @tf-expand=${this.on_expand}></tf-news>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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="#q=${this.tag}"
|
||||||
${generate_theme()}</style
|
style="display: inline-block; margin: 3px; border: 1px solid black; background-color: #444; padding: 4px; border-radius: 3px"
|
||||||
><a
|
|
||||||
href=${'#' + encodeURIComponent(this.tag)}
|
|
||||||
class="w3-tag w3-theme-d1 w3-round-4 w3-button"
|
|
||||||
>${this.tag}${number}</a
|
>${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,55 +15,32 @@ 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 shape =
|
|
||||||
user?.follow_depth === undefined || user.follow_depth >= 2
|
|
||||||
? 'w3-circle'
|
|
||||||
: 'w3-round';
|
|
||||||
let image = html`<span
|
|
||||||
class=${'w3-theme-l4 ' + shape}
|
|
||||||
style="display: inline-block; width: 2em; height: 2em; text-align: center; line-height: 2em"
|
|
||||||
>😎</span
|
|
||||||
>`;
|
|
||||||
let name = this.users?.[this.id]?.name;
|
let name = this.users?.[this.id]?.name;
|
||||||
let name_string = name ?? this.fallback_name ?? this.id;
|
name =
|
||||||
name = this.icon_only
|
name !== undefined
|
||||||
? undefined
|
? html`<a target="_top" href=${'#' + this.id}>${name}</a>`
|
||||||
: !this.nolink
|
: html`<a target="_top" href=${'#' + this.id}>${this.id}</a>`;
|
||||||
? html`<a target="_top" href=${'#' + this.id}>${name_string}</a>`
|
|
||||||
: html`<span>${name_string}</span>`;
|
|
||||||
|
|
||||||
if (user) {
|
if (this.users[this.id]) {
|
||||||
let image_link = user.image;
|
let image = this.users[this.id].image;
|
||||||
if (typeof image_link == 'string' && !image_link.startsWith('&')) {
|
image = typeof image == 'string' ? image : image?.link;
|
||||||
try {
|
return html` <div style="display: inline-block; font-weight: bold">
|
||||||
image_link = JSON.parse(image_link)?.link;
|
<img
|
||||||
} catch {}
|
style="width: 2em; height: 2em; vertical-align: middle; border-radius: 50%"
|
||||||
}
|
?hidden=${image === undefined}
|
||||||
if (image_link !== undefined) {
|
src="${image ? '/' + image + '/view' : undefined}"
|
||||||
image = html`<img
|
/>
|
||||||
class=${'w3-theme-l4 ' + shape}
|
${name}
|
||||||
style="width: 2em; height: 2em; vertical-align: middle; object-fit: cover"
|
|
||||||
src="/${image_link}/view"
|
|
||||||
title=${name_string + ' (' + this.id + ')'}
|
|
||||||
/>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return html` <style>
|
|
||||||
${generate_theme()}
|
|
||||||
</style>
|
|
||||||
<div
|
|
||||||
style=${'display: inline-block; vertical-align: middle; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis' +
|
|
||||||
(this.nolink ? '' : '; font-weight: bold')}
|
|
||||||
>
|
|
||||||
${image} ${name}
|
|
||||||
</div>`;
|
</div>`;
|
||||||
|
} else {
|
||||||
|
return html` <div style="display: inline-block; font-weight: bold">
|
||||||
|
${name}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
|
import * as linkify from './commonmark-linkify.js';
|
||||||
import * as hashtagify from './commonmark-hashtag.js';
|
import * as hashtagify from './commonmark-hashtag.js';
|
||||||
|
|
||||||
const k_code_classes = 'w3-theme-l4 w3-theme-border w3-round';
|
|
||||||
|
|
||||||
var reUnsafeProtocol = /^javascript:|vbscript:|file:|data:/i;
|
|
||||||
var reSafeDataProtocol = /^data:image\/(?:png|gif|jpeg|webp)/i;
|
|
||||||
var potentiallyUnsafe = function (url) {
|
|
||||||
return reUnsafeProtocol.test(url) && !reSafeDataProtocol.test(url);
|
|
||||||
};
|
|
||||||
|
|
||||||
function image(node, entering) {
|
function image(node, entering) {
|
||||||
if (
|
if (
|
||||||
node.firstChild?.type === 'text' &&
|
node.firstChild?.type === 'text' &&
|
||||||
@@ -50,9 +43,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;
|
||||||
@@ -68,32 +61,13 @@ function image(node, entering) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function code(node) {
|
|
||||||
let attrs = this.attrs(node);
|
|
||||||
attrs.push(['class', k_code_classes]);
|
|
||||||
this.tag('code', attrs);
|
|
||||||
this.out(node.literal);
|
|
||||||
this.tag('/code');
|
|
||||||
}
|
|
||||||
|
|
||||||
function attrs(node) {
|
|
||||||
let result = commonmark.HtmlRenderer.prototype.attrs.bind(this)(node);
|
|
||||||
if (node.type == 'block_quote') {
|
|
||||||
result.push(['class', 'w3-theme-d1']);
|
|
||||||
} else if (node.type == 'code_block') {
|
|
||||||
result.push(['class', k_code_classes]);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function markdown(md) {
|
export function markdown(md) {
|
||||||
let reader = new commonmark.Parser();
|
let reader = new commonmark.Parser({safe: true});
|
||||||
let writer = new commonmark.HtmlRenderer({safe: true});
|
let writer = new commonmark.HtmlRenderer();
|
||||||
writer.image = image;
|
writer.image = image;
|
||||||
writer.code = code;
|
|
||||||
writer.attrs = attrs;
|
|
||||||
let parsed = reader.parse(md || '');
|
let parsed = reader.parse(md || '');
|
||||||
parsed = hashtagify.transform(parsed);
|
parsed = hashtagify.transform(parsed);
|
||||||
|
parsed = linkify.transform(parsed);
|
||||||
let walker = parsed.walker();
|
let walker = parsed.walker();
|
||||||
let event, node;
|
let event, node;
|
||||||
while ((event = walker.next())) {
|
while ((event = walker.next())) {
|
||||||
@@ -104,12 +78,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')
|
||||||
|
|||||||
@@ -482,7 +482,16 @@ class TributeRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getDocument() {
|
getDocument() {
|
||||||
return document;
|
let iframe;
|
||||||
|
if (this.tribute.current.collection) {
|
||||||
|
iframe = this.tribute.current.collection.iframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!iframe) {
|
||||||
|
return document
|
||||||
|
}
|
||||||
|
|
||||||
|
return iframe.contentWindow.document
|
||||||
}
|
}
|
||||||
|
|
||||||
positionMenuAtCaret(scrollTo) {
|
positionMenuAtCaret(scrollTo) {
|
||||||
@@ -644,8 +653,8 @@ class TributeRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getWindowSelection() {
|
getWindowSelection() {
|
||||||
if (this.tribute.collection[0].iframe?.getSelection) {
|
if (this.tribute.collection.iframe) {
|
||||||
return this.tribute.collection[0].iframe.getSelection()
|
return this.tribute.collection.iframe.contentWindow.getSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
return window.getSelection()
|
return window.getSelection()
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "tildefriends-app",
|
|
||||||
"emoji": "💾",
|
|
||||||
"previous": "&tzZFIe7Y54O4sx1QtAPdemkXh+p5qHXSG/dlS7NP6OQ=.sha256"
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
async function query(sql, args) {
|
|
||||||
let rows = [];
|
|
||||||
await ssb.sqlAsync(sql, args ?? [], function (row) {
|
|
||||||
rows.push(row);
|
|
||||||
});
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function get_biggest() {
|
|
||||||
return query(`
|
|
||||||
select author, size from messages_stats group by author order by size desc limit 10;
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function get_total() {
|
|
||||||
return (
|
|
||||||
await query(`
|
|
||||||
select sum(length(content)) as size, count(distinct author) as count from messages;
|
|
||||||
`)
|
|
||||||
)[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function get_names(identities) {
|
|
||||||
return 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)]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function get_most_follows() {
|
|
||||||
return query(`
|
|
||||||
select author, count(*) as count
|
|
||||||
from messages
|
|
||||||
where content ->> 'type' = 'contact' and content ->> 'following' = true
|
|
||||||
group by author
|
|
||||||
order by count desc
|
|
||||||
limit 10;
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function nice_size(bytes) {
|
|
||||||
let value = bytes;
|
|
||||||
let index = 0;
|
|
||||||
let units = ['B', 'kB', 'MB', 'GB'];
|
|
||||||
while (value > 1024 && index < units.length - 1) {
|
|
||||||
value /= 1024;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
return `${Math.round(value * 10) / 10} ${units[index]}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
await app.setDocument('<p style="color: #fff">Analyzing feeds...</p>');
|
|
||||||
let most_follows = get_most_follows();
|
|
||||||
let total = await get_total();
|
|
||||||
let identities = await ssb.getAllIdentities();
|
|
||||||
let following1 = await ssb.following(identities, 1);
|
|
||||||
let following2 = await ssb.following(identities, 2);
|
|
||||||
let biggest = await get_biggest();
|
|
||||||
most_follows = await most_follows;
|
|
||||||
let names = await get_names(
|
|
||||||
[].concat(
|
|
||||||
biggest.map((x) => x.author),
|
|
||||||
most_follows.map((x) => x.author)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
names = Object.fromEntries(names.map((x) => [x.author, x.name]));
|
|
||||||
for (let item of biggest) {
|
|
||||||
item.name = names[item.author];
|
|
||||||
item.following =
|
|
||||||
identities.indexOf(item.author) != -1
|
|
||||||
? 0
|
|
||||||
: following1[item.author] !== undefined
|
|
||||||
? 1
|
|
||||||
: following2[item.author] !== undefined
|
|
||||||
? 2
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
for (let item of most_follows) {
|
|
||||||
item.name = names[item.author];
|
|
||||||
}
|
|
||||||
let html = `<body style="color: #000; background-color: #ddd">\n
|
|
||||||
<h1>Storage Summary</h1>
|
|
||||||
<h2>Top Accounts by Size</h2>
|
|
||||||
<ol>`;
|
|
||||||
for (let item of biggest) {
|
|
||||||
html += `<li>
|
|
||||||
<span style="color: #888">${nice_size(item.size)}</span>
|
|
||||||
<a target="_top" href="/~core/ssb/#${encodeURI(item.author)}">${item.name ?? item.author}</a>
|
|
||||||
</li>
|
|
||||||
\n`;
|
|
||||||
}
|
|
||||||
html += `
|
|
||||||
</ol>
|
|
||||||
<h2>Top Accounts by Follows</h2>
|
|
||||||
<ol>`;
|
|
||||||
for (let item of most_follows) {
|
|
||||||
html += `<li>
|
|
||||||
<span style="color: #888">${item.count}</span>
|
|
||||||
${following2[item.author] ? '✅' : '🚫'}
|
|
||||||
<a target="_top" href="/~core/ssb/#${encodeURI(item.author)}">${item.name ?? item.author}</a>
|
|
||||||
</li>
|
|
||||||
\n`;
|
|
||||||
}
|
|
||||||
html += `
|
|
||||||
</ol>
|
|
||||||
<p>Total <span style="color: #888">${nice_size(total.size)}</span> in ${total.count} accounts.</p>
|
|
||||||
`;
|
|
||||||
await app.setDocument(html);
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch(function (e) {
|
|
||||||
print(e);
|
|
||||||
});
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "tildefriends-app",
|
|
||||||
"emoji": "📦"
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
app.setDocument(
|
|
||||||
'<p style="color: #fff">Maybe one day this app will run tests, but for now there is nothing to see here.</p>'
|
|
||||||
);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Hello, world!
|
|
||||||
@@ -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-432XE7GS.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();
|
|
||||||
Binary file not shown.
@@ -1,2 +0,0 @@
|
|||||||
(()=>{var D="./favicon-16x16-VSI62OPJ.png";})();
|
|
||||||
//# sourceMappingURL=favicon-16x16-V2DMIAZS.js.map
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
(()=>{var T="./favicon-32x32-3EB2YCUY.png";})();
|
|
||||||
//# sourceMappingURL=favicon-32x32-THY3JDJL.js.map
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,2 +0,0 @@
|
|||||||
(()=>{var m="./favicon-FOKUP5Y5.ico";})();
|
|
||||||
//# sourceMappingURL=favicon-M34RF7BI.js.map
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<!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-GHPHNKXC.css">
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32-3EB2YCUY.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16-VSI62OPJ.png">
|
|
||||||
<link rel="icon" type="image/x-icon" href="favicon-FOKUP5Y5.ico">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script src="speedscope-432XE7GS.js"></script>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
speedscope@1.24.0
|
|
||||||
Mon Oct 20 18:11:29 PDT 2025
|
|
||||||
fc76932551754a442cd5c4f0afdba28032d14d8a
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
Copyright 2010-2019 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
|
|
||||||
|
|
||||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
||||||
|
|
||||||
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
|
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------------------------------
|
|
||||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
||||||
-----------------------------------------------------------
|
|
||||||
|
|
||||||
PREAMBLE
|
|
||||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
||||||
development of collaborative font projects, to support the font creation
|
|
||||||
efforts of academic and linguistic communities, and to provide a free and
|
|
||||||
open framework in which fonts may be shared and improved in partnership
|
|
||||||
with others.
|
|
||||||
|
|
||||||
The OFL allows the licensed fonts to be used, studied, modified and
|
|
||||||
redistributed freely as long as they are not sold by themselves. The
|
|
||||||
fonts, including any derivative works, can be bundled, embedded,
|
|
||||||
redistributed and/or sold with any software provided that any reserved
|
|
||||||
names are not used by derivative works. The fonts and derivatives,
|
|
||||||
however, cannot be released under any other type of license. The
|
|
||||||
requirement for fonts to remain under this license does not apply
|
|
||||||
to any document created using the fonts or their derivatives.
|
|
||||||
|
|
||||||
DEFINITIONS
|
|
||||||
"Font Software" refers to the set of files released by the Copyright
|
|
||||||
Holder(s) under this license and clearly marked as such. This may
|
|
||||||
include source files, build scripts and documentation.
|
|
||||||
|
|
||||||
"Reserved Font Name" refers to any names specified as such after the
|
|
||||||
copyright statement(s).
|
|
||||||
|
|
||||||
"Original Version" refers to the collection of Font Software components as
|
|
||||||
distributed by the Copyright Holder(s).
|
|
||||||
|
|
||||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
||||||
or substituting -- in part or in whole -- any of the components of the
|
|
||||||
Original Version, by changing formats or by porting the Font Software to a
|
|
||||||
new environment.
|
|
||||||
|
|
||||||
"Author" refers to any designer, engineer, programmer, technical
|
|
||||||
writer or other person who contributed to the Font Software.
|
|
||||||
|
|
||||||
PERMISSION & CONDITIONS
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
||||||
redistribute, and sell modified and unmodified copies of the Font
|
|
||||||
Software, subject to the following conditions:
|
|
||||||
|
|
||||||
1) Neither the Font Software nor any of its individual components,
|
|
||||||
in Original or Modified Versions, may be sold by itself.
|
|
||||||
|
|
||||||
2) Original or Modified Versions of the Font Software may be bundled,
|
|
||||||
redistributed and/or sold with any software, provided that each copy
|
|
||||||
contains the above copyright notice and this license. These can be
|
|
||||||
included either as stand-alone text files, human-readable headers or
|
|
||||||
in the appropriate machine-readable metadata fields within text or
|
|
||||||
binary files as long as those fields can be easily viewed by the user.
|
|
||||||
|
|
||||||
3) No Modified Version of the Font Software may use the Reserved Font
|
|
||||||
Name(s) unless explicit written permission is granted by the corresponding
|
|
||||||
Copyright Holder. This restriction only applies to the primary font name as
|
|
||||||
presented to the users.
|
|
||||||
|
|
||||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
||||||
Software shall not be used to promote, endorse or advertise any
|
|
||||||
Modified Version, except to acknowledge the contribution(s) of the
|
|
||||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
||||||
permission.
|
|
||||||
|
|
||||||
5) The Font Software, modified or unmodified, in part or in whole,
|
|
||||||
must be distributed entirely under this license, and must not be
|
|
||||||
distributed under any other license. The requirement for fonts to
|
|
||||||
remain under this license does not apply to any document created
|
|
||||||
using the Font Software.
|
|
||||||
|
|
||||||
TERMINATION
|
|
||||||
This license becomes null and void if any of the above conditions are
|
|
||||||
not met.
|
|
||||||
|
|
||||||
DISCLAIMER
|
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
||||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
||||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
||||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
||||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:"";content:none}table{border-collapse:collapse;border-spacing:0}html{overflow:hidden;height:100%}body{height:100%;overflow:auto}@font-face{font-family:Source Code Pro;font-weight:400;font-style:normal;font-stretch:normal;src:url("./SourceCodePro-Regular.ttf-ILST5JV6.woff2") format("woff2")}
|
|
||||||
/*# sourceMappingURL=speedscope-GHPHNKXC.css.map */
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "tildefriends-app",
|
|
||||||
"emoji": "🕸",
|
|
||||||
"previous": "&n7hu5b8/TsfiG6FDlCRG5nPCrIdCr96+xpIJ/aQT/uM=.sha256"
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user